在.NET Core中使用MVC之外的Razor

Chr*_*ans 54 .net c# razor .net-core

我想在我正在.NET Core中编写的.NET控制台应用程序中使用Razor作为模板引擎.

我遇到的独立Razor引擎(RazorEngine,RazorTemplates)都需要完整的.NET.我正在寻找适用于.NET Core的解决方案.

Tod*_*ams 35

最近我创建了一个名为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" }); 
Run Code Online (Sandbox Code Playgroud)

  • 更新:2.0版本缓存从字符串构建的模板 (5认同)
  • 这很容易实现,但它的性能非常糟糕.我创建了一个生成大约1000行html的循环.每次花费大约12秒.只需创建一个包含200行的单个页面大约需要1-2秒.在MVC项目中,1页花了大约20毫秒.因此,如果您不担心性能,这是一个可行的选择. (2认同)
  • @Toddams这将无法在生产中使用,因为视图是在发布时预编译的。您能否添加支持生产(预编译视图)和开发环境的mvc项目示例。我无法使其在两种环境下都能一起工作:(( (2认同)
  • 不幸的是,这已停止与.net5一起使用,特别是与Microsoft.Extensions.Primitives5.0.0一起使用。出现的错误是“无法加载类型”Microsoft.Extensions.Primitives.InplaceStringBuilder” (2认同)
  • 恕我直言,这不应该是公认的答案。Razor 是一种产品 Razor 标记语言是 Razor 引擎使用的标记语言,Razor 引擎是 Razor 产品的一部分。因此,如果有人问如何使用 Razor ML,这可能是答案,但 Chrostof 询问如何使用 Razor,那是 MS 产品。这里的答案没有解释,只是告诉如何使用声称支持 Razor ML 的定制产品有 Razor 做的(但不保证这一点,也不保证对未来的支持)。 (2认同)

ADO*_*ion 27

对于 2021 年以上的任何人:我已经开始了https://github.com/adoconnection/RazorEngineCore

它具有最新的ASP.NET Core 5 Razor及其语法功能。

用法与 RazorEngine 完全相同:

RazorEngine razorEngine = new RazorEngine();
RazorEngineCompiledTemplate template = razorEngine.Compile("Hello @Model.Name");

string result = template.Run(new
{
    Name = "Alex"
});

Console.WriteLine(result);
Run Code Online (Sandbox Code Playgroud)

快速保存和加载

// save to file
template.SaveToFile("myTemplate.dll");

//save to stream
MemoryStream memoryStream = new MemoryStream();
template.SaveToStream(memoryStream);
Run Code Online (Sandbox Code Playgroud)
var template1 = RazorEngineCompiledTemplate.LoadFromFile("myTemplate.dll");
var template2 = RazorEngineCompiledTemplate.LoadFromStream(myStream);
Run Code Online (Sandbox Code Playgroud)

  • 它从来没有打算在 NET Framework 中工作,有一个众所周知的 https://github.com/Antaris/RazorEngine 包 (3认同)
  • @thalacker v2020.9.1 获得 NET 4.7.2 支持 (2认同)

Sim*_*ier 26

下面是一个示例代码,它仅依赖于Razor(用于解析和C#代码生成)和Roslyn(用于C#代码编译,但您也可以使用旧的CodeDom).

在那段代码中没有MVC,因此,没有View,没有.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.

此C#源代码与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'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...
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 做得好,谢谢!为了使其在Mac和Linux上的netcore2应用程序中运行的netstandard 2.0类库中正常工作,我必须添加对netstandard dll的附加引用:`MetadataReference.CreateFromFile(Path.Combine(Path.GetDirectoryName(typeof(object).Assembly。位置),“ netstandard.dll”)),` (2认同)
  • @Isantipov-好的,感谢您指出,我没有在Windows以外的其他平台上对此进行测试。我已经更新了答案。 (2认同)
  • 好吧,如果我记得,实际的RazorViewEngine是在MVC中的,所以我想这使Razor不过是我猜想的解析器和编译器。;) (2认同)

Nat*_*ini 17

aspnet/Entropy/samples/Mvc.RenderViewToString上有.NET Core 1.0的工作示例.由于这可能会改变或消失,我将详细介绍我在自己的应用程序中使用的方法.

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());
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

一个简单的测试控制台应用程序只需要初始化服务(和一些支持服务),并调用它:

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>();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这假设您有一个视图模型类:

EmailViewModel.cs

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

        public string SenderName { get; set; }
    }
}
Run Code Online (Sandbox Code Playgroud)

以及布局和视图文件:

查看/ _Layout.cshtml

<!DOCTYPE html>

<html>
<body>
    <div>
        @RenderBody()
    </div>
    <footer>
Thanks,<br />
@Model.SenderName
    </footer>
</body>
</html>
Run Code Online (Sandbox Code Playgroud)

查看/ EmailTemplate.cshtml

@model RenderRazorToString.EmailViewModel
@{ 
    Layout = "_EmailLayout";
}

Hello @Model.UserName,

<p>
    This is a generic email about something.<br />
    <br />
</p>
Run Code Online (Sandbox Code Playgroud)

  • 请注意,要与 ASP.NET Core 3 一起使用,您需要将“IWebHostEnvironment”实例添加到 DI 容器,并使用“MvcRazorRuntimeCompilationOptions”类来提供文件提供程序,而不是“RazorViewEngineOptions”类。此处提供了详细说明“RazorViewToStringRenderer”实例化的示例:http://corstianboerman.com/2019-12-25/using-the-razorviewtostringrenderer-with-asp-net-core-3.html。 (4认同)
  • @dustinmoris如果我没记错的话,它会为你做一些缓存.我有一段时间没试过. (2认同)
  • 真棒!但是,这在.net core 2.0中不起作用:(似乎无法加载依赖项:`类型'Attribute'在未引用的程序集中定义.您必须添加对程序集'netstandard的引用,Version = 2.0.0.0,Culture = neutral,PublicKeyToken = cc7b13ffcd2ddd51'` - 我不知道如何告诉razor加载它需要的所有依赖项 - 任何想法? (2认同)
  • 对于任何人来说,'类型'属性'是在未引用错误的程序集中定义的,添加`<PreserveCompilationContext> true </ PreserveCompilationContext>`解决了问题. (2认同)

Arc*_*ade 6

这是一个让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);
    }
}
Run Code Online (Sandbox Code Playgroud)

在Startup.cs中

public void ConfigureServices(IServiceCollection services)
{
     services.AddScoped<IViewRender, ViewRender>();
}
Run Code Online (Sandbox Code Playgroud)

在控制器中

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();
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 5

如果你在 2022 年,有一个易于使用的库,名为Razor.Templating.Core.

  • 它开箱即用,适用于MVC、API、控制台和许多其他类型的应用程序。
  • 支持.NET Core 3.1、.NET 5、.NET 6
  • 支持大多数 Razor 功能,例如ViewModel、ViewBag、ViewData、TagHelpers、Partial Views、ViewComponents 等
  • 支持单个文件发布、ReadyToRun

用法更简单:

var htmlString = await RazorTemplateEngine.RenderAsync("/Views/ExampleView.cshtml", model, viewData);
Run Code Online (Sandbox Code Playgroud)

请参阅此处的文档

PS:我是这个库的作者。