Created
December 3, 2022 18:37
-
-
Save craigmccauley/ff090e76212df682cb8c0eddbab5bc21 to your computer and use it in GitHub Desktop.
Renders Blazor Components and Razor Views into HTML
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using System.Text.Encodings.Web; | |
using System.Threading.Tasks; | |
using Microsoft.AspNetCore.Components; | |
using Microsoft.AspNetCore.Http; | |
using Microsoft.AspNetCore.Mvc; | |
using Microsoft.AspNetCore.Mvc.Abstractions; | |
using Microsoft.AspNetCore.Mvc.ModelBinding; | |
using Microsoft.AspNetCore.Mvc.Razor; | |
using Microsoft.AspNetCore.Mvc.Rendering; | |
using Microsoft.AspNetCore.Mvc.TagHelpers; | |
using Microsoft.AspNetCore.Mvc.ViewEngines; | |
using Microsoft.AspNetCore.Mvc.ViewFeatures; | |
using Microsoft.AspNetCore.Razor.TagHelpers; | |
using Microsoft.AspNetCore.Routing; | |
namespace HtmlRendering; | |
public interface IHtmlRenderer | |
{ | |
Task<string> RenderBlazorComponentAsync<TComponent>(Dictionary<string, object> parameters) where TComponent : IComponent; | |
Task<string> RenderRazorViewToStringAsync<TModel>(string viewName, TModel model); | |
} | |
/// <summary> | |
/// Html rendering code mashed together from various stackoverflow answers and blog posts. | |
/// </summary> | |
public class HtmlRenderer : IHtmlRenderer | |
{ | |
private readonly IHttpContextAccessor httpContextAccessor; | |
private readonly IRazorViewEngine razorViewEngine; | |
private readonly ITempDataProvider tempDataProvider; | |
public HtmlRenderer( | |
IHttpContextAccessor httpContextAccessor, | |
IRazorViewEngine razorViewEngine, | |
ITempDataProvider tempDataProvider) | |
{ | |
this.httpContextAccessor = httpContextAccessor; | |
this.razorViewEngine = razorViewEngine; | |
this.tempDataProvider = tempDataProvider; | |
} | |
/// <summary> | |
/// Renders a blazor component as html. | |
/// </summary> | |
/// <typeparam name="TComponent">The type of the blazor component.</typeparam> | |
/// <param name="parameters">Parameters to pass to the blazor component.</param> | |
/// <returns>Task with string of html from rendered component.</returns> | |
public async Task<string> RenderBlazorComponentAsync<TComponent>(Dictionary<string, object> parameters) where TComponent : IComponent | |
{ | |
var componentTagHelper = new ComponentTagHelper | |
{ | |
ComponentType = typeof(TComponent), | |
RenderMode = RenderMode.Static, | |
Parameters = parameters, | |
ViewContext = new ViewContext { HttpContext = httpContextAccessor.HttpContext }, | |
}; | |
//uniqueid and tagName are arbitrary | |
var tagHelperContext = new TagHelperContext( | |
new TagHelperAttributeList(), | |
new Dictionary<object, object>(), | |
"uniqueid"); | |
var tagHelperOutput = new TagHelperOutput( | |
"tagName", | |
new TagHelperAttributeList(), | |
(useCachedResult, encoder) => Task.FromResult<TagHelperContent>(new DefaultTagHelperContent())); | |
await componentTagHelper.ProcessAsync(tagHelperContext, tagHelperOutput); | |
using var stringWriter = new StringWriter(); | |
tagHelperOutput.Content.WriteTo(stringWriter, HtmlEncoder.Default); | |
return stringWriter.ToString(); | |
} | |
/// <summary> | |
/// Renders a razor view as html. | |
/// </summary> | |
/// <typeparam name="TModel">The type of the view model.</typeparam> | |
/// <param name="viewName">The name of the view. ex. ~/Views/MyView.cshtml</param> | |
/// <param name="model">The view model.</param> | |
/// <returns>Task with string of html from rendered view.</returns> | |
/// <exception cref="InvalidOperationException"></exception> | |
public async Task<string> RenderRazorViewToStringAsync<TModel>(string viewName, TModel model) | |
{ | |
var actionContext = new ActionContext(httpContextAccessor.HttpContext, httpContextAccessor.HttpContext.GetRouteData(), new ActionDescriptor()); | |
var view = FindView(actionContext, viewName); | |
using var output = new StringWriter(); | |
var viewContext = new ViewContext( | |
actionContext, | |
view, | |
new ViewDataDictionary<TModel>( | |
metadataProvider: new EmptyModelMetadataProvider(), | |
modelState: new ModelStateDictionary()) | |
{ | |
Model = model | |
}, | |
new TempDataDictionary( | |
actionContext.HttpContext, | |
tempDataProvider), | |
output, | |
new HtmlHelperOptions()); | |
await view.RenderAsync(viewContext); | |
return output.ToString(); | |
IView FindView(ActionContext actionContext, string viewName) | |
{ | |
var getViewResult = razorViewEngine.GetView(executingFilePath: null, viewPath: viewName, isMainPage: true); | |
if (getViewResult.Success) | |
{ | |
return getViewResult.View; | |
} | |
var findViewResult = razorViewEngine.FindView(actionContext, viewName, isMainPage: true); | |
if (findViewResult.Success) | |
{ | |
return findViewResult.View; | |
} | |
var searchedLocations = getViewResult.SearchedLocations.Concat(findViewResult.SearchedLocations); | |
var errorMessage = string.Join( | |
Environment.NewLine, | |
new[] { $"Unable to find view '{viewName}'. The following locations were searched:" }.Concat(searchedLocations)); | |
throw new InvalidOperationException(errorMessage); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment