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)
{
}
}
@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