2013-02-14 50 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,但是您失去了靜態類型類的好處。