Как я могу использовать привязку модели Razor из строки ресурса?
Обычно мы имеем:
<div>
<div>Some property from a model: @Model.Property</div>
</div>
Но допустим, у меня есть точная строка в виде полной строки html в файле ресурсов, и я попытаюсь сослаться на нее:
<div>
@Resource.MyResourceLine
</div>
Привязка модели не работает. Это делает строку необработанной без привязки.
Как я могу заставить Razor связываться в этом сценарии?
РЕДАКТИРОВАТЬ: есть альтернативный способ, который работает, изменяя содержимое строки ресурса на строку. Формат заполнителя:
<div>Some property from a model:{0}</div>
а потом:
<div>
@string.Format(@Resource.MyResourceLine,@Model.Property)
</div>
Но это затрудняет ведение больших текстов со многими ссылками на свойства. Было бы идеально, если бы имена свойств можно было увидеть в файле ресурсов. Есть ли более элегантный способ?
1 ответ
Я немного копался в исходном коде Asp.Net Mvc (последняя версия 5.2.3), взятом из официального кодекса: https://aspnetwebstack.codeplex.com/
Краткий ответ: для этого нет простого способа, поскольку страница уже скомпилирована, и любая передаваемая вами строка в вашей модели обрабатывается как строка - либо MvcHtmlString, либо String. Вы можете использовать пакет RazorEngine, чтобы сделать это быстро и без особых проблем: ( https://github.com/Antaris/RazorEngine)
Длинный ответ:
Когда вы открываете маршрут, и контроллер обслуживает его представление, вы должны взять проанализированный и скомпилированный код для этого представления (который может быть сгенерирован во время запуска или лениво прямо перед тем, как вы фактически используете это представление), а затем визуализировать страницу, объединяющую скомпилированный Просмотр и данные вашей модели (что делается, когда вы звоните 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 Stackru.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 Stackru.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 Stackru;
public class _Page_Views_Test_Index_cshtml : System.Web.Mvc.WebViewPage<Stackru.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 Stackru.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, полученного из движка.
Это может работать для многих сценариев, но я настоятельно рекомендую вам не делать этого, если вам это действительно не нужно, и нет жизнеспособных альтернатив. Динамические шаблоны бритвы очень сложно поддерживать.