【问题标题】:Render string containing razor-code in view在视图中呈现包含剃刀代码的字符串
【发布时间】:2016-06-25 03:13:51
【问题描述】:

在这里考虑 CMS 用例。想象一下这样的视图:

// /Home/Index.cshtml
@model object
@{
  var str = "My <b>CMS</b> content with razor code: @Html.ActionLink(\"Click\", \"Home\")"
}
@Html.MyCustomRazorStringRenderer(Model)

预期输出:

My <b>CMS</b> content with razor code: <a href="/Home/Click">Click</a>

MyCustomRazorStringRenderer 是什么样的?它必须以某种方式做某事。喜欢创建/使用 ViewContext 并渲染它(比如这里:Render a view as a string),但我无法完全理解它。

【问题讨论】:

  • 为什么不能在控制器中渲染并传递给视图?
  • @Html.Raw(str) 做你期望的工作。
  • 不,@Html.Raw() 只输出字符串而不对其进行编码。他想要的是让 Razor 实际处理字符串中的 Razor 标签。

标签: c# asp.net .net asp.net-mvc razor


【解决方案1】:

您必须创建一个包含extension method 的静态类。该方法必须返回包含安全呈现的 HTML 输出的 MvcHtmlString 实例。话虽如此,到达renderedOutput 正确地意味着“劫持” Razor 渲染器,这很棘手。

您真正要做的是在其预期环境之外使用 Razor 引擎,如下所述:http://vibrantcode.com/blog/2010/7/22/using-the-razor-parser-outside-of-aspnet.html

这里也有很多不错的资料,从中我得到了很多灵感,下面的代码:http://www.codemag.com/article/1103081

这些类是此的起点:RazorEngineHostRazorTemplateEngineCSharpCodeProviderHtmlHelper

工作代码

我实际上得到了一个几乎可以工作的版本,但意识到这是一件非常徒劳的事情。 Razor 引擎通过生成代码来工作,然后必须使用CSharpCodeProvider 进行编译。这需要时间。很多时间!

唯一可行且有效的方法是将模板字符串保存在某处,预编译它们,并在调用时调用这些已编译的模板。这使得它对你所追求的基本无用,因为这正是带有 Razor 的 ASP.NET MVC 擅长的 - 将视图保持在一个好的位置,预编译它们,并在引用时调用它们。 更新:好吧,也许大量缓存可能会有所帮助,但我仍然不会真正推荐这种解决方案。

生成代码时,Razor 会发出对this.Writethis.WriteLiteral 的调用。因为this 是继承自您自己编写的基类的对象,所以由您提供WriteWriteLiteral 的实现。

如果您在模板字符串中使用任何其他 HtmlHelper 扩展名,则需要为所有这些包括程序集引用和命名空间导入。下面的代码添加了最常见的代码。由于匿名类型的性质,它们不能用于模型类。

MyRazorExtensions 类

public static class MyRazorExtensions
{
    public static MvcHtmlString RazorEncode(this HtmlHelper helper, string template)
    {
        return RazorEncode(helper, template, (object)null);
    }

    public static MvcHtmlString RazorEncode<TModel>(this HtmlHelper helper, string template, TModel model)
    {
        string output = Render(helper, template, model);
        return new MvcHtmlString(output);
    }

