在视图中呈现包含剃刀代码的字符串 [英] Render string containing razor-code in view

查看:76
本文介绍了在视图中呈现包含剃刀代码的字符串的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

此处有关CMS用例的思考。想象这样的视图:

Thinking of CMS use cases here. Imagine a view like this:

// /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并进行渲染(如此处:将视图渲染为字符串),但我不太了解。

What does MyCustomRazorStringRenderer look like? It must somehow do sth. like creating/using the ViewContext and render it (like here: Render a view as a string) but I can't quite get my head around it.

推荐答案

您将必须创建一个包含以下内容的静态类扩展方法。该方法必须返回 MvcHtmlString ,其中包含安全呈现的HTML输出。话虽如此,到达 renderedOutput 正确地意味着劫持了Razor渲染器,这很棘手。

You will have to create a static class containing an extension method. The method must return an instance of MvcHtmlString that contains the safe rendered HTML output. Having said this, getting to the renderedOutput properly means "hijacking" the Razor renderer, which is tricky.

什么您真正在做的是在其预期环境之外使用Razor引擎,该引擎在此处进行了描述: http://vibrantcode.com/blog/2010/7/22/using-the-razor-parser-outside-of-aspnet.html

What you're really doing is using the Razor engine outside of its intended environment, which is described here: http://vibrantcode.com/blog/2010/7/22/using-the-razor-parser-outside-of-aspnet.html

这里还有很多很好的信息,从中我可以从下面的代码中学到很多灵感: http://www.codemag.com/article/1103081

There is also a lot of good information here, from which I got a lot of inspiration for the code below: http://www.codemag.com/article/1103081

这些类是起点为此: RazorEngineHost RazorTemplateEngine CSharpCodeProvider HtmlHelper

These classes are the starting point for this: RazorEngineHost, RazorTemplateEngine, CSharpCodeProvider, HtmlHelper.

工作代码

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

I actually got an almost working version of this, but realized this is a really futile thing to do. The Razor engine works by generating code, which must then be compiled using CSharpCodeProvider. This takes time. A lot of time!

做到这一点的唯一可行和有效的方法是将模板字符串保存在某个地方,对其进行预编译,并在调用时调用这些已编译的模板。这使它基本上对您的使用毫无用处,因为这正是带有Razor的ASP.NET MVC所擅长的-将视图保存在适当的位置,对其进行预编译,并在引用时调用它们。 更新 :嗯,也许大量的缓存可能会有所帮助,但是我仍然不会真正推荐此解决方案。

The only viable and efficient way of doing this would be to save your template strings somewhere, precompile them, and call those compiled templates when called upon. This makes it basically useless for what you are after, because this would be exactly what ASP.NET MVC with Razor is good at - keeping Views in a good place, precompiling them, and calling upon them when referenced. Update: Well, maybe a heavy dose of caching might help, but I still wouldn't actually recommend this solution.

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

When generating the code, Razor emits calls to this.Write and this.WriteLiteral. Because this is an object inheriting from a baseclass that you write yourself, it is up to you to provide implementations of Write and WriteLiteral.

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

If you are using any other HtmlHelper extensions in your template string, you need to include assembly references and namespace imports for all of them. The code below adds the most common ones. Because of the nature of anonymous types, those cannot be used for model classes.

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自动请求重新编译,您就可以做一些非常有趣的事情在这里。

Doing this "live" - having Razor compile and run for each page load is obviously too slow if you don't go heavy on the caching, but if you break out pieces of my code and have your CMS request a recompilation automatically whenever the contents of a page changes, you could do something really interesting here.

这篇关于在视图中呈现包含剃刀代码的字符串的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