Generate HTML Email from Razor View Page with a Strongly Typed Model

47 views Asked by At

I am attempting to generate an HTML email from a Razor page. This works well for HTML without any model. However when I add a model "WelcomeEmailModel" and attempt to pass a variable, I get the following error on the '@Model.UserName' parameter.

'Object reference not set to an instance of an object.'

The model is not being passed to the view.

I can see in the ViewData (see image below), that the Model 'Username' is populated with "Jane Doe".

The examples I have found via google do not include a model. Is it possible to send the model this way or should the model be passed another way?

Drilldown

Razor View

@page
@model WelcomeEmailModel

<!DOCTYPE html>
<html>
<head>
    <title>Welcome to Our Website!</title>
</head>
<body>
    <h2>Welcome, @Model.UserName!</h2>
    <p>Thank you for signing up to our website. We're excited to have you on board!</p>
</body>
</html>

EmailService.cs

namespace FlightLog.Services
{
    public class EmailService
    {
        private readonly EmailClient _emailClient;
        private readonly string _senderEmailAddress;
        private readonly IRazorViewEngine _razorViewEngine;
        private readonly IServiceProvider _serviceProvider;

        public EmailService(string connectionString, string senderEmailAddress, IRazorViewEngine razorViewEngine, IServiceProvider serviceProvider)
        {
            _emailClient = new EmailClient(connectionString);
            _senderEmailAddress = senderEmailAddress;
            _razorViewEngine = razorViewEngine;
            _serviceProvider = serviceProvider;
        }

        public async Task<string> RenderViewToStringAsync<TModel>(string viewName, TModel model)
        {
            var actionContext = GetActionContext();
            var viewEngineResult = _razorViewEngine.FindView(actionContext, viewName, false);
            //var viewEngineResult = _razorViewEngine.GetView("~/Views/EmailTemplates", $"{viewName}.cshtml", false);

            if (!viewEngineResult.Success)
            {
                throw new InvalidOperationException($"Unable to find view '{viewName}'");
            }

         
            using (var sw = new StringWriter())
            {
                var tempDataSerializer = (TempDataSerializer)_serviceProvider.GetService(typeof(TempDataSerializer));
                var tempDataProvider = new SessionStateTempDataProvider(tempDataSerializer);

                var viewContext = new ViewContext(
                    actionContext,
                    viewEngineResult.View,
                    new ViewDataDictionary<TModel>(
                    metadataProvider: new EmptyModelMetadataProvider(),
                    modelState: new ModelStateDictionary())
                    {
                        Model = model
                    },
                    new TempDataDictionary(actionContext.HttpContext, tempDataProvider),
                    sw,
                    new HtmlHelperOptions()
                );

                await viewEngineResult.View.RenderAsync(viewContext);

                return sw.ToString();
            }
        }

        private ActionContext GetActionContext()
        {
            var httpContext = new DefaultHttpContext
            {
                RequestServices = _serviceProvider
            };
            return new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
        }

        public async Task SendEmailAsync(string recipient, string subject, string body)
        {

            var emailContent = new EmailContent(subject)
            {
                Html = body
            };

            var emailMessage = new EmailMessage(
                  _senderEmailAddress,
                  recipient,
                  emailContent);

            await _emailClient.SendAsync(WaitUntil.Completed, emailMessage, CancellationToken.None);
        }

        public async Task SendWelcomeEmailAsync(string recipient, string userName)
        {
            var viewModel = new WelcomeEmailModel
            {
                UserName = userName
            };

            string emailBody = await RenderViewToStringAsync("WelcomeEmail", viewModel);
            await SendEmailAsync(recipient, "Welcome to Our Website!", emailBody);
        }
    }
}
1

There are 1 answers

1
Rena On BEST ANSWER

Be sure remove the @page in your razor view because your custom RenderViewToStringAsync method is used to render razor view instead of razor pages to string.

The difference between razor view and razor pages:

Razor Pages can make coding page-focused scenarios easier and more productive than using controllers and views. What makes it different is the @page directive. @page makes the file into an MVC action, which means that it handles requests directly, without going through a controller. @page must be the first Razor directive on a page. @page affects the behavior of other Razor constructs.