2013-02-14 51 views
4

我注意到RazorEngine.Compile()似乎以不同于其他类型的方式处理匿名类型。例如,请考虑下面的代码:为什么RazorEngine动态地使用匿名类型模型编译模板?

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通过生成动态模板来为匿名类型提供特殊支持。

因此我有两个问题: (1)我对此行为的理解是否正确? (2)有没有办法让剃刀为匿名模型输出强类型的模板?

+0

如果不太了解剃刀是如何工作的,它似乎很可能。虽然匿名类型确实是常规的“强”类型,但在编译模板时,即使他们想要做这种类型的voodoo,也不可能引用具有确定性的类型(因为模板可能会被许多控制器调用)。 – millimoose 2013-02-14 16:12:16

+0

但是,假设它们生成了一个类,它们可以为具有相同模式的本地匿名类型生成代码,并从一个类型映射到另一个类型。 – ChaseMedallion 2013-02-14 16:15:40

+0

他们可以做所有的事情,但当它们的价值超过所需的努力时,它们中的大多数可以很容易地踢。在这种情况下,除了为了强类型而强制键入代码之外,没有什么好处。 – millimoose 2013-02-14 16:20:43

回答

3

我的想法是让剃刀组件a friend assembly到您的程序集(从而使内部可见),但没有奏效。在剃刀的源代码,我们可以看到下面的代码(在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; 

所以是的 - 你的理解是正确的。此外,我们可以看到,使内部可见是不够的,因为剃刀将任何匿名类型视为动态(RazorDynamicObject扩展为DynamicObject)。您可以尝试修补Razor代码,以便在包含程序集的内部结构可见时不会将匿名类型视为动态的。

在这种情况下,我认为代码必须发出一个新的公共类型,其中包含与模型的匿名类型相同的属性,以便Razor生成的代码可以实例化该类型的实例。或者,也可以使用描述here的破解方法,以允许方法返回匿名类型的实例。

1

动态用于允许用于匿名类型。当它编译模型时,它不能引用匿名类型,因为它们在你的程序集内部。考虑到这一点,Razor解析器试图生成一个从TemplateBase<T>继承的模板,以提供强类型支持。这就是我们使用TemplateBase<dynamic>的原因,因为这意味着我们将对这些属性的访问委托给DLR,但是您失去了静态类型类的好处。