tag net mvc for asp c# .net razor .net-core

c# - net - razor mvc



Uso de Razor fuera de MVC en.NET Core (5)

Me gustaría usar Razor como motor de plantillas en una aplicación de consola .NET que estoy escribiendo en .NET Core.

Los motores Razor independientes que he encontrado (RazorEngine, RazorTemplates) requieren todos .NET. Estoy buscando una solución que funcione con .NET Core.


Aquí hay un código de muestra que solo depende de Razor (para el análisis y la generación de código C #) y Roslyn (para la compilación de código C #, pero también puede usar el antiguo CodeDom).

No hay MVC en ese fragmento de código, por lo tanto, no hay vista, no hay archivos .cshtml, no hay controlador, solo análisis de fuente Razor y ejecución de tiempo de ejecución compilada. Sin embargo, todavía existe la noción de modelo.

Solo necesitará agregar los siguientes paquetes nuget: Microsoft.AspNetCore.Razor.Language (v2.1.1), Microsoft.AspNetCore.Razor.Runtime (v2.1.1) y Microsoft.CodeAnalysis.CSharp (v2.8.2) nugets.

Este código fuente de C # es compatible con NETCore, NETStandard 2 y .NET Framework. Para probarlo, simplemente cree un marco .NET o una aplicación de consola central .NET, péguelo y agregue los nugets.

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); 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"); // parse and generate C# code, outputs it on the console //var cs = te.GenerateCode(item); //Console.WriteLine(cs.GeneratedCode); var codeDocument = engine.Process(item); var cs = codeDocument.GetCSharpDocument(); // 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... } } }


Aquí hay una clase para que la respuesta de Nate funcione como un servicio con alcance en un proyecto ASP.NET Core 2.0.

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); } }

En Startup.cs

public void ConfigureServices(IServiceCollection services) { services.AddScoped<IViewRender, ViewRender>(); }

En un controlador

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(); } }


Hay un ejemplo de trabajo para .NET Core 1.0 en aspnet/Entropy/samples/Mvc.RenderViewToString . Como esto puede cambiar o desaparecer, detallaré el enfoque que estoy usando en mis propias aplicaciones aquí.

Tl; dr - ¡Razor funciona realmente bien fuera de MVC! Este enfoque puede manejar escenarios de representación más complejos como vistas parciales e inyectar objetos en las vistas también, aunque solo demostraré un ejemplo simple a continuación.

El servicio principal se ve así:

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()); } } }

Una aplicación de consola de prueba simple solo necesita inicializar el servicio (y algunos servicios de soporte) y llamarlo:

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>(); } } }

Esto supone que tiene una clase de modelo de vista:

EmailViewModel.cs

namespace RenderRazorToString { public class EmailViewModel { public string UserName { get; set; } public string SenderName { get; set; } } }

Y diseño y visualización de archivos:

Vistas / _Layout.cshtml

<!DOCTYPE html> <html> <body> <div> @RenderBody() </div> <footer> Thanks,<br /> @Model.SenderName </footer> </body> </html>

Views / EmailTemplate.cshtml

@model RenderRazorToString.EmailViewModel @{ Layout = "_EmailLayout"; } Hello @Model.UserName, <p> This is a generic email about something.<br /> <br /> </p>


Pasé varios días jugando con la luz de afeitar, pero tiene una serie de deficiencias, como no tener ayudantes html (@ Html. *) O ayudantes de URL, y otras peculiaridades.

Aquí hay una solución encapsulada para su uso fuera de una aplicación mvc. Requiere referencias de paquetes a aspnet core y mvc, pero son fáciles de agregar a un servicio o aplicación de consola. No se necesitan controladores ni servidores web. RenderToStringAsync es el método para llamar para representar una vista en una cadena.

La ventaja es que puede escribir sus vistas de la misma manera que lo haría en un proyecto web principal .net. Puede usar el mismo @Html y otras funciones y métodos auxiliares.

Puede reemplazar o agregar al proveedor de archivos físicos en la configuración de opciones de la vista de afeitar con su propio proveedor personalizado para cargar vistas desde la base de datos, llamadas al servicio web, etc. Probado con .net core 2.2 en Windows y Linux.

Tenga en cuenta que su archivo .csproj debe tener esto como la línea superior:

<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) { } } }


Recientemente he creado una biblioteca llamada RazorLight .

No tiene dependencias redundantes, como las partes ASP.NET MVC y se puede usar en aplicaciones de consola. Por ahora solo es compatible con .NET Core (NetStandard1.6), pero eso es exactamente lo que necesita.

Aquí hay un breve ejemplo:

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" });