Search code examples
c#asp.net-mvcrazor

FluentEmail.Razor Value cannot be null. Parameter name: operatingAssembly


I have an ASP.Net MVC application (targeted for .Net Framework 4.7.2).

I installed FluentEmail.Core, FluentEmail.Smtp and FluentEmail.Razor (all of them version 3.0.0)

FluentEmail.Razor required LightRazor version 2.0.0-rc3, I had to manually install it since nuget default settings will only download final release (at the time of posting this question, the final stable release of RazorLight is version 1.0.0 which does not meet the requirement of FluentEmail.Razor v 3.0.0).

In my code, I am sending emails with no issues like this:

public async Task<ActionResult> Index()
{

       var sender = new SmtpSender(() => new SmtpClient("localhost"));
       Email.DefaultSender = sender;


      var email = await Email
         .From("[email protected]")
         .To("[email protected]")
         .Subject("subject-test")
         .Body("body test")
         .SendAsync();           // this works perfectly

    return View();
}

Now when I try to use Razor, I get an error:

Value cannot be null. Parameter name: operatingAssembly ASP.Net MVC FluentEmail.Razor Error Here is what I changed:

public async Task<ActionResult> Index()
{

    var sender = new SmtpSender(() => new SmtpClient("localhost"));
    Email.DefaultSender = sender;
    Email.DefaultRenderer = new RazorRenderer();    // this line causes the error
    
    var email = await Email
         .From("[email protected]")
         .To("[email protected]")
         .Subject("subject-test")
        .UsingTemplate("Dear @Model.FirstName this is a test", new { FirstName = "Mike" })
        .SendAsync();           
    
    return View();
}

I searched for answers to this question and found that I need to tell the RazorEngine soem information about the model typeof(dynamic) but I can't do that, I have to specify a class name. So I created a dedicated class for the my model, but I cant pass a reference of RazorEngine to FluentEmail Default Renderer.

What am I doing wrong?


Solution

  • I was able to reproduce the issue with a simple ASP.Net MVC application.

    The code fails because Assembly.GetEntryAssembly() returns null here when it tries to build the RazorLightEngine. The official documentation says Assembly.GetEntryAssembly() :

    Can return null when called from unmanaged code.

    In your case, the issue happens because your code is probably run from a non-managed process (IIS Express).

    Someone suggested this workaround which consists of replacing Assembly.GetEntryAssembly() with Assembly.GetCallingAssembly() (which always returns a non null value) :

    new RazorLightEngineBuilder()
        .SetOperatingAssembly(operatingAssembly ?? Assembly.GetCallingAssembly())
        .UseFileSystemProject(Directory.GetCurrentDirectory())
        .UseMemoryCachingProvider()
        .Build();
    

    But in your case, you can't use it directly because RazorRenderer doesn't expose how the RazorLightEngine is built.

    So I think you need to create your own custom RazorRenderer to work around the issue.

    Here is an example of a renderer that will work (I copied RazorRenderer code from here, modified the first constructor and removed the other constructors):

    using FluentEmail.Core.Interfaces;
    using FluentEmail.Razor;
    using RazorLight;
    using System.IO;
    using System.Reflection;
    using System.Threading.Tasks;
    
    namespace WebApplication2.Controllers
    {
        public class CustomRazorRenderer : ITemplateRenderer
        {
            private readonly RazorLightEngine _engine;
    
            public CustomRazorRenderer(string root = null)
            {
                _engine = new RazorLightEngineBuilder()
                    .SetOperatingAssembly(Assembly.GetCallingAssembly())
                    .UseFileSystemProject(root ?? Directory.GetCurrentDirectory())
                    .UseMemoryCachingProvider()
                    .Build();
            }
    
            public Task<string> ParseAsync<T>(string template, T model, bool isHtml = true)
            {
                dynamic viewBag = (model as IViewBagModel)?.ViewBag;
                return _engine.CompileRenderStringAsync<T>(RazorRenderer.GetHashString(template), template, model, viewBag);
            }
    
            string ITemplateRenderer.Parse<T>(string template, T model, bool isHtml)
            {
                return ParseAsync(template, model, isHtml).GetAwaiter().GetResult();
            }
    
        }
    }
    

    And it can be used like this :

    public async Task<ActionResult> Index()
    {
    
        var sender = new SmtpSender(() => new SmtpClient("localhost"));
        Email.DefaultSender = sender;
        Email.DefaultRenderer = new CustomRazorRenderer();
    
        var email = await Email
            .From("[email protected]")
            .To("[email protected]")
            .Subject("subject-test")
            .UsingTemplate("Dear @Model.FirstName this is a test", new { FirstName = "Mike" })
        .SendAsync();
    
        return View();
    }