为什么RazorEngine使用匿名类型模型动态编译模板
本文关键字:动态 模型 编译 类型 RazorEngine 为什么 | 更新日期: 2023-09-27 17:59:26
我注意到RazorEngine.Compiler()似乎对待匿名类型与其他类型不同。例如,考虑以下代码:
public static void Main()
{
try {
var model = new { s = default(string) };
RazorEngine.Razor.Compile("@Model.s.Length", model.GetType(), "a");
RazorEngine.Razor.Run(model, "a");
} catch (Exception ex) {
Console.WriteLine(ex); // RuntimeBinderException (Cannot perform runtime binding on a null reference)
}
try
{
var model = "";
RazorEngine.Razor.Compile(@"@Model.Length", model.GetType(), "b");
RazorEngine.Razor.Run(default(string), "b");
} catch (Exception ex) {
Console.WriteLine(ex); // NullReferenceException
}
try
{
var model = Tuple.Create(default(string));
RazorEngine.Razor.Compile(@"@Model.Item1.Length", model.GetType(), "c");
RazorEngine.Razor.Run(model, "c");
} catch (Exception ex) {
Console.WriteLine(ex); // NullReferenceException
}
try
{
var model = new Internal();
RazorEngine.Razor.Compile(@"@Model.S.Length", model.GetType(), "d");
RazorEngine.Razor.Run(model, "d");
} catch (Exception ex) {
Console.WriteLine(ex); // TemplateCompilationException (type Internal is not visible)
}
}
internal class Internal {
public string S { get; set; }
}
我的理解是:匿名类型是内部的,所以Razor通常不会处理它们。然而,Razor通过生成动态模板来为匿名类型提供特殊支持。
因此,我有两个问题:(1) 我对这种行为的理解正确吗?(2) 有什么方法可以让剃刀为匿名模型输出强类型模板吗?
我曾想过让Razor程序集成为程序集的朋友程序集(从而使内部可见),但这并没有奏效。在Razor源代码中,我们可以看到以下代码(在CompilerServiceBase.cs
中):
if (modelType != null)
{
if (CompilerServices.IsAnonymousType(modelType))
{
type.CustomAttributes.Add(new CodeAttributeDeclaration(new CodeTypeReference(typeof(HasDynamicModelAttribute))));
}
}
然后(在TemplateBaseOfT.cs
中):
HasDynamicModel = GetType().IsDefined(typeof(HasDynamicModelAttribute), true);
稍后在同一文件中使用:
if (HasDynamicModel && !(value is DynamicObject) && !(value is ExpandoObject))
model = new RazorDynamicObject { Model = value };
else
model = value;
是的,你的理解是正确的。此外,我们可以看到,仅仅使内部可见是不够的,因为Razor将任何匿名类型都视为动态类型(RazorDynamicObject
扩展了DynamicObject
)。如果包含程序集的内部可见,您可以尝试修补Razor代码,使其不将匿名类型视为动态类型。
不过,在这种情况下,我认为代码必须发出一个新的公共类型,该类型包含与模型的匿名类型相同的属性,这样Razor生成的代码才能实例化该类型的实例。或者,您可以使用此处描述的破解方法,以允许方法返回匿名类型的实例。
Dynamic用于允许匿名类型。编译模型时,它不能引用匿名类型,因为它们是程序集内部的。考虑到这一点,Razor解析器正试图生成一个从TemplateBase<T>
继承的模板,以提供强类型支持。这就是我们使用TemplateBase<dynamic>
的原因,因为这意味着我们将对这些属性的访问委托给DLR,但您将失去静态类型类的好处。