[asp.net-mvc]呈现在视图中包含 razor 代码的字符串

发布时间: 2017/3/25 0:12:26
注意事项: 本文中文内容可能为机器翻译,如要查看英文原文请点击上面连接.

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 和呈现它 (像这里︰呈现的视图中作为一个字符串) 但是我弄不明白我的头在它附近。

解决方法 1:

您将必须创建一个包含扩展方法的静态类。该方法必须返回一个实例MvcHtmlString包含安全呈现的 HTML 输出。话虽如此,到达 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 。这需要时间。大量的时间 !

这会保存您的模板字符串在某个地方,预编译它们,并调用那些做的唯一可行和有效方式编译模板时呼吁。这使它为你在追求什么,因为这将是完全用剃须刀什么 ASP.NET MVC 基本上无用擅长-保持视图在一个好地方,预编译它们,并呼吁他们引用时。更新︰ 好吧,也许大剂量的缓存会有帮助,但仍然不会实际上推荐此解决方案。

当生成代码,Razor发出呼吁 this.Writethis.WriteLiteral 。因为 this 是你自己写的它是由你来提供实现的 baseclass 从继承的对象 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 要求重新编译自动页面的内容发生更改时,您在这里可以做一些有趣的东西。

赞助商