| File: ViewExecutor.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.Diagnostics; using System.Text; 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.ViewEngines; using Microsoft.AspNetCore.Mvc.ViewFeatures.Filters; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Mvc.ViewFeatures; /// <summary> /// Executes an <see cref="IView"/>. /// </summary> public class ViewExecutor { /// <summary> /// The default content-type header value for views, <c>text/html; charset=utf-8</c>. /// </summary> public static readonly string DefaultContentType = "text/html; charset=utf-8"; /// <summary> /// Creates a new <see cref="ViewExecutor"/>. /// </summary> /// <param name="viewOptions">The <see cref="IOptions{MvcViewOptions}"/>.</param> /// <param name="writerFactory">The <see cref="IHttpResponseStreamWriterFactory"/>.</param> /// <param name="viewEngine">The <see cref="ICompositeViewEngine"/>.</param> /// <param name="tempDataFactory">The <see cref="ITempDataDictionaryFactory"/>.</param> /// <param name="diagnosticListener">The <see cref="DiagnosticListener"/>.</param> /// <param name="modelMetadataProvider">The <see cref="IModelMetadataProvider" />.</param> public ViewExecutor( IOptions<MvcViewOptions> viewOptions, IHttpResponseStreamWriterFactory writerFactory, ICompositeViewEngine viewEngine, ITempDataDictionaryFactory tempDataFactory, DiagnosticListener diagnosticListener, IModelMetadataProvider modelMetadataProvider) : this(writerFactory, viewEngine, diagnosticListener) { ArgumentNullException.ThrowIfNull(viewOptions); ArgumentNullException.ThrowIfNull(tempDataFactory); ArgumentNullException.ThrowIfNull(diagnosticListener); ViewOptions = viewOptions.Value; TempDataFactory = tempDataFactory; ModelMetadataProvider = modelMetadataProvider; } /// <summary> /// Creates a new <see cref="ViewExecutor"/>. /// </summary> /// <param name="writerFactory">The <see cref="IHttpResponseStreamWriterFactory"/>.</param> /// <param name="viewEngine">The <see cref="ICompositeViewEngine"/>.</param> /// <param name="diagnosticListener">The <see cref="System.Diagnostics.DiagnosticListener"/>.</param> protected ViewExecutor( IHttpResponseStreamWriterFactory writerFactory, ICompositeViewEngine viewEngine, DiagnosticListener diagnosticListener) { ArgumentNullException.ThrowIfNull(writerFactory); ArgumentNullException.ThrowIfNull(viewEngine); ArgumentNullException.ThrowIfNull(diagnosticListener); WriterFactory = writerFactory; ViewEngine = viewEngine; DiagnosticListener = diagnosticListener; } /// <summary> /// Gets the <see cref="DiagnosticListener"/>. /// </summary> protected DiagnosticListener DiagnosticListener { get; } /// <summary> /// Gets the <see cref="ITempDataDictionaryFactory"/>. /// </summary> protected ITempDataDictionaryFactory? TempDataFactory { get; } /// <summary> /// Gets the default <see cref="IViewEngine"/>. /// </summary> protected IViewEngine ViewEngine { get; } /// <summary> /// Gets the <see cref="MvcViewOptions"/>. /// </summary> protected MvcViewOptions? ViewOptions { get; } /// <summary> /// Gets the <see cref="IModelMetadataProvider"/>. /// </summary> protected IModelMetadataProvider? ModelMetadataProvider { get; } /// <summary> /// Gets the <see cref="IHttpResponseStreamWriterFactory"/>. /// </summary> protected IHttpResponseStreamWriterFactory WriterFactory { get; } /// <summary> /// Executes a view asynchronously. /// </summary> /// <param name="actionContext">The <see cref="ActionContext"/> associated with the current request.</param> /// <param name="view">The <see cref="IView"/>.</param> /// <param name="viewData">The <see cref="ViewDataDictionary"/>.</param> /// <param name="tempData">The <see cref="ITempDataDictionary"/>.</param> /// <param name="contentType"> /// The content-type header value to set in the response. If <c>null</c>, /// <see cref="DefaultContentType"/> will be used. /// </param> /// <param name="statusCode"> /// The HTTP status code to set in the response. May be <c>null</c>. /// </param> /// <returns>A <see cref="Task"/> which will complete when view execution is completed.</returns> public virtual async Task ExecuteAsync( ActionContext actionContext, IView view, ViewDataDictionary viewData, ITempDataDictionary tempData, string? contentType, int? statusCode) { ArgumentNullException.ThrowIfNull(actionContext); ArgumentNullException.ThrowIfNull(view); if (ViewOptions == null) { throw new InvalidOperationException(Resources.FormatPropertyOfTypeCannotBeNull(nameof(ViewOptions), GetType().Name)); } if (TempDataFactory == null) { throw new InvalidOperationException(Resources.FormatPropertyOfTypeCannotBeNull(nameof(TempDataFactory), GetType().Name)); } if (ModelMetadataProvider == null) { throw new InvalidOperationException(Resources.FormatPropertyOfTypeCannotBeNull(nameof(ModelMetadataProvider), GetType().Name)); } if (viewData == null) { viewData = new ViewDataDictionary(ModelMetadataProvider, actionContext.ModelState); } if (tempData == null) { tempData = TempDataFactory.GetTempData(actionContext.HttpContext); } var viewContext = new ViewContext( actionContext, view, viewData, tempData, TextWriter.Null, ViewOptions.HtmlHelperOptions); await ExecuteAsync(viewContext, contentType, statusCode); } /// <summary> /// Executes a view asynchronously. /// </summary> /// <param name="viewContext">The <see cref="ViewContext"/> associated with the current request.</param> /// <param name="contentType"> /// The content-type header value to set in the response. If <c>null</c>, /// <see cref="DefaultContentType"/> will be used. /// </param> /// <param name="statusCode"> /// The HTTP status code to set in the response. May be <c>null</c>. /// </param> /// <returns>A <see cref="Task"/> which will complete when view execution is completed.</returns> protected async Task ExecuteAsync( ViewContext viewContext, string? contentType, int? statusCode) { ArgumentNullException.ThrowIfNull(viewContext); var response = viewContext.HttpContext.Response; ResponseContentTypeHelper.ResolveContentTypeAndEncoding( contentType, response.ContentType, (DefaultContentType, Encoding.UTF8), MediaType.GetEncoding, out var resolvedContentType, out var resolvedContentTypeEncoding); response.ContentType = resolvedContentType; if (statusCode != null) { response.StatusCode = statusCode.Value; } OnExecuting(viewContext); await using (var writer = WriterFactory.CreateWriter(response.Body, resolvedContentTypeEncoding)) { var view = viewContext.View; var oldWriter = viewContext.Writer; try { viewContext.Writer = writer; DiagnosticListener.BeforeView(view, viewContext); await view.RenderAsync(viewContext); DiagnosticListener.AfterView(view, viewContext); } finally { viewContext.Writer = oldWriter; } // Perf: Invoke FlushAsync to ensure any buffered content is asynchronously written to the underlying // response asynchronously. In the absence of this line, the buffer gets synchronously written to the // response as part of the Dispose which has a perf impact. await writer.FlushAsync(); } } private static void OnExecuting(ViewContext viewContext) { var viewDataValuesProvider = viewContext.HttpContext.Features.Get<IViewDataValuesProviderFeature>(); viewDataValuesProvider?.ProvideViewDataValues(viewContext.ViewData); } }