如何从资源字符串中使用Razor模型绑定?

时间:2016-08-27 18:41:33

标签: asp.net-mvc razor embedded-resource

通常我们有:

<div>
  <div>Some property from a model: @Model.Property</div>
</div>

但是,让我说我在资源文件中将这条确切的行作为完整的html行并尝试引用它:

<div>
  @Resource.MyResourceLine
</div>

模型绑定不起作用。它渲染生成线而不绑定。

如何在这种情况下使Razor绑定?

编辑: 通过将资源字符串上的内容更改为string.Format占位符,可以采用另一种方法:

<div>Some property from a model:{0}</div>

然后:

<div>
  @string.Format(@Resource.MyResourceLine,@Model.Property)
</div>

但这使得很难维护包含许多属性引用的大型文本。如果可以在资源文件中看到属性名称,那将是理想的。有更优雅的方式吗?

1 个答案:

答案 0 :(得分:1)

我在官方codeplex中搜索的Asp.Net Mvc(最新版本为5.2.3)的源代码中进行了一些挖掘:https://aspnetwebstack.codeplex.com/

简短回答: 开箱即用没有简单的方法,因为页面已经被编译,并且您在模型中传递的任何字符串都被视为字符串 - MvcHtmlString或String。您可以使用RazorEngine软件包快速完成并且没有太多问题:(https://github.com/Antaris/RazorEngine

答案很长:

当你打开路径,并且控制器为它提供视图时,你必须为该视图获取已解析和编译的代码(可能在启动期间生成,或者在实际使用该视图之前懒洋洋地生成),然后渲染将已编译的View和模型数据组合在一起的页面(在控制器中调用View()方法时完成)。

ASP.NET如何解析和编译视图,为其生成运行代码:

// https://aspnetwebstack.codeplex.com/SourceControl/latest#src/System.Web.Razor/RazorTemplateEngine.cs
// Line 152
protected internal virtual GeneratorResults GenerateCodeCore(ITextDocument input, string className, string rootNamespace, string sourceFileName, CancellationToken? cancelToken) {
    //...
    // Run the parser
    RazorParser parser = CreateParser();
    Debug.Assert(parser != null);
    ParserResults results = parser.Parse(input);

    // Generate code
    RazorCodeGenerator generator = CreateCodeGenerator(className, rootNamespace, sourceFileName);
    generator.DesignTimeMode = Host.DesignTimeMode;
    generator.Visit(results);
    //...
}

asp.net如何呈现页面,结合视图的源代码和模型中的数据

// https://aspnetwebstack.codeplex.com/SourceControl/latest#src/System.Web.WebPages/WebPageBase.cs
// Line 215
// public override void ExecutePageHierarchy() {
// ...
    try
    {
        // Execute the developer-written code of the WebPage
        Execute(); //**you can see example of the code it executes right below in the code block**
    }
    finally
    {
        TemplateStack.Pop(Context);
    }
}

编译视图后,它变成了一个简单的C#类,它生成一个字符串,然后在浏览器中显示给用户。让我们创建一个简单的Controller,View和ViewModel:

以下是一些代码:

ViewModel类:

namespace StackOverflow.Models
{
    public class TestViewModel
    {
        public int IntProperty { get; set; }
        public string StringProperty { get; set; }
    }
}

控制器代码:

public ActionResult Test()
{       
    var viewModel = new TestViewModel
    {
        IntProperty = 5,
        StringProperty = "@DateTime.UtcNow.ToString()"
    };

    return View(viewModel);
}

查看:

@model StackOverflow.Models.TestViewModel

@{
    ViewBag.Title = "just a test";
    Layout = null;
}

@Model.IntProperty

@Html.Raw(@Model.StringProperty)

使用上面代码的页面示例生成以下编译视图:

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:4.0.30319.42000
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace ASP {
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Web;
    using System.Web.Helpers;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.WebPages;
    using System.Web.Mvc;
    using System.Web.Mvc.Ajax;
    using System.Web.Mvc.Html;
    using System.Web.Optimization;
    using System.Web.Routing;
    using StackOverflow;


    public class _Page_Views_Test_Index_cshtml : System.Web.Mvc.WebViewPage<StackOverflow.Models.TestViewModel> {

        #line hidden

        public _Page_Views_Test_Index_cshtml() {
        }

        protected ASP.global_asax ApplicationInstance {
            get {
                return ((ASP.global_asax)(Context.ApplicationInstance));
            }
        }

        public override void Execute() {

            #line 3 "XXX\Views\Test\Index.cshtml"

            ViewBag.Title = "just a test";
            Layout = null;


            #line default
            #line hidden
            BeginContext("~/Views/Test/Index.cshtml", 100, 4, true);

            WriteLiteral("\r\n\r\n");

            EndContext("~/Views/Test/Index.cshtml", 100, 4, true);

            BeginContext("~/Views/Test/Index.cshtml", 105, 17, false);


            #line 8 "XXX\Views\Test\Index.cshtml"
            Write(Model.IntProperty);


            #line default
            #line hidden
            EndContext("~/Views/Test/Index.cshtml", 105, 17, false);

            BeginContext("~/Views/Test/Index.cshtml", 122, 4, true);

            WriteLiteral("\r\n\r\n");

            EndContext("~/Views/Test/Index.cshtml", 122, 4, true);

            BeginContext("~/Views/Test/Index.cshtml", 127, 31, false);


            #line 10 "XXX\Views\Test\Index.cshtml"
            Write(Html.Raw(@Model.StringProperty));


            #line default
            #line hidden
            EndContext("~/Views/Test/Index.cshtml", 127, 31, false);

        }
    }
}

如您所见,您的页面代码只是逐节写入输出,检查Write方法会导致这些实现细节:

public override void Write(object value)
{
    WriteTo(Output, value);
}

public static void WriteTo(TextWriter writer, object content)
{
    writer.Write(HttpUtility.HtmlEncode(content)); //writer - instance of TextWriter
}

因此,您在viewmodel中放入字符串字段的任何内容都只是使用HtmlEncode方法进行编码并放到页面中,并且无法在运行时使用默认使用的mvc功能进行编译。

我很确定,你可以用Mvc和Razor深入挖掘资源,但这需要更多的时间,可能需要很多好的老黑客。要获得快速简单的解决方案,您可以使用https://github.com/Antaris/RazorEngine包。您还可以查看其源代码,了解他们是如何做到的。

这是控制器代码,它将使用RazorEngine包提供模板:

public ActionResult Test()
{
    var stringTemplate = @"
        @model StackOverflow.Models.TestViewModel  

        <br/>
        >>COMPILED
        <br/>

        @DateTime.UtcNow.ToString() 
        <br/>
        Compiled model int property value:
        <br/>
        @Model.IntProperty
    ";

    var viewModel = new TestViewModel
    {
        IntProperty = 5,
        StringProperty = null
    };

    viewModel.StringProperty = Engine.Razor.RunCompile(stringTemplate, viewModel.GetType().ToString(), null, viewModel);

    return View("Index", viewModel);
}

这里的基本思路非常简单 - 将渲染传递给组件,在控制器中执行并将您获得的字符串传递给ViewModel,然后使用@ HtmlHelper.Raw呈现从引擎获取的HTML。

这可能适用于很多场景,但我强烈建议你不要这样做,除非你真的需要它并且没有可行的选择。动态剃刀模板很难维护。