I'm attempting to take this example that shows how to render Razor templates to string so that i can use them for rich email templates.
https://github.com/aspnet/Entropy/tree/master/samples/Mvc.RenderViewToString
I've updated code based on this repo that describes how to update the example to .NET Core 3.1
https://github.com/scottsauber/RazorHtmlEmails
I've altered it slightly because i'm trying to use a BackgroundService Worker in order to perform the render and transmission based on database changes.
Based on the answer in this question (Can a Worker Service be called and/or used inside an existing ASPNET.Core web project?) multiple process can be running at the same time. So i setup my hostbuilder like so.
public static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.UseWindowsService()
.ConfigureServices((hostContext, services) =>
{
services.AddDbContext<MyDataContext>(dbOptions =>
{
dbOptions.UseSqlServer(
hostContext.Configuration.GetConnectionString("db"),
sqlOptions => sqlOptions.EnableRetryOnFailure()
);
});
// This is our main worker that will loop
services.AddHostedService<Worker>();
// Add NLog as the logging handler
services.AddLogging(loggingBuilder =>
{
loggingBuilder.ClearProviders();
loggingBuilder.SetMinimumLevel(LogLevel.Trace);
loggingBuilder.AddNLog(hostContext.Configuration);
});
})
// Add our template renderer
.ConfigureWebHostDefaults(builder =>
{
builder.UseStartup<RazorStartup>();
});
}
and my RazorBuilder like so..
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IRazorViewToStringRenderer, RazorViewToStringRenderer>();
services.AddRazorPages();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
var factory = app.ApplicationServices.GetService<IServiceScopeFactory>();
using (var scope = factory.CreateScope())
{
var confirmAccountModel = new ConfirmAccountEmailViewModel($"NOOOOO");
var service = scope.ServiceProvider.GetService<IRazorViewToStringRenderer>();
var result = service.RenderViewToStringAsync("/Views/Emails/ConfirmAccount/ConfirmAccountEmail.cshtml", confirmAccountModel).Result;
Console.WriteLine(result);
}
}
And it's all.... "working"... as long as i change the target SDK from <Project Sdk="Microsoft.NET.Sdk.Worker">
to <Project Sdk="Microsoft.NET.Sdk.Web">
But when i do that my ILogger
for the worker loses the ability to write to console. So obviously the Sdk.Worker
is doing some setup that the Sdk.Web
is not doing.
When i use Sdk.Worker
i get errors about not being able to find the view. This means that Sdk.Web
is obviously doing something that Sdk.Worker
is not.
System.AggregateException
HResult=0x80131500
Message=One or more errors occurred. (Unable to find view '/Views/Emails/ConfirmAccount/ConfirmAccountEmail.cshtml'. The following locations were searched:
/Views/Emails/ConfirmAccount/ConfirmAccountEmail.cshtml)
Source=System.Private.CoreLib
StackTrace:
at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification)
at System.Threading.Tasks.Task`1.get_Result()
at CompoundAlertProcessor.RazorStartup.Configure(IApplicationBuilder app, IWebHostEnvironment env) in C:\Users\c.rice\Documents\Solutions\GIT\CompoundAlertProcessor\CompoundAlertProcessor\RazorStartup.cs:line 46
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at Microsoft.AspNetCore.Hosting.ConfigureBuilder.Invoke(Object instance, IApplicationBuilder builder)
at Microsoft.AspNetCore.Hosting.ConfigureBuilder.<>c__DisplayClass4_0.<Build>b__0(IApplicationBuilder builder)
at Microsoft.AspNetCore.Hosting.GenericWebHostBuilder.<>c__DisplayClass13_0.<UseStartup>b__2(IApplicationBuilder app)
at Microsoft.AspNetCore.Mvc.Filters.MiddlewareFilterBuilderStartupFilter.<>c__DisplayClass0_0.<Configure>g__MiddlewareFilterBuilder|0(IApplicationBuilder builder)
at Microsoft.AspNetCore.HostFilteringStartupFilter.<>c__DisplayClass0_0.<Configure>b__0(IApplicationBuilder app)
at Microsoft.AspNetCore.Hosting.GenericWebHostService.<StartAsync>d__31.MoveNext()
This exception was originally thrown at this call stack:
CompoundAlertProcessor.Razor.Services.RazorViewToStringRenderer.FindView(Microsoft.AspNetCore.Mvc.ActionContext, string) in RazorViewToStringRenderer.cs
CompoundAlertProcessor.Razor.Services.RazorViewToStringRenderer.RenderViewToStringAsync<TModel>(string, TModel) in RazorViewToStringRenderer.cs
Inner Exception 1:
InvalidOperationException: Unable to find view '/Views/Emails/ConfirmAccount/ConfirmAccountEmail.cshtml'. The following locations were searched:
/Views/Emails/ConfirmAccount/ConfirmAccountEmail.cshtml
I've been playing with packages and settings trying to figure out what magic Sdk.Web
is doing that Sdk.Worker
is not doing but i can't figure it out. I understand that the SDK loads and orchestrates a whole lot of things behinds the scenes to make your needed outcome easier to achieve, but i can't find a single thing that states what it is actually doing.
I'm worried that trying to use a worker with a base Sdk.Web
will have other changes that i can't foresee right now. Is it safe to use it like this? Is there any way for me to just take the pieces i need from Sdk.Web
and inject them into Sdk.Worker
?
Should i just abandon using full Razor and instead try to use something like RazorLite like described here Razor email templates in .net standard 2.1 / Core 3.1 ? I'm always wary of projects that haven't been updated in multiple years.
User contributions licensed under CC BY-SA 3.0