    private static string Render<TModel>(HtmlHelper helper, string template, TModel model)
    {
        // 1. Create a host for the razor engine
        //    TModel CANNOT be an anonymous class!
        var host = new RazorEngineHost(RazorCodeLanguage.GetLanguageByExtension("cshtml");
        host.DefaultNamespace = typeof(MyTemplateBase<TModel>).Namespace;
        host.DefaultBaseClass = nameof(MyTemplateBase<TModel>) + "<" + typeof(TModel).FullName + ">";
        host.NamespaceImports.Add("System.Web.Mvc.Html");

        // 2. Create an instance of the razor engine
        var engine = new RazorTemplateEngine(host);

        // 3. Parse the template into a CodeCompileUnit
        using (var reader = new StringReader(template))
        {
            razorResult = engine.GenerateCode(reader);
        }
        if (razorResult.ParserErrors.Count > 0)
        {
            throw new InvalidOperationException($"{razorResult.ParserErrors.Count} errors when parsing template string!");
        }

        // 4. Compile the produced code into an assembly
        var codeProvider = new CSharpCodeProvider();
        var compilerParameters = new CompilerParameters { GenerateInMemory = true };
        compilerParameters.ReferencedAssemblies.Add(typeof(MyTemplateBase<TModel>).Assembly.Location);
        compilerParameters.ReferencedAssemblies.Add(typeof(TModel).Assembly.Location);
        compilerParameters.ReferencedAssemblies.Add(typeof(HtmlHelper).Assembly.Location);
        var compilerResult = codeProvider.CompileAssemblyFromDom(compilerParameters, razorResult.GeneratedCode);
        if (compilerResult.Errors.HasErrors)
        {
            throw new InvalidOperationException($"{compilerResult.Errors.Count} errors when compiling template string!");
        }

        // 5. Create an instance of the compiled class and run it
        var templateType = compilerResult.CompiledAssembly.GetType($"{host.DefaultNamespace}.{host.DefaultClassName}");
        var templateImplementation = Activator.CreateInstance(templateType) as MyTemplateBase<TModel>;
        templateImplementation.Model = model;
        templateImplementation.Html = helper;
        templateImplementation.Execute();

        // 6. Return the html output
        return templateImplementation.Output.ToString();
    }
}

MyTemplateBase 类

public abstract class MyTemplateBase<TModel>
{
    public TModel Model { get; set; }
    public HtmlHelper Html { get; set; }

    public void WriteLiteral(object output)
    {
        Output.Append(output.ToString());
    }

    public void Write(object output)
    {
        Output.Append(Html.Encode(output.ToString()));
    }

    public void Write(MvcHtmlString output)
    {
        Output.Append(output.ToString());
    }

    public abstract void Execute();

    public StringBuilder Output { get; private set; } = new StringBuilder();
}

test.cshtml

@using WebApplication1.Models

<h2>Test</h2>

@Html.RazorEncode("<p>Paragraph output</p>")
@Html.RazorEncode("<p>Using a @Model</p>", "string model" )
@Html.RazorEncode("@for (int i = 0; i < 100; ++i) { <p>@i</p> }")
@Html.RazorEncode("@Html.ActionLink(Model.Text, Model.Action)", new TestModel { Text = "Foo", Action = "Bar" })

更新

执行此“实时”操作 - 如果您不进行大量缓存,则为每个页面加载编译和运行 Razor 显然太慢,但是如果您分解我的代码片段并让您的 CMS 自动请求重新编译每当页面内容发生变化时,您都可以在这里做一些非常有趣的事情。

【讨论】:

  • 哇,这真是令人印象深刻!!!我愿意承担一些缺点(使用输出缓存),因为读写比率非常低。
  • 我希望这对你有用。请记住,让它与模型的匿名类型一起工作真的很棘手。
  • 是的,非常感谢您的帮助!我现在正在尝试,会让你知道它是如何为我工作的:)。在我的情况下,匿名模型不会发生(模型始终是 类型)
  • 那么您可以通过将CmsPage 硬编码为模型类型来简化这些类并摆脱泛型,如果您敢的话... :)
  • 我删除了标签结构中的 RazorTags,它在生产环境中运行了几天......到目前为止,解决方案没有问题:)
【解决方案2】:

对于 2021 年,如果有人使用 .Net Core 寻找答案,您可以使用 nuget 的 RazorEngine.NetCore。

https://khalidabuhakmeh.com/generate-outputs-with-razor-engine-in-dotnet-core

 string template = "Hello @Model.Name!";
        var result = Engine
            .Razor
            .RunCompile(template,
                "templateKey",
                null,
                new
                {
                    Name = "CSharp"
                });

        return View("ViewName", result);

输出: 你好 CSharp!

【讨论】:

    猜你喜欢
    • 2019-12-09
    • 2021-07-28
    • 1970-01-01
    • 2022-01-18
    • 2012-04-13
    • 1970-01-01
    • 2015-08-20
    • 2019-02-17
    • 1970-01-01
    相关资源
    最近更新 更多