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!
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! 😃
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>