在视图中渲染包含剃刀代码的字符串

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

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

预期产量:

 My CMS content with razor code: Click 

MyCustomRazorStringRenderer是什么样的? 它必须以某种方式做某事。 比如创建/使用ViewContext并渲染它(就像这里: 将视图渲染为一个字符串 ),但我无法理解它。

您必须创建一个包含扩展方法的静态类。 该方法必须返回包含安全呈现的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

这些类是这个类的起点: RazorEngineHost , RazorTemplateEngine , CSharpCodeProvider , HtmlHelper 。

工作代码

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

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

生成代码时,Razor会发出对this.Writethis.WriteLiteral调用。 因为this是一个inheritance自您自己编写的基类的对象,所以您需要提供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(this HtmlHelper helper, string template, TModel model) { string output = Render(helper, template, model); return new MvcHtmlString(output); } private static string Render(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).Namespace; host.DefaultBaseClass = nameof(MyTemplateBase) + "<" + 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).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; templateImplementation.Model = model; templateImplementation.Html = helper; templateImplementation.Execute(); // 6. Return the html output return templateImplementation.Output.ToString(); } } 

MyTemplateBase <>类

 public abstract class MyTemplateBase { 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 

Test

@Html.RazorEncode("

Paragraph output

") @Html.RazorEncode("

Using a @Model

", "string model" ) @Html.RazorEncode("@for (int i = 0; i < 100; ++i) {

@i

}") @Html.RazorEncode("@Html.ActionLink(Model.Text, Model.Action)", new TestModel { Text = "Foo", Action = "Bar" })

更新

这样做“实时” – 如果你不重视缓存,让Razor编译并运行每个页面加载显然太慢了,但如果你分解我的代码并让你的CMS在内容时自动重新编译一个页面的变化,你可以在这里做一些非常有趣的事情。