Ich möchte Razor als Vorlagen-Engine in einer .NET-Konsolenanwendung verwenden, die ich in .NET Core schreibe.
Die eigenständigen Razor-Engines (RazorEngine, RazorTemplates) erfordern alle .NET. Ich suche nach einer Lösung, die mit .NET Core funktioniert.
Antworten:
Kürzlich habe ich eine Bibliothek namens RazorLight erstellt .
Es hat keine redundanten Abhängigkeiten wie ASP.NET MVC-Teile und kann in Konsolenanwendungen verwendet werden. Im Moment unterstützt es nur .NET Core (NetStandard1.6) - aber genau das brauchen Sie.
Hier ist ein kurzes Beispiel:
IRazorLightEngine engine = EngineFactory.CreatePhysical("Path-to-your-views"); // Files and strong models string resultFromFile = engine.Parse("Test.cshtml", new Model("SomeData")); // Strings and anonymous models string stringResult = engine.ParseString("Hello @Model.Name", new { Name = "John" });
quelle
dotnet publish
die App erstellen.Hier ist ein Beispielcode, der nur von Razor (zum Parsen und Generieren von C # -Code) und Roslyn (zum Kompilieren von C # -Code, aber Sie können auch das alte CodeDom verwenden) abhängt.
In diesem Code befindet sich keine MVC, also keine Ansicht, keine CSHTM-Dateien, kein Controller, nur das Parsen von Razor-Quellen und die kompilierte Laufzeitausführung. Es gibt jedoch immer noch den Begriff Modell.
Sie müssen nur die folgenden Nuget-Pakete hinzufügen:
Microsoft.AspNetCore.Razor.Language
(getestet mit v3.1.7),Microsoft.AspNetCore.Razor.Runtime
(getestet mit v2.2.0) undMicrosoft.CodeAnalysis.CSharp
(getestet mit v3.7.0) Nugets.Dieser C # -Quellcode ist kompatibel mit NETCore 3.1 (für ältere Versionen überprüfen Sie den Verlauf dieser Antwort), NETStandard 2 und .NET Framework. Um es zu testen, erstellen Sie einfach ein .NET Framework oder eine .NET Core Console-App, fügen Sie es ein, fügen Sie die Nugets hinzu und erstellen Sie die Datei hello.txt von Hand.
using System; using System.IO; using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Hosting; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; namespace RazorTemplate { class Program { static void Main(string[] args) { // points to the local path var fs = RazorProjectFileSystem.Create("."); // customize the default engine a little bit var engine = RazorProjectEngine.Create(RazorConfiguration.Default, fs, (builder) => { // InheritsDirective.Register(builder); // in .NET core 3.1, compatibility has been broken (again), and this is not needed anymore... builder.SetNamespace("MyNamespace"); // define a namespace for the Template class }); // get a razor-templated file. My "hello.txt" template file is defined like this: // // @inherits RazorTemplate.MyTemplate // Hello @Model.Name, welcome to Razor World! // var item = fs.GetItem("hello.txt", null); // parse and generate C# code var codeDocument = engine.Process(item); var cs = codeDocument.GetCSharpDocument(); // outputs it on the console //Console.WriteLine(cs.GeneratedCode); // now, use roslyn, parse the C# code var tree = CSharpSyntaxTree.ParseText(cs.GeneratedCode); // define the dll const string dllName = "hello"; var compilation = CSharpCompilation.Create(dllName, new[] { tree }, new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location), // include corlib MetadataReference.CreateFromFile(typeof(RazorCompiledItemAttribute).Assembly.Location), // include Microsoft.AspNetCore.Razor.Runtime MetadataReference.CreateFromFile(Assembly.GetExecutingAssembly().Location), // this file (that contains the MyTemplate base class) // for some reason on .NET core, I need to add this... this is not needed with .NET framework MetadataReference.CreateFromFile(Path.Combine(Path.GetDirectoryName(typeof(object).Assembly.Location), "System.Runtime.dll")), // as found out by @Isantipov, for some other reason on .NET Core for Mac and Linux, we need to add this... this is not needed with .NET framework MetadataReference.CreateFromFile(Path.Combine(Path.GetDirectoryName(typeof(object).Assembly.Location), "netstandard.dll")) }, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); // we want a dll // compile the dll string path = Path.Combine(Path.GetFullPath("."), dllName + ".dll"); var result = compilation.Emit(path); if (!result.Success) { Console.WriteLine(string.Join(Environment.NewLine, result.Diagnostics)); return; } // load the built dll Console.WriteLine(path); var asm = Assembly.LoadFile(path); // the generated type is defined in our custom namespace, as we asked. "Template" is the type name that razor uses by default. var template = (MyTemplate)Activator.CreateInstance(asm.GetType("MyNamespace.Template")); // run the code. // should display "Hello Killroy, welcome to Razor World!" template.ExecuteAsync().Wait(); } } // the model class. this is 100% specific to your context public class MyModel { // this will map to @Model.Name public string Name => "Killroy"; } // the sample base template class. It's not mandatory but I think it's much easier. public abstract class MyTemplate { // this will map to @Model (property name) public MyModel Model => new MyModel(); public void WriteLiteral(string literal) { // replace that by a text writer for example Console.Write(literal); } public void Write(object obj) { // replace that by a text writer for example Console.Write(obj); } public async virtual Task ExecuteAsync() { await Task.Yield(); // whatever, we just need something that compiles... } } }
quelle
MetadataReference.CreateFromFile(Path.Combine(Path.GetDirectoryName(typeof(object).Assembly.Location),"netstandard.dll")),
Für alle ab 2020 hier: Ich habe https://github.com/adoconnection/RazorEngineCore gestartet
Es verfügt über den neuesten ASP.NET Core 3.1.1 Razor und seine Syntaxfunktionen.
Die Verwendung entspricht der von RazorEngine:
RazorEngine razorEngine = new RazorEngine(); RazorEngineCompiledTemplate template = razorEngine.Compile("Hello @Model.Name"); string result = template.Run(new { Name = "Alex" }); Console.WriteLine(result);
Schnelles Speichern und Laden
// save to file template.SaveToFile("myTemplate.dll"); //save to stream MemoryStream memoryStream = new MemoryStream(); template.SaveToStream(memoryStream);
var template1 = RazorEngineCompiledTemplate.LoadFromFile("myTemplate.dll"); var template2 = RazorEngineCompiledTemplate.LoadFromStream(myStream);
quelle
System.IO.FileNotFoundException: Could not load file or assembly 'Microsoft.CSharp' or one of its dependencies. The system cannot find the file specified.
eine Chance , dieses Arbeits für .NET Framework?PreserveCompilationContext
in den Projekteinstellungen? Ich würde es vorziehen, wenn möglich zu vermeiden.Es gibt ein funktionierendes Beispiel für .NET Core 1.0 unter aspnet / Entropy / samples / Mvc.RenderViewToString . Da sich dies ändern oder verschwinden könnte, werde ich hier den Ansatz, den ich in meinen eigenen Anwendungen verwende, detailliert beschreiben.
Tl; dr - Razor funktioniert wirklich gut außerhalb von MVC! Dieser Ansatz kann auch komplexere Rendering-Szenarien wie Teilansichten und das Einfügen von Objekten in Ansichten verarbeiten, obwohl ich im Folgenden nur ein einfaches Beispiel zeigen werde.
Der Kerndienst sieht folgendermaßen aus:
RazorViewToStringRenderer.cs
using System; using System.IO; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Routing; namespace RenderRazorToString { public class RazorViewToStringRenderer { private readonly IRazorViewEngine _viewEngine; private readonly ITempDataProvider _tempDataProvider; private readonly IServiceProvider _serviceProvider; public RazorViewToStringRenderer( IRazorViewEngine viewEngine, ITempDataProvider tempDataProvider, IServiceProvider serviceProvider) { _viewEngine = viewEngine; _tempDataProvider = tempDataProvider; _serviceProvider = serviceProvider; } public async Task<string> RenderViewToString<TModel>(string name, TModel model) { var actionContext = GetActionContext(); var viewEngineResult = _viewEngine.FindView(actionContext, name, false); if (!viewEngineResult.Success) { throw new InvalidOperationException(string.Format("Couldn't find view '{0}'", name)); } var view = viewEngineResult.View; using (var output = new StringWriter()) { var viewContext = new ViewContext( actionContext, view, new ViewDataDictionary<TModel>( metadataProvider: new EmptyModelMetadataProvider(), modelState: new ModelStateDictionary()) { Model = model }, new TempDataDictionary( actionContext.HttpContext, _tempDataProvider), output, new HtmlHelperOptions()); await view.RenderAsync(viewContext); return output.ToString(); } } private ActionContext GetActionContext() { var httpContext = new DefaultHttpContext { RequestServices = _serviceProvider }; return new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); } } }
Eine einfache Testkonsolen-App muss lediglich den Dienst (und einige unterstützende Dienste) initialisieren und aufrufen:
Program.cs
using System; using System.Diagnostics; using System.IO; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Internal; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.ObjectPool; using Microsoft.Extensions.PlatformAbstractions; namespace RenderRazorToString { public class Program { public static void Main() { // Initialize the necessary services var services = new ServiceCollection(); ConfigureDefaultServices(services); var provider = services.BuildServiceProvider(); var renderer = provider.GetRequiredService<RazorViewToStringRenderer>(); // Build a model and render a view var model = new EmailViewModel { UserName = "User", SenderName = "Sender" }; var emailContent = renderer.RenderViewToString("EmailTemplate", model).GetAwaiter().GetResult(); Console.WriteLine(emailContent); Console.ReadLine(); } private static void ConfigureDefaultServices(IServiceCollection services) { var applicationEnvironment = PlatformServices.Default.Application; services.AddSingleton(applicationEnvironment); var appDirectory = Directory.GetCurrentDirectory(); var environment = new HostingEnvironment { WebRootFileProvider = new PhysicalFileProvider(appDirectory), ApplicationName = "RenderRazorToString" }; services.AddSingleton<IHostingEnvironment>(environment); services.Configure<RazorViewEngineOptions>(options => { options.FileProviders.Clear(); options.FileProviders.Add(new PhysicalFileProvider(appDirectory)); }); services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>(); var diagnosticSource = new DiagnosticListener("Microsoft.AspNetCore"); services.AddSingleton<DiagnosticSource>(diagnosticSource); services.AddLogging(); services.AddMvc(); services.AddSingleton<RazorViewToStringRenderer>(); } } }
Dies setzt voraus, dass Sie eine Ansichtsmodellklasse haben:
EmailViewModel.cs
namespace RenderRazorToString { public class EmailViewModel { public string UserName { get; set; } public string SenderName { get; set; } } }
Und Layout- und Ansichtsdateien:
Ansichten / _Layout.cshtml
Ansichten / EmailTemplate.cshtml
@model RenderRazorToString.EmailViewModel @{ Layout = "_EmailLayout"; } Hello @Model.UserName, <p> This is a generic email about something.<br /> <br /> </p>
quelle
The type 'Attribute' is defined in an assembly that is not referenced. You must add a reference to assembly 'netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'
- Ich bin nicht sicher, wie ich Rasierer anweisen soll, alle benötigten Abhängigkeiten zu laden - irgendwelche Ideen?The type 'Attribute' is defined in an assembly that is not referenced
Fehler erhalten, wurde<PreserveCompilationContext>true</PreserveCompilationContext>
das Problem durch Hinzufügen behoben.IWebHostEnvironment
dem DI-Container eine Instanz hinzufügen und dieMvcRazorRuntimeCompilationOptions
Klasse verwenden müssen, um Dateianbieter anstelle derRazorViewEngineOptions
Klasse bereitzustellen . Ein Beispiel für eine detaillierteRazorViewToStringRenderer
Instanziierung finden Sie hier: corstianboerman.com/2019-12-25/… .Hier ist eine Klasse, mit der Nates Antwort als Scoped Service in einem ASP.NET Core 2.0-Projekt funktioniert.
using System; using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Routing; namespace YourNamespace.Services { public class ViewRender : IViewRender { private readonly IRazorViewEngine _viewEngine; private readonly ITempDataProvider _tempDataProvider; private readonly IServiceProvider _serviceProvider; public ViewRender( IRazorViewEngine viewEngine, ITempDataProvider tempDataProvider, IServiceProvider serviceProvider) { _viewEngine = viewEngine; _tempDataProvider = tempDataProvider; _serviceProvider = serviceProvider; } public async Task<string> RenderAsync(string name) { return await RenderAsync<object>(name, null); } public async Task<string> RenderAsync<TModel>(string name, TModel model) { var actionContext = GetActionContext(); var viewEngineResult = _viewEngine.FindView(actionContext, name, false); if (!viewEngineResult.Success) { throw new InvalidOperationException(string.Format("Couldn't find view '{0}'", name)); } var view = viewEngineResult.View; using (var output = new StringWriter()) { var viewContext = new ViewContext( actionContext, view, new ViewDataDictionary<TModel>( metadataProvider: new EmptyModelMetadataProvider(), modelState: new ModelStateDictionary()) { Model = model }, new TempDataDictionary( actionContext.HttpContext, _tempDataProvider), output, new HtmlHelperOptions()); await view.RenderAsync(viewContext); return output.ToString(); } } private ActionContext GetActionContext() { var httpContext = new DefaultHttpContext {RequestServices = _serviceProvider}; return new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); } } public interface IViewRender { Task<string> RenderAsync(string name); Task<string> RenderAsync<TModel>(string name, TModel model); } }
In Startup.cs
public void ConfigureServices(IServiceCollection services) { services.AddScoped<IViewRender, ViewRender>(); }
In einer Steuerung
public class VenuesController : Controller { private readonly IViewRender _viewRender; public VenuesController(IViewRender viewRender) { _viewRender = viewRender; } public async Task<IActionResult> Edit() { string html = await _viewRender.RenderAsync("Emails/VenuePublished", venue.Name); return Ok(); } }
quelle
Ich habe mehrere Tage damit verbracht, mit Rasiermesserlicht herumzuspielen, aber es hat eine Reihe von Mängeln, wie zum Beispiel keine HTML-Helfer (@ Html. *) Oder URL-Helfer und andere Macken.
Hier ist eine Lösung, die für die Verwendung außerhalb einer MVC-App gekapselt ist. Es sind zwar Paketverweise auf Aspnet Core und MVC erforderlich, diese können jedoch problemlos zu einer Dienst- oder Konsolenanwendung hinzugefügt werden. Es werden keine Controller oder Webserver benötigt. RenderToStringAsync ist die aufzurufende Methode zum Rendern einer Ansicht für eine Zeichenfolge.
Der Vorteil ist, dass Sie Ihre Ansichten genauso schreiben können wie in einem .net Core-Webprojekt. Sie können dieselbe @ HTML- und andere Hilfsfunktionen und -methoden verwenden.
Sie können den physischen Dateianbieter in den Optionen für die Optionen für die Rasiermesseransicht durch einen eigenen benutzerdefinierten Anbieter ersetzen oder hinzufügen, um Ansichten aus Datenbanken, Webdienstaufrufen usw. zu laden. Getestet mit .net Core 2.2 unter Windows und Linux.
Bitte beachten Sie, dass Ihre .csproj-Datei dies als oberste Zeile haben muss:
<Project Sdk="Microsoft.NET.Sdk.Web">
using System; using System.Collections.Generic; using System.Diagnostics; using System.Dynamic; using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Internal; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging; using Microsoft.Extensions.ObjectPool; namespace RazorRendererNamespace { /// <summary> /// Renders razor pages with the absolute minimum setup of MVC, easy to use in console application, does not require any other classes or setup. /// </summary> public class RazorRenderer : ILoggerFactory, ILogger { private class ViewRenderService : IDisposable, ITempDataProvider, IServiceProvider { private static readonly System.Net.IPAddress localIPAddress = System.Net.IPAddress.Parse("127.0.0.1"); private readonly Dictionary<string, object> tempData = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); private readonly IRazorViewEngine _viewEngine; private readonly ITempDataProvider _tempDataProvider; private readonly IServiceProvider _serviceProvider; private readonly IHttpContextAccessor _httpContextAccessor; public ViewRenderService(IRazorViewEngine viewEngine, IHttpContextAccessor httpContextAccessor, ITempDataProvider tempDataProvider, IServiceProvider serviceProvider) { _viewEngine = viewEngine; _httpContextAccessor = httpContextAccessor; _tempDataProvider = tempDataProvider ?? this; _serviceProvider = serviceProvider ?? this; } public void Dispose() { } public async Task<string> RenderToStringAsync<TModel>(string viewName, TModel model, ExpandoObject viewBag = null, bool isMainPage = false) { HttpContext httpContext; if (_httpContextAccessor?.HttpContext != null) { httpContext = _httpContextAccessor.HttpContext; } else { DefaultHttpContext defaultContext = new DefaultHttpContext { RequestServices = _serviceProvider }; defaultContext.Connection.RemoteIpAddress = localIPAddress; httpContext = defaultContext; } var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); using (var sw = new StringWriter()) { var viewResult = _viewEngine.FindView(actionContext, viewName, isMainPage); if (viewResult.View == null) { viewResult = _viewEngine.GetView("~/", viewName, isMainPage); } if (viewResult.View == null) { return null; } var viewDictionary = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()) { Model = model }; if (viewBag != null) { foreach (KeyValuePair<string, object> kv in (viewBag as IDictionary<string, object>)) { viewDictionary.Add(kv.Key, kv.Value); } } var viewContext = new ViewContext( actionContext, viewResult.View, viewDictionary, new TempDataDictionary(actionContext.HttpContext, _tempDataProvider), sw, new HtmlHelperOptions() ); await viewResult.View.RenderAsync(viewContext); return sw.ToString(); } } object IServiceProvider.GetService(Type serviceType) { return null; } IDictionary<string, object> ITempDataProvider.LoadTempData(HttpContext context) { return tempData; } void ITempDataProvider.SaveTempData(HttpContext context, IDictionary<string, object> values) { } } private readonly string rootPath; private readonly ServiceCollection services; private readonly ServiceProvider serviceProvider; private readonly ViewRenderService viewRenderer; public RazorRenderer(string rootPath) { this.rootPath = rootPath; services = new ServiceCollection(); ConfigureDefaultServices(services); serviceProvider = services.BuildServiceProvider(); viewRenderer = new ViewRenderService(serviceProvider.GetRequiredService<IRazorViewEngine>(), null, null, serviceProvider); } private void ConfigureDefaultServices(IServiceCollection services) { var environment = new HostingEnvironment { WebRootFileProvider = new PhysicalFileProvider(rootPath), ApplicationName = typeof(RazorRenderer).Assembly.GetName().Name, ContentRootPath = rootPath, WebRootPath = rootPath, EnvironmentName = "DEVELOPMENT", ContentRootFileProvider = new PhysicalFileProvider(rootPath) }; services.AddSingleton<IHostingEnvironment>(environment); services.Configure<RazorViewEngineOptions>(options => { options.FileProviders.Clear(); options.FileProviders.Add(new PhysicalFileProvider(rootPath)); }); services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>(); services.AddSingleton<ILoggerFactory>(this); var diagnosticSource = new DiagnosticListener(environment.ApplicationName); services.AddSingleton<DiagnosticSource>(diagnosticSource); services.AddMvc(); } public void Dispose() { } public Task<string> RenderToStringAsync<TModel>(string viewName, TModel model, ExpandoObject viewBag = null, bool isMainPage = false) { return viewRenderer.RenderToStringAsync(viewName, model, viewBag, isMainPage); } void ILoggerFactory.AddProvider(ILoggerProvider provider) { } IDisposable ILogger.BeginScope<TState>(TState state) { throw new NotImplementedException(); } ILogger ILoggerFactory.CreateLogger(string categoryName) { return this; } bool ILogger.IsEnabled(Microsoft.Extensions.Logging.LogLevel logLevel) { return false; } void ILogger.Log<TState>(Microsoft.Extensions.Logging.LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) { } } }
quelle
Microsoft.NET.Sdk.Web
bedeutet, dass das Projekt zur Ausführung ASP.NET Core-Laufzeit benötigt. Dies kann sehr schwer und für Konsolen- / Desktop-Apps unnötig sein.