Open kobruleht opened 3 years ago
@kobruleht what kind of app you are building right now? (to understand your needs better) the code you provided has is more about MVC rather the RazorEngine itself.
I'm building ASP.NET 5 MVC Shopping cart + ERP application.
RazorEngineCore.Extension is used to inject Razor code from database into application razor views:
public async Task<string> RenderAsync<TModel>(string template, TModel model, ITempDataDictionary tempData)
{
int templateHashCode = template.GetHashCode();
if (!templateCache.TryGetValue(templateHashCode, out object vaart))
{
vaart = await CompileTempate<TModel>(template);
templateCache.TryAdd(templateHashCode, vaart);
}
var compiledTemplate = (IRazorEngineCompiledTemplate<MyRazorEngineCorePageModel<TModel>>) vaart;
return await compiledTemplate.RunAsync(instance =>
{
instance.Model = model;
instance.TempData = tempData;
instance.HttpContext = httpContextAccessor.HttpContext;
});
}
async Task<IRazorEngineCompiledTemplate<MyRazorEngineCorePageModel<TModel2>>> CompileTempate<TModel2>(string template)
{
var razorEngine = new RazorEngine();
var compiledTemplateLocal = await razorEngine.CompileAsync<MyRazorEngineCorePageModel<TModel2>>(template, builder =>
{
builder.AddAssemblyReference(typeof(RazorEngineCorePageModel));
builder.AddAssemblyReference(typeof(ITempDataDictionary));
builder.AddAssemblyReference(typeof(MyScaffoldContext));
builder.AddAssemblyReference(typeof(DbContext));
});
return compiledTemplateLocal;
}
public abstract class MyRazorEngineCorePageModel<T> : RazorEngineCorePageModel, IRazorEngineTemplate
{
public ITempDataDictionary TempData;
public HttpContext HttpContext;
}
Why dont you use Razor that comes together with MVC?
MVC does not allow to use templates from database. Those templates are created buy application users and needs rendered runtime, for example to create new shipping methods in shopping cart.
Thank you. It should be async version, @Html.PartialAsync
since sync version causes locks and will removed from .NET 6
I would have no issues adding asynchronous support but there will need to be changes added to the base project, template writing currently only supports synchronous writing (see here).
I will make the changes and raise a PR.
I posted it in https://github.com/adoconnection/RazorEngineCore/issues/48
@kobruleht
I have implemented the following:
@Html.Partial("Partial", new { Name = "Partial Template"})
@(await Html.PartialAsync("Partial", new { Name = "Partial Template"}))
/* Note that the second parameter (model) is optional and will pass through the parent model if not specified */
@(await Html.PartialAsync("Partial"))
I originally had plans for Html.PartialAsync(Func<string> func, ...)
, unfortunately Func<>
is not supported :/
I have added a basic PartialViewsManager
(it's the name I came up with at the time) to manage the Partial
views used by the extensions.
PartialViewsManager
is a static class that implements a ConcurrentDictionary<string, Lazy<...>>
, you can define your partials views before using .RunAsync(...)
In a nutshell, you can so the following.
// setup
PartialViewsManager.TryAdd("Body", SampleApp.Content.BodyContent);
PartialViewsManager.TryAdd("Partial", SampleApp.Content.PartialContent);
Primary template:
<!DOCTYPE html>
<html lang="en">
<body>
@(await Html.PartialAsync("Body"), new { Name = "Body"})
</body>
</html>
Body template:
<div>
<main>
Hello <b>@Model.Name</b>
@(await Html.PartialAsync("Partial", new { Name = "Partial Template"}))
</main>
</div>
Additional template:
Hello <b>@Model.Name</b>
This will render:
<!DOCTYPE html>
<html lang="en">
<body>
<div>
<main>
Hello <b>Body</b>
Hello <b>Partial Template</b>
</main>
</div>
</body>
</html>
Will it allow to use existing, pre-compiled views in applicaton, without using PartialViewsManager
?
@await Html.ParialAsync("_PartialName")
should find view in Views/Shared directory without additional settings required. In .NET 4 Antalis RazoreEngine does not have view manager. Not sure is view manager required.
@kobruleht
I am still looking into this, I will get to it later this evening (UK time here).
I did forget about pre-compiled
partials/views but will ensure they work as expected.
It's worth noting that RazorEngineCore
is NOT AspNetCore so things do behave differently (as I am sure you are aware), there's no mechanism to find templates as you would have in AspNetCore.
It's possible to implement some rudimentary finder/lookup mechanism if you are going to use .cshtml
templates from a Content resource (rather than strings) which would be better IMHO.
Making changes to the main repo owned by adoconnection would be simpler but I think he's against the whole Partial views thing (in his project), I have to make workarounds here to compensate for what I think is lacking (hence why this project exists).
I am limited in options when it comes to extending RazorEngineCore
w/o forking the entire project and creating another package (which there's a bunch [of broken ones] already on NuGet).
That all said, I will happily get something working and you are most welcome to add input, make requests and/or raise PRs for anything you need.
Code and SO link which I posted in this issue finds pre-compiled views by name and renders it as string:
var viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName);
var viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw);
viewResult.View.Render(viewContext, sw);
viewResult.ViewEngine.ReleaseView(ControllerContext, viewResult.View);
return new Htmlstring (sw.GetStringBuilder().ToString());
HtmlPartialAsync should run this code and return result string.
If application is published, there are no .cshtml files. Only compiled assemblies are published.
Builtin views contain @inject
commands which are not supported in RazorEngineExtensions. So those cannot compiled.
Since compiled code can used there is no reason to add cshtml files as resources.
Maybe it is possible to leave RazorEngineCore without MVC and dependency injection like author prefers and implement MVC specific thinga like Html.PartialAsync
and @inject
in your project using template base class.
In this case using MVC application willl require two packages included.
I am implementing the following.
RazorEngineCoreCompiler.MSBuild.Tasks
(this replaces the old RazorEngineCore.Precompiler
which never really worked)
Pages
directory where your .csproj
file is located, get all the .cshtml
files and compile them.To use the embedded templates should be as simple as.
var resourceTemplate = await PrecompiledTemplate.LoadAsync("Template", Assembly.GetExecutingAssembly());
await resourceTemplate.RunAsync(model: model);
These precompiled templates can have @Html.PartialAsync()
(or @Html.Partial()
), scanning for precompiled templates will be against the EntryAssembly (for now).
There's a bunch of things that still need to be optimised but here's a basic benchmark (Runtime vs Precompiled) of rendering templates
Results of 10 runs |
Runtime | total: 00:00:01.0446029; average: 00:00:00.1044602
Precompile | total: 00:00:00.2078749; average: 00:00:00.0207874
No sure is this required:
Scripts are created at runtime by users or by code using StringBuilder. Desing-time scripts can added into Views as .cshtml files. At build time they are compiled automatically. At runtime compiled code can retrieved by view name using
ViewEngines.Engines.FindPartialView(ControllerContext, viewName)
@kobruleht
Can you please tell me what you are attempting to achieve? can you create a repo and share the code (even if it's real
razor pages or mvc) so I can understand.
You keep posting the same code over again and I am not sure why.
ViewEngines.Engines.FindPartialView(ControllerContext, viewName)
RazorEngineCore has no concept of ControllerContext
or ViewControllers
In RazorPages (AspNetCore) the Pages
(a.k.a Views
) are compiled during build
to a satellite assembly (something like <assembly>.Views.dll
), (from my understanding) this is typically where AspNetCore will go find them.
Calling @Html.PartialAsync()
will go find this precompiled razor code during runtime, partial views are not rendered into the main
page during build (the partial [raw cshtml] isn't injected into the main page during the build).
From my understanding you do NOT want precompiled templates in your implementation as you said "templates are created buy application users and needs rendered runtime"
, "inject Razor code from database into application razor views"
Am I misunderstanding this? Do you use raw (string) templates in your database?
I think if you could create a small repo and show what your intensions are this would be a lot easier to understand.
There are many ways to combat the issue(s) w/o the need for adding additional dependencies.
There is partial view _LoginOrRegister in project in
Views\Shared\_LoginOrRegister.cshtml
Can your package allow to use it using
RazorEngine razorEngine = new RazorEngine();
var template =
await razorEngine.CompileAsync<RazorEngineCorePageModel>(
@"
@await HtmlPartialAsync(""_LoginOrRegister"")
");
var res = await template.RunAsync();
?
Absolutely, that's what I am working on at the moment.
There will be options to have the .cshtml
either ...
Precompiled, these are *.cshtml
that are compiled during the build process (similar to AspNetCore)
The precompiled templates can either be added to the output assembly as a Resource or as a separate file.
I know you aren't going to use this option
Copied to the output directory (as Content
), preserving folder structure.
so: Views\Shared\_LoginOrRegister.cshtml
gets copied to Release\net5.0\Views\Shared\_LoginOrRegister.cshtml
Other things worth mentioning...
The source directory can be configured in your .csproj
file, the default is Pages
and can be easily changed to Views
I will look at adding code to possibly scan Views
as a fall-back if Pages
doesn't exist or is empty.
The PartialViewsManager
I mentioned previously is not a requirement, it's optional and is intended for users that don't want to include precompiled templates or have .cshtml
in their output.
I am currently testing the following:
@(await Html.PartialAsync("PartialTemplate", new { Name = "Partial Template"}))
With this as my PartialTemplate.cshtml
@inherits RazorEngineCore.RazorEngineCorePageModel
Hello <b>@Model.Name</b>
If you are interested, I can push a development branch that you can pull and run some of your own tests against the SampleApp
included?
Thank you. I can try to use it in my application calling _LoginOrRegister and similar partial views. I want to allow users to create their own pages as razor views by using predefined partial views for logon, register, account and similar tasks.
@kobruleht
I am almost done with the package, hopefully I will get it out in the next few days (very busy at the moment), apologies for the wait.
Thank you. Great news. How are things going?
I will get around to uploading a pre-release to Nuget this evening. I'll also post the details of what's been done and what you can and can't do with the update. :)
@kobruleht
Apologies for the delay, I have been ill for a few days.
The package is uploading to NuGet, version 0.5.0-alpha.1
(please check that you include prerelease
versions in your NuGet search).
You can use partial views as you would expect in Asp.NetCore Razor pages
i.e
@(await Html.PartialAsync("PartialName", new { Name = "Something"}))
//OR
@(await Html.PartialAsync("PartialName")) // without specifying the Model (it will be inherited)
Names of the Partial Views are created from the name of the .cshtml
file.
i.e
Pages\Template.cshtml
becomes Template
@(await Html.PartialAsync("Template"))
Pages\Shared\_SharedTemplate.cshtml
becomes Shared/_SharedTemplate
@(await Html.PartialAsync("Shared/_SharedTemplate")
//OR
@(await Html.PartialAsync("_SharedTemplate") //if there's only a single `_SharedTemplate`
var resourceTemplate = await PrecompiledTemplate.LoadAsync("Template", Assembly.GetExecutingAssembly());
The default behaviour is to precompile and embed the templates in the output assembly, if this is undesirable you can disable this
<PropertyGroup>
<RazorEngineCoreExtEmbedResources>False</RazorEngineCoreExtEmbedResources>
<RazorEngineCoreExtPrecompile>False</RazorEngineCoreExtPrecompile>
</PropertyGroup>
RazorEngineCoreExtEmbedResources
is used to embed the files, set this to false
to copy the files to the output.
RazorEngineCoreExtPrecompile
is used for precompiling the .cshtml
files (into .rzhtml
), set this to false
to disable this feature.
The default location for scanning for .cshtml
files is Pages
(ala Razor pages), if this is undesirable, you can change the following
<PropertyGroup>
<RazorEngineCoreExtViewsDirectory>Views</RazorEngineCoreExtViewsDirectory>
</PropertyGroup>
You mentioned allow users to create their own pages as razor views by using predefined partial views for logon, register, account and similar tasks.
I have added logic to the Partial View lookup, if you precompile
and/or embed
templates into the output assembly and the file exists on the file system (at the original [relative] location), this file will be used rather than the precompiled
or embedded
ones (thus allowing you to override them).
Original Template file
= Pages\Login.cshtml
RazorEngineCoreExtPrecompile
= True
RazorEngineCoreExtEmbedResources
= True
This would embed the (pre)compiled template into the output assembly (as expected)
Creating (Adding) a file Pages\Login.cshtml
would override the (pre)compiled and embedded template.
Original Template file
= Pages\Login.cshtml
RazorEngineCoreExtPrecompile
= True
RazorEngineCoreExtEmbedResources
= False
This would copy (pre)compiled Pages\Login.rzhtml
to the output.
Creating (Adding) a file Pages\Login.cshtml
would override this (pre)compiled template.
There's still some things that need to be ironed out and optimized but hopefully it works as intended.
It causes build error
Error MSB4018 The "RazorEngineCoreCompiler" task failed unexpectedly.
System.IO.FileNotFoundException: Could not load file or assembly 'System.Runtime, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified.
File name: 'System.Runtime, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
at RazorEngineCoreCompiler.MSBuild.RazorEngineCoreCompiler.Execute()
at Microsoft.Build.BackEnd.TaskExecutionHost.Microsoft.Build.BackEnd.ITaskExecutionHost.Execute()
at Microsoft.Build.BackEnd.TaskBuilder.<ExecuteInstantiatedTask>d__26.MoveNext()
WRN: Assembly binding logging is turned OFF.
To enable assembly bind failure logging, set the registry value [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) to 1.
Note: There is some performance penalty associated with assembly bind failure logging.
To turn this feature off, remove the registry value [HKLM\Software\Microsoft\Fusion!EnableLog].
Eeva C:\Users\andrus\.nuget\packages\razorenginecore.extensions\0.5.0-alpha.1\build\net5.0\RazorEngineCore.Extensions.targets 31
Clean solution and rebuild all does not fix it. So I reverted back to previous version.
Hi @kobruleht,
There's a chance that I missed something, hence why there's a preview build.
Could you create a repo on GitHub and upload a sample project?
Also, what versions of dotnet do you have installed on your system?
'dotnet --list-sdks'
'dotnet --list-runtimes'
Partial view content from script is empty. I created testcase in https://github.com/kobruleht/RazorEngineCoreExtensionsTest
@kobruleht
Thanks for the sample code, it's a great help!
I can see a few issues and will get them fixed as soon as possible.
Thank you. Great. How are things going.
Hi @kobruleht
I need some changes pushed upstream to make things a little simpler, I will push a PR this week. :)
Thank you. I also tried
@model myModel
but got compile error
RazorEngineCompilationException: Unable to compile template: cjgmehoi.y0p(7,7): error CS0103: The name 'model' does not exist in the current context
It generates code
` Write(model);
WriteLiteral(" myModel\r\n\r\n<!DOCTYPE html>\r\n\r\n
\r\n <meta charset=\"utf-8\" />\r\n@wdcossey Any news ?
Using @Html.PartialAsync in template causes error.
Add partial support using code like