RazorEngine extension method fails with RuntimeBin

2019-05-24 11:48发布

问题:

I have a RazorEngine project that fails following an upgrade to Razor 2.0 and RazorEngine 3.2.0

This worked fine in the previous Razor 1.0 based version of RazorEngine (3.0.8).

I have an instance (myInstance) of a class (MyClass) and and extension method:

namespace MyCompany.Extensions 
{
    public static class MyClassExtensions
    {
        public static string ExtensionMethod(this MyClass thing) 
        {
            // do stuff
        }
    }
}

I want to call this in a RazorEngine view (simplified example, there are loads of these methods, and all fail the same way):

@using MyCompany.Extensions
@using MyCompany
@{
    var myInstance = new MyClass(Model, ...);
}

Some text @myInstance.ExtensionMethod() some more text

This is in a text file that's compiled by RazorEngine:

string parsedResult = RE::Razor.Parse(fileContent, myModel, "testfile.txt");

The problem is that this line (which used to work) throws a RuntimeBinderException:

'MyCompany.MyClass' does not contain a definition for 'ExtensionMethod'

Note that if I change the text file to:

Some text @MyClassExtensions.ExtensionMethod(myInstance) some more text

It works fine, so I think it must find the extension method's namespace.

My first thought was that it must be considering the passed model as a dynamic (and hence anything derived from it as dynamic too), but it knows the expected type in the RuntimeBinderException. As the exception is run-time I think it must be failing to identify the extension method while the template is compiled, but why would that have changed?

I'm not sure what's changed between 3.0.8 and 3.2.0, or why this is broken. Is there something I need to add so that the extension method can be found while the template is compiled?

回答1:

This is a bug in RazorEngine: the Razor.Compile works on TemplateBase<dynamic> (so Model and everything derived from it is dynamic too) and that means that no extension methods undergo the 'compiler-magic' to convert them to the static calls. Then Razor.Run passes the Model as the correct type, but the extension method syntax is called as an instance method.

There will probably be a fix for this soon (the bug's only a few days old and this is a corner case), but in the meantime I have a workaround: explicitly type the Model in the Razor template

@using MyCompany.Extensions
@using MyCompany
@{
    ExpectedModelClass strongTypeModel = Model as ExpectedModelClass;
    MyClass myInstance = new MyClass(strongTypeModel , ...);
}

Some text @myInstance.ExtensionMethod() some more text

This now works, because even though Model is still dynamic at compile-time that doesn't spread to myInstance any more.

It's not ideal, and everywhere I used Model now has to be strongTypeModel, but that's a much simpler substitution.