adoconnection / RazorEngineCore

.NET6 Razor Template Engine
MIT License
580 stars 85 forks source link

@Model is null when using RazorEngineTemplateBase #28

Open kobynz opened 4 years ago

kobynz commented 4 years ago

I've run into an issue when attempting to strongly type a template. My template has @inherits RazorEngineCore.RazorEngineTemplateBase<MyModelType> at the top, and this makes intellisense work nicely.

The template compiles just fine, but when I run it, I always get "Object not sent to instance of an object" when I reference any property on my model. I know my model is not null. It works fine when render it without using RazorEngineTemplateBase.

Running a template with @(Model == null) works, but always renders "True".

My generic render function:

private static ConcurrentDictionary<int, IRazorEngineCompiledTemplate<IRazorEngineTemplate>> TemplateCache = 
            new ConcurrentDictionary<int, IRazorEngineCompiledTemplate<IRazorEngineTemplate>>();

public static string Render<TModel>(string template, TModel model, Assembly[] referencedAssemblies = null)
        {
            int templateHashCode = template.GetHashCode();

            var compiledTemplate = TemplateCache.GetOrAdd(templateHashCode, i =>
            {
                var razorEngine = new RazorEngine();

                var compiledTemplate = razorEngine.Compile<RazorEngineTemplateBase<TModel>>(template, builder =>
                {
                    ...
                });

                return compiledTemplate;
            });

            return compiledTemplate.Run(instance =>
            {
                instance.Model = model;
            });
        }

Any help on this would be greatly appreciated. Thanks.

kobynz commented 4 years ago

Another update on this, It seems my "weakly typed" version of this function is able to render my template just fine. My template still has @inherits RazorEngineCore.RazorEngineTemplateBase<MyModelType>

private static ConcurrentDictionary<int, IRazorEngineCompiledTemplate> TemplateCache = 
            new ConcurrentDictionary<int, IRazorEngineCompiledTemplate>();

public static string Render(string template, object model, Assembly[] referencedAssemblies = null)
        {
            int templateHashCode = template.GetHashCode();

            var compiledTemplate = TemplateCache.GetOrAdd(templateHashCode, i =>
            {
                var razorEngine = new RazorEngine();

                return razorEngine.Compile(template, builder =>
                {
                    if (referencedAssemblies != null)
                    {
                        foreach (var assembly in referencedAssemblies)
                        {
                            builder.AddAssemblyReference(assembly);
                        }
                    }
                });
            });

            return compiledTemplate.Run(model);
        }

I'm guessing the null issue is related to using the generic Compile<T>.

adoconnection commented 4 years ago

Its because of new keywork I used: https://github.com/adoconnection/RazorEngineCore/blob/master/RazorEngineCore/RazorEngineTemplateBaseT.cs

Thats design issue, I need to think a little.

adoconnection commented 4 years ago

as a quick solution I would simplify cache dictionary:

private static ConcurrentDictionary<int, object> TemplateCache = new ConcurrentDictionary<int, object>();
public static string Render<TModel>(string template, TModel model, Assembly[] referencedAssemblies = null)
{
    int templateHashCode = template.GetHashCode();

    var compiledTemplate = (IRazorEngineCompiledTemplate<RazorEngineTemplateBase<TModel>>) TemplateCache.GetOrAdd(templateHashCode, i =>
    {
        var razorEngine = new RazorEngine();

        var compiledTemplate = razorEngine.Compile<RazorEngineTemplateBase<TModel>>(template, builder =>
        {

        });

        return compiledTemplate;
    });

    return compiledTemplate.Run(instance =>
    {
        instance.Model = model;
    });
}
farlee2121 commented 4 years ago

This is a design expression issue I've long struggled with in C#.

C# 9 will supposedly be implementing covariant overrides. That would solve this issue, but is a ways out https://github.com/dotnet/csharplang/issues/2844

farlee2121 commented 4 years ago

Maybe not so far out. Looks like they plan to release C# 9 with .NET 5, which is already to release candidates https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/configure-language-version

adoconnection commented 4 years ago

Cool, however this breaking change for new keywork is confusing. I would prefer something like this, with override keyword

public abstract class RazorEngineTemplateBase<T> : RazorEngineTemplateBase
{
    public override T Model { get; set; }
}

public abstract class RazorEngineTemplateBase : IRazorEngineTemplate
{
    public virtual dynamic Model { get; set; }
}
farlee2121 commented 4 years ago

That appears to be the design they're going for. https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/covariant-returns.md#motivation

I wasn't clear that this library would need to change from new to override.

adoconnection commented 4 years ago

good news anyway :)