Why does RazorEngine compile templates with anonymously typed models dynamically?

I noticed that RazorEngine.Compile () seems to relate to anonymous types differently from other types. For example, consider the following code:

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; } } 

My understanding is this: Anonymous types are internal, so Razor usually can't handle them. However, Razor provides special support for anonymous types, creating a dynamic template instead.

So I have two questions: (1) Do I understand this behavior correctly? (2) Is there a way to get a razor to output a strongly typed template for an anonymous model?

+4
source share
2 answers

I had the idea to make the Razor assembly an assembly of friends on your assembly (thereby making internal visibility), but this did not work. In the Razor source code, we can see the following code (in CompilerServiceBase.cs ):

  if (modelType != null) { if (CompilerServices.IsAnonymousType(modelType)) { type.CustomAttributes.Add(new CodeAttributeDeclaration(new CodeTypeReference(typeof(HasDynamicModelAttribute)))); } } 

And then later (in TemplateBaseOfT.cs ):

 HasDynamicModel = GetType().IsDefined(typeof(HasDynamicModelAttribute), true); 

Used later in the same file:

 if (HasDynamicModel && !(value is DynamicObject) && !(value is ExpandoObject)) model = new RazorDynamicObject { Model = value }; else model = value; 

So yes, your understanding is correct. In addition, we see that the visibility of internal elements is insufficient, because Razor considers any anonymous types as dynamic ( RazorDynamicObject extends DynamicObject ). You can try to fix the Razor code so that you do not consider anonymous types as dynamic if the internal elements of the contained assembly are visible.

In this case, I think that the code would have to generate a new public type that contains the same properties as the anonymous type of the model, so that the code created by Razor can create instances of this type. Alternatively, you could use the hack described here to allow methods to return instances of anonymous types.

+3
source

Dynamic is used for anonymous types. When it compiles the model, it cannot refer to anonymous types because they are internal to your assembly. With that in mind, the Razor parser is trying to generate a template that inherits from TemplateBase<T> to give strongly typed support. That's why we use TemplateBase<dynamic> because it means that we delegate access to these DLR properties, but you lose the benefits of a statically typed class.

+1
source

All Articles