Skip to content

Instantly share code, notes, and snippets.

@wmeints
Last active August 27, 2020 03:20
Show Gist options
  • Save wmeints/c547209b69128e4ee537cb8f35c3a6dc to your computer and use it in GitHub Desktop.
Save wmeints/c547209b69128e4ee537cb8f35c3a6dc to your computer and use it in GitHub Desktop.
A template service based on the ASP.NET Core razor engine
/// <summary>
/// Renders email content based on razor templates
/// </summary>
public interface ITemplateService
{
/// <summary>
/// Renders a template given the provided view model
/// </summary>
/// <typeparam name="TViewModel"></typeparam>
/// <param name="filename">Filename of the template to render</param>
/// <param name="viewModel">View model to use for rendering the template</param>
/// <returns>Returns the rendered template content</returns>
Task<string> RenderTemplateAsync<TViewModel>(string filename, TViewModel viewModel);
}
public class TemplateService: ITemplateService
{
private IRazorViewEngine _viewEngine;
private readonly IServiceProvider _serviceProvider;
private readonly ITempDataProvider _tempDataProvider;
public TemplateService(IRazorViewEngine viewEngine, IServiceProvider serviceProvider, ITempDataProvider tempDataProvider)
{
_viewEngine = viewEngine;
_serviceProvider = serviceProvider;
_tempDataProvider = tempDataProvider;
}
public async Task<string> RenderTemplateAsync<TViewModel>(string filename, TViewModel viewModel)
{
var httpContext = new DefaultHttpContext
{
RequestServices = _serviceProvider
};
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
using (var outputWriter = new StringWriter())
{
var viewResult = _viewEngine.FindView(actionContext, filename, false);
var viewDictionary = new ViewDataDictionary<TViewModel>(new EmptyModelMetadataProvider(), new ModelStateDictionary())
{
Model = viewModel
};
var tempDataDictionary = new TempDataDictionary(httpContext, _tempDataProvider);
if (!viewResult.Success)
{
throw new TemplateServiceException($"Failed to render template {filename} because it was not found.");
}
try
{
var viewContext = new ViewContext(actionContext, viewResult.View, viewDictionary,
tempDataDictionary, outputWriter, new HtmlHelperOptions());
await viewResult.View.RenderAsync(viewContext);
}
catch (Exception ex)
{
throw new TemplateServiceException("Failed to render template due to a razor engine failure", ex);
}
return outputWriter.ToString();
}
}
}
/// <summary>
/// Gets thrown when the template service was unable to render the template
/// </summary>
public class TemplateServiceException: Exception
{
/// <summary>
/// Initializes a new instance of <see cref="TemplateServiceException"/>
/// </summary>
/// <param name="message"></param>
public TemplateServiceException(string message) : base(message)
{
}
/// <summary>
/// Initializes a new instance of <see cref="TemplateServiceException"/>
/// </summary>
/// <param name="message"></param>
/// <param name="innerException"></param>
public TemplateServiceException(string message, Exception innerException) : base(message, innerException)
{
}
}
@satbav
Copy link

satbav commented Oct 31, 2017

For anyone that comes across this and is wondering why their models are null in the view, causing exceptions, just add this line in, after line 26 in TemplateService.cs:

viewDictionary.Model = viewModel;

@utarn
Copy link

utarn commented Nov 9, 2017

Thanks. It's working.

@Franklin89
Copy link

Franklin89 commented Feb 23, 2018

This works great if I run it with dotnet run or inside of visual studio. But if I publish the app I get an error that the "Template" was not found. Using .net core 2.0 the views will be pre-compiled. Do I need to copy the files separately?

[Edit]
Okay, so it seems that it will not work with PreCompiled views. Had to add the following to my csproj

<MvcRazorCompileOnPublish>false</MvcRazorCompileOnPublish>

@luksfk
Copy link

luksfk commented Nov 5, 2018

Nice code, worked perfectly. I just changed a little to use a list of viewmodels. Thanks

@PeterDyrholm
Copy link

In rendering the view I use Viewbag and javascript in the view. Is that something that can be used as well and how do I specify those?
any thoughts? ideas?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment