Last active
May 25, 2018 19:34
-
-
Save JeanCollas/22154325c6da339d5ac0060f91ea7d53 to your computer and use it in GitHub Desktop.
Service to render views as HTML string in ASP.NET Core
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
// Inspired by several web solutions and https://raw.githubusercontent.com/aspnet/Mvc/133dd964abb1c2a4167cf38faa38fe0319b7b931/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewResultExecutor.cs | |
public class ViewToStringRendererService: ViewExecutor | |
{ | |
private ITempDataProvider _tempDataProvider; | |
private IServiceProvider _serviceProvider; | |
public ViewToStringRendererService( | |
IOptions<MvcViewOptions> viewOptions, | |
IHttpResponseStreamWriterFactory writerFactory, | |
ICompositeViewEngine viewEngine, | |
ITempDataDictionaryFactory tempDataFactory, | |
DiagnosticSource diagnosticSource, | |
IModelMetadataProvider modelMetadataProvider, | |
ITempDataProvider tempDataProvider, | |
IServiceProvider serviceProvider) | |
: base(viewOptions, writerFactory, viewEngine, tempDataFactory, diagnosticSource, modelMetadataProvider) | |
{ | |
_tempDataProvider = tempDataProvider; | |
_serviceProvider = serviceProvider; | |
} | |
public async Task<string> RenderViewToStringAsync<TModel>(string viewName, TModel model) | |
{ | |
var context = GetActionContext(); | |
if (context == null) throw new ArgumentNullException(nameof(context)); | |
var result = new ViewResult() | |
{ | |
ViewData = new ViewDataDictionary<TModel>( | |
metadataProvider: new EmptyModelMetadataProvider(), | |
modelState: new ModelStateDictionary()) | |
{ | |
Model = model | |
}, | |
TempData = new TempDataDictionary( | |
context.HttpContext, | |
_tempDataProvider), | |
ViewName = viewName, | |
}; | |
var viewEngineResult = FindView(context, result); | |
viewEngineResult.EnsureSuccessful(originalLocations: null); | |
var view = viewEngineResult.View; | |
using (var output = new StringWriter()) | |
{ | |
var viewContext = new ViewContext( | |
context, | |
view, | |
new ViewDataDictionary<TModel>( | |
metadataProvider: new EmptyModelMetadataProvider(), | |
modelState: new ModelStateDictionary()) | |
{ | |
Model = model | |
}, | |
new TempDataDictionary( | |
context.HttpContext, | |
_tempDataProvider), | |
output, | |
new HtmlHelperOptions()); | |
await view.RenderAsync(viewContext); | |
return output.ToString(); | |
} | |
} | |
private ActionContext GetActionContext() | |
{ | |
var httpContext = new DefaultHttpContext(); | |
httpContext.RequestServices = _serviceProvider; | |
return new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); | |
} | |
/// <summary> | |
/// Attempts to find the <see cref="IView"/> associated with <paramref name="viewResult"/>. | |
/// </summary> | |
/// <param name="actionContext">The <see cref="ActionContext"/> associated with the current request.</param> | |
/// <param name="viewResult">The <see cref="ViewResult"/>.</param> | |
/// <returns>A <see cref="ViewEngineResult"/>.</returns> | |
ViewEngineResult FindView(ActionContext actionContext, ViewResult viewResult) | |
{ | |
if (actionContext == null) | |
{ | |
throw new ArgumentNullException(nameof(actionContext)); | |
} | |
if (viewResult == null) | |
{ | |
throw new ArgumentNullException(nameof(viewResult)); | |
} | |
var viewEngine = viewResult.ViewEngine ?? ViewEngine; | |
var viewName = viewResult.ViewName ?? GetActionName(actionContext); | |
var result = viewEngine.GetView(executingFilePath: null, viewPath: viewName, isMainPage: true); | |
var originalResult = result; | |
if (!result.Success) | |
{ | |
result = viewEngine.FindView(actionContext, viewName, isMainPage: true); | |
} | |
if (!result.Success) | |
{ | |
if (originalResult.SearchedLocations.Any()) | |
{ | |
if (result.SearchedLocations.Any()) | |
{ | |
// Return a new ViewEngineResult listing all searched locations. | |
var locations = new List<string>(originalResult.SearchedLocations); | |
locations.AddRange(result.SearchedLocations); | |
result = ViewEngineResult.NotFound(viewName, locations); | |
} | |
else | |
{ | |
// GetView() searched locations but FindView() did not. Use first ViewEngineResult. | |
result = originalResult; | |
} | |
} | |
} | |
if(!result.Success) | |
throw new InvalidOperationException(string.Format("Couldn't find view '{0}'", viewName)); | |
return result; | |
} | |
private const string ActionNameKey = "action"; | |
private static string GetActionName(ActionContext context) | |
{ | |
if (context == null) | |
{ | |
throw new ArgumentNullException(nameof(context)); | |
} | |
if (!context.RouteData.Values.TryGetValue(ActionNameKey, out var routeValue)) | |
{ | |
return null; | |
} | |
var actionDescriptor = context.ActionDescriptor; | |
string normalizedValue = null; | |
if (actionDescriptor.RouteValues.TryGetValue(ActionNameKey, out var value) && | |
!string.IsNullOrEmpty(value)) | |
{ | |
normalizedValue = value; | |
} | |
var stringRouteValue = routeValue?.ToString(); | |
if (string.Equals(normalizedValue, stringRouteValue, StringComparison.OrdinalIgnoreCase)) | |
{ | |
return normalizedValue; | |
} | |
return stringRouteValue; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment