I have noticed that RazorEngine.Compile() seems to treat 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 normally Razor wouldn't handle them. However, Razor provides special support for anonymous types by generating a dynamic template instead.
I thus have two questions: (1) Is my understanding of this behavior correct? (2) Is there any way to get razor to output a strongly-typed template for an anonymous model?
I had the idea to make the Razor assembly a friend assembly to your assembly (thereby making internals visible), but that didn't 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);
Which is 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. Also, we can see that making internals visible is not sufficient, since Razor treats any anonymous types as dynamic (RazorDynamicObject
extends DynamicObject
). You could try to patch the Razor code to not treat anonymous types as dynamic if the internals of the containing assembly are visible.
In that case, though, I think the code would have to emit a new public type that contains the same properties as the anonymous type of the model, so that the code generated by Razor can instantiate instances of that type. Alternatively, you could maybe use the hack described here in order to allow methods to return instances of anonymous types.
Dynamic is used to allow for anonymous types. When it compiles the model, it can't reference the 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. This is why we use TemplateBase<dynamic>
because it means we delegate the access to these properties to the DLR, but you lose the benefits of a statically typed class.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With