My colleague and I were wondering how we could use our existing, predefined Razor views in a console application that is ran to do some background job. Typical use case: automated regular email sending. We were searching for the solution but all we found were examples of this case in dotnet core 2.1. So we had to do some investigation on how to upgrade to netcoreapp3.1.
The example project was one of the aspnet entropy experimental repository: https://github.com/aspnet/Entropy/tree/master/samples/Mvc.RenderViewToString
You can find the source of the updated project in my repo: https://github.com/codernr/dotnet-mvc-renderviewtostring
Long story short, you can see all the changes needed to be made in this commit. But let's see the inside:
As the documentation says, A project that includes Razor views or Razor Pages must use the Microsoft.NET.Sdk.Razor SDK. In addition you have to set AddRazorSupportForMvc
property to true
and include the FrameworkReference
for the 1shared framework. The whole .csproj file looks like this:
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<OutputType>exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
</Project>
The way you can configure RazorViewEngine
has changed since dotnet core 2.1, now you can't set the FileProvider
through RazorViewEngineOptions
. This is set through IWebHostEnvironment.ContentRootFileProvider
, where IWebHostEnvironment
is the dependency that replaces the old IHostingEnvironment
(you can check the diff here). Since the implementing classes are internal, you have to create a custom implementation and add it to the service collection.
There are two other changes in the DI configuration that are needed for the RazorViewEngine
to work:
DiagnosticListener
services.AddMvc()
it is enough to use services.AddMvcCore().AddRazorViewEngine()
so you can reduce the registered servicesSee the whole dependency injection container setup here:
private static void ConfigureDefaultServices(IServiceCollection services, string customApplicationBasePath)
{
string applicationName;
string rootPath;
if (!string.IsNullOrEmpty(customApplicationBasePath))
{
applicationName = Path.GetFileName(customApplicationBasePath);
rootPath = customApplicationBasePath;
}
else
{
applicationName = Assembly.GetEntryAssembly().GetName().Name;
rootPath = Directory.GetCurrentDirectory();
}
var fileProvider = new PhysicalFileProvider(rootPath);
var environment = new CustomHostingEnvironment
{
WebRootFileProvider = fileProvider,
ApplicationName = applicationName,
ContentRootPath = rootPath,
WebRootPath = rootPath,
EnvironmentName = "DEVELOPMENT",
ContentRootFileProvider = fileProvider
};
services.AddSingleton<IWebHostEnvironment>(environment);
var diagnosticSource = new DiagnosticListener("Microsoft.AspNetCore");
services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
services.AddSingleton<DiagnosticSource>(diagnosticSource);
services.AddSingleton<DiagnosticListener>(diagnosticSource);
services.AddLogging();
services.AddMvcCore()
.AddRazorViewEngine();
services.AddTransient<RazorViewToStringRenderer>();
}
Happy email sending!