Использование Razor вне MVC в .NET Core
Я хотел бы использовать Razor как механизм шаблонов в консольном приложении .NET, который я пишу в .NET Core.
Отдельные двигатели Razor, с которыми я столкнулся (RazorEngine, RazorTemplates), требуют полного .NET. Я ищу решение, которое работает с .NET Core.
Ответы
Ответ 1
Недавно я создал библиотеку под названием RazorLight.
Он не имеет избыточных зависимостей, таких как детали ASP.NET MVC и может использоваться в консольных приложениях. Пока он поддерживает только .NET Core (NetStandard1.6), но это именно то, что вам нужно.
Вот краткий пример:
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" });
Ответ 2
Там работает пример для.NET Core 1.0 в aspnet/Entropy/samples/Mvc.RenderViewToString. Поскольку это может измениться или исчезнуть, я подробно опишу подход, который я использую в своих приложениях.
Tl; dr - Razor отлично работает за пределами MVC! Этот подход может обрабатывать более сложные сценарии рендеринга, такие как частичные представления и впрыскивание объектов в представления, хотя я просто продемонстрирую простой пример ниже.
Основная услуга выглядит так:
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());
}
}
}
Простое тестовое консольное приложение просто должно инициализировать службу (и некоторые вспомогательные службы) и вызвать ее:
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>();
}
}
}
Это предполагает, что у вас есть класс модели представления:
EmailViewModel.cs
namespace RenderRazorToString
{
public class EmailViewModel
{
public string UserName { get; set; }
public string SenderName { get; set; }
}
}
И макет и файлы просмотра:
Просмотров/_Layout.cshtml
<!DOCTYPE html>
<html>
<body>
<div>
@RenderBody()
</div>
<footer>
Thanks,<br />
@Model.SenderName
</footer>
</body>
</html>
Просмотров /EmailTemplate.cshtml
@model RenderRazorToString.EmailViewModel
@{
Layout = "_EmailLayout";
}
Hello @Model.UserName,
<p>
This is a generic email about something.<br />
<br />
</p>
Ответ 3
Вот пример кода, который зависит только от Razor (для синтаксического анализа и генерации кода С#) и Roslyn (для компиляции кода С#, но вы также можете использовать старый CodeDom).
В этом фрагменте кода нет MVC, поэтому нет файлов View, no.cshtml, нет Controller, только разбор исходного кода Razor и скомпилированное исполнение во время выполнения. Тем не менее по-прежнему существует понятие модели.
Вам нужно будет добавить только следующие пакеты nuget: Microsoft.AspNetCore.Razor.Language
(v2.1.1), Microsoft.AspNetCore.Razor.Runtime
(v2.1.1) и Microsoft.CodeAnalysis.CSharp
(v2.8.2) nugets.
Этот исходный код С# совместим с NETCore, NETStandard 2 и.NET Framework. Чтобы протестировать его, просто создайте.NET-платформу или консольное приложение.NET, вставьте его и добавьте 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 not mandatory but I think it 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...
}
}
}
Ответ 4
Вот класс, чтобы получить ответ Nate, работающий как служба с областью в проекте 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);
}
}
В Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IViewRender, ViewRender>();
}
В контроллере
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();
}
}