Search code examples
c#razor.net-5razorlight

Project can not find template with key _Layout.cshtml


I'm struggling to make email work while working with embedded resource for _Layout.cshtml in FluentEmail.

This is the exception error I'm getting:

Project can not find template with key _Layout.cshtml

This is my setup so far:

Inside the ConfigureServices in Program.cs, I've added the RazorRenderer as:

//Set email service using FluentEmail
 services.AddFluentEmail("[email protected]")
 .AddRazorRenderer(typeof(Program))
 .AddSmtpSender("smtp.somesmtp.com", 25)
 .AddSmtpSender(new System.Net.Mail.SmtpClient() { });

Inside my NotificationService.cs, I always fall into that exception block:

private async Task SendEmailAsync<TModel>(string subject, TModel model)
{
    try
    {
        using (var scope = _serviceProvider.CreateScope())
        {
            var email = await scope.ServiceProvider.GetRequiredService<IFluentEmail>()
                    .To(string.Join(";", _emailRecipients))
                    .Subject(subject)
                    .UsingTemplateFromEmbedded("AppName.Views.Emails.SomeReport.cshtml", model, GetType().Assembly)
                    .SendAsync();
        }
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Failed to send email. Check exception for more information.");
    }
}

SomeReport.cshtml is inside Views\Emails\SomeReport.cshtml which looks like this:

@using System;
@using RazorLight;
@using System.Collections.Generic;
@using AppName.Models;

@inherits TemplatePage<IEnumerable<SomeReport>>
@{
    @* For working with Embedded Resource Views *@
    Layout = "_Layout.cshtml";
}

@* Work with the Model here... *@

_Layout.cshtml is inside Views\Shared\_Layout.cshtml which looks like this:

@using RazorLight;
@* Some common layout styles here *@
@RenderBody()

Both the SomeReport.cshtml and _Layout.cshtml are Embedded resources:

My references has RazorLight through FluentEmail.Razor package.

If it helps, this is a .NET 5 Worker Service project and I've also added PreserveCompilationContext and PreserveCompilationReferences in the .csproj file:

<PropertyGroup>
  <TargetFramework>net5.0</TargetFramework>
  <PreserveCompilationContext>true</PreserveCompilationContext>
  <PreserveCompilationReferences>true</PreserveCompilationReferences>
</PropertyGroup>

I've looked at everywhere and still haven't found a solution to this. Something this simple has been such a struggle to make work. Please help.

Thanks!


Solution

  • TL;DR

    Point to Note 1:

    Specifying the path to _Layout.cshtml in your Views must be relative to the embedded resource root you enter while adding FluentEmail. For eg:

    .AddRazorRenderer(typeof(Program))
    

    Since my Program.cs and Views folder are in the same level, to locate _Layout.cshtml by SomeReport.cshtml, my path needs to be:

    @{
        Layout = "Views.Shared._Layout.cshtml";
    }
    

    Point to Note 2:

    Specifying the path to your View needs to be the full path starting from your Assembly name. For eg:

    var razorTemplatePath = "AppName.Views.Emails.SomeReport.cshtml";
    

    It works like a charm now! 😃


    My full setup

    Inside the ConfigureServices method in Program.cs -->

    // Set email service using FluentEmail
    services.AddFluentEmail("[email protected]")
            // For Fluent Email to find _Layout.cshtml, just mention here where your views are. This took me hours to figure out. - AshishK
            //.AddRazorRenderer(@$"{Directory.GetCurrentDirectory()}/Views/")
            .AddRazorRenderer(typeof(Program)) //If you want to use views as embedded resource, use this
            .AddSmtpSender("smtp.somesmtp.com", 25)
            .AddSmtpSender(new System.Net.Mail.SmtpClient() { });
    

    Inside my FluentEmailService.cs:

    public class FluentEmailService
    {
        private readonly IFluentEmailFactory _fluentEmailFactory;
        private readonly ILogger<FluentEmailService> _logger;
    
        // Keep in mind that only the Singleton services can be injected through the constructor - AshishK.
        public FluentEmailService(ILogger<FluentEmailService> logger, IFluentEmailFactory fluentEmailFactory)
        {
            _logger = logger;
            _fluentEmailFactory = fluentEmailFactory;
        }
    
        public async Task<SendResponse> SendEmailAsync<TModel>(string subject, string razorTemplatePath, TModel model, string semicolonSeparatedEmailRecipients)
        {
            try
            {
                // var razorTemplatePathExample = "AppName.Views.Emails.SomeReport.cshtml";
                // var semicolonSeparatedEmailRecipientsExample = "[email protected];[email protected]";
                // 'model' is whatever you send to the View. For eg: @model IEnumerable<SomeReport> that we put at the top of SomeReport.cshtml view (see below).
    
                var email = await _fluentEmailFactory
                                .Create()
                                .To(semicolonSeparatedEmailRecipients)
                                .Subject(subject)
                                .UsingTemplateFromEmbedded(razorTemplatePath, model, GetType().Assembly) //If you want to use embedded resource Views.
                                .SendAsync();
                return email;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to send email. Check exception for more information.");
                return new SendResponse() { ErrorMessages = new string[] { ex.Message } };
            }
        }
    }
    

    The properties of my Views (SomeReport.cshtml, _Layout.cshtml) look like this:

    (You don't need to do this for _ViewImports.cshtml and _ViewStart.cshtml.)

    My View SomeReport.cshtml is inside Views\Emails\SomeReport.cshtml and looks like this:

    @model IEnumerable<SomeReport>
    
    @{
        Layout = "Views.Shared._Layout.cshtml";
    }
    
    @* Work with the Model here... *@
    

    _Layout.cshtml is inside Views\Shared\_Layout.cshtml which looks like this:

    <!DOCTYPE html>
    <html>
    ...
    </html>
    

    My references has RazorLight through FluentEmail.Razor package.

    The settings for Razorlight looks like this in my .csproj file:

    <PropertyGroup>
      <!-- This group contains project properties for RazorLight on .NET Core -->
      <PreserveCompilationContext>true</PreserveCompilationContext>
      <MvcRazorCompileOnPublish>false</MvcRazorCompileOnPublish>
      <MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>
    </PropertyGroup>