| File: ViewComponentResultExecutor.cs | Web Access |
| Project: src\aspnetcore\src\Mvc\Mvc.ViewFeatures\src\Microsoft.AspNetCore.Mvc.ViewFeatures.csproj (Microsoft.AspNetCore.Mvc.ViewFeatures) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. #nullable enable using System.Text; using System.Text.Encodings.Web; using Microsoft.AspNetCore.Html; using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewFeatures.Buffers; using Microsoft.AspNetCore.Mvc.ViewFeatures.Filters; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Mvc.ViewFeatures; /// <summary> /// A <see cref="IActionResultExecutor{ViewComponentResult}"/> for <see cref="ViewComponentResult"/>. /// </summary> public partial class ViewComponentResultExecutor : IActionResultExecutor<ViewComponentResult> { private readonly HtmlEncoder _htmlEncoder; private readonly HtmlHelperOptions _htmlHelperOptions; private readonly ILogger<ViewComponentResult> _logger; private readonly IModelMetadataProvider _modelMetadataProvider; private readonly ITempDataDictionaryFactory _tempDataDictionaryFactory; private readonly IHttpResponseStreamWriterFactory _writerFactory; /// <summary> /// Initialize a new instance of <see cref="ViewComponentResultExecutor"/> /// </summary> /// <param name="mvcHelperOptions">The <see cref="IOptions{MvcViewOptions}"/>.</param> /// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param> /// <param name="htmlEncoder">The <see cref="HtmlEncoder"/>.</param> /// <param name="modelMetadataProvider">The <see cref="IModelMetadataProvider"/>.</param> /// <param name="tempDataDictionaryFactory">The <see cref="ITempDataDictionaryFactory"/>.</param> /// <param name="writerFactory">The <see cref=" IHttpResponseStreamWriterFactory"/>.</param> public ViewComponentResultExecutor( IOptions<MvcViewOptions> mvcHelperOptions, ILoggerFactory loggerFactory, HtmlEncoder htmlEncoder, IModelMetadataProvider modelMetadataProvider, ITempDataDictionaryFactory tempDataDictionaryFactory, IHttpResponseStreamWriterFactory writerFactory) { ArgumentNullException.ThrowIfNull(mvcHelperOptions); ArgumentNullException.ThrowIfNull(loggerFactory); ArgumentNullException.ThrowIfNull(htmlEncoder); ArgumentNullException.ThrowIfNull(modelMetadataProvider); ArgumentNullException.ThrowIfNull(tempDataDictionaryFactory); _htmlHelperOptions = mvcHelperOptions.Value.HtmlHelperOptions; _logger = loggerFactory.CreateLogger<ViewComponentResult>(); _htmlEncoder = htmlEncoder; _modelMetadataProvider = modelMetadataProvider; _tempDataDictionaryFactory = tempDataDictionaryFactory; _writerFactory = writerFactory; } /// <inheritdoc /> public virtual async Task ExecuteAsync(ActionContext context, ViewComponentResult result) { ArgumentNullException.ThrowIfNull(context); ArgumentNullException.ThrowIfNull(result); var response = context.HttpContext.Response; var viewData = result.ViewData; if (viewData == null) { viewData = new ViewDataDictionary(_modelMetadataProvider, context.ModelState); } var tempData = result.TempData; if (tempData == null) { tempData = _tempDataDictionaryFactory.GetTempData(context.HttpContext); } ResponseContentTypeHelper.ResolveContentTypeAndEncoding( result.ContentType, response.ContentType, (ViewExecutor.DefaultContentType, Encoding.UTF8), MediaType.GetEncoding, out var resolvedContentType, out var resolvedContentTypeEncoding); response.ContentType = resolvedContentType; if (result.StatusCode != null) { response.StatusCode = result.StatusCode.Value; } await using var writer = _writerFactory.CreateWriter(response.Body, resolvedContentTypeEncoding); var viewContext = new ViewContext( context, NullView.Instance, viewData, tempData, writer, _htmlHelperOptions); OnExecuting(viewContext); // IViewComponentHelper is stateful, we want to make sure to retrieve it every time we need it. var viewComponentHelper = context.HttpContext.RequestServices.GetRequiredService<IViewComponentHelper>(); (viewComponentHelper as IViewContextAware)?.Contextualize(viewContext); var viewComponentResult = await GetViewComponentResult(viewComponentHelper, _logger, result); if (viewComponentResult is ViewBuffer viewBuffer) { // In the ordinary case, DefaultViewComponentHelper will return an instance of ViewBuffer. We can simply // invoke WriteToAsync on it. await viewBuffer.WriteToAsync(writer, _htmlEncoder); await writer.FlushAsync(); } else { await using var bufferingStream = new FileBufferingWriteStream(); await using (var intermediateWriter = _writerFactory.CreateWriter(bufferingStream, resolvedContentTypeEncoding)) { viewComponentResult.WriteTo(intermediateWriter, _htmlEncoder); } await bufferingStream.DrainBufferAsync(response.Body); } } private static void OnExecuting(ViewContext viewContext) { var viewDataValuesProvider = viewContext.HttpContext.Features.Get<IViewDataValuesProviderFeature>(); viewDataValuesProvider?.ProvideViewDataValues(viewContext.ViewData); } private static Task<IHtmlContent> GetViewComponentResult(IViewComponentHelper viewComponentHelper, ILogger logger, ViewComponentResult result) { if (result.ViewComponentType == null && result.ViewComponentName == null) { throw new InvalidOperationException(Resources.FormatViewComponentResult_NameOrTypeMustBeSet( nameof(ViewComponentResult.ViewComponentName), nameof(ViewComponentResult.ViewComponentType))); } else if (result.ViewComponentType == null) { Log.ViewComponentResultExecuting(logger, result.ViewComponentName); return viewComponentHelper.InvokeAsync(result.ViewComponentName!, result.Arguments); } else { Log.ViewComponentResultExecuting(logger, result.ViewComponentType); return viewComponentHelper.InvokeAsync(result.ViewComponentType, result.Arguments); } } private static partial class Log { [LoggerMessage(1, LogLevel.Information, "Executing ViewComponentResult, running {ViewComponentName}.", EventName = "ViewComponentResultExecuting")] public static partial void ViewComponentResultExecuting(ILogger logger, string? viewComponentName); public static void ViewComponentResultExecuting(ILogger logger, Type viewComponentType) { ViewComponentResultExecuting(logger, viewComponentType.Name); } } }