// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Diagnostics; using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Mvc.ViewFeatures { /// /// Executes an . /// public class ViewExecutor { /// /// The default content-type header value for views, text/html; charset=utf-8. /// public static readonly string DefaultContentType = "text/html; charset=utf-8"; /// /// Creates a new . /// /// The . /// The . /// The . /// The . /// The . /// The . public ViewExecutor( IOptions viewOptions, IHttpResponseStreamWriterFactory writerFactory, ICompositeViewEngine viewEngine, ITempDataDictionaryFactory tempDataFactory, DiagnosticSource diagnosticSource, IModelMetadataProvider modelMetadataProvider) : this(writerFactory, viewEngine, diagnosticSource) { if (viewOptions == null) { throw new ArgumentNullException(nameof(viewOptions)); } if (tempDataFactory == null) { throw new ArgumentNullException(nameof(tempDataFactory)); } if (diagnosticSource == null) { throw new ArgumentNullException(nameof(diagnosticSource)); } ViewOptions = viewOptions.Value; TempDataFactory = tempDataFactory; ModelMetadataProvider = modelMetadataProvider; } /// /// Creates a new . /// /// The . /// The . /// The . protected ViewExecutor( IHttpResponseStreamWriterFactory writerFactory, ICompositeViewEngine viewEngine, DiagnosticSource diagnosticSource) { if (writerFactory == null) { throw new ArgumentNullException(nameof(writerFactory)); } if (viewEngine == null) { throw new ArgumentNullException(nameof(viewEngine)); } if (diagnosticSource == null) { throw new ArgumentNullException(nameof(diagnosticSource)); } WriterFactory = writerFactory; ViewEngine = viewEngine; DiagnosticSource = diagnosticSource; } /// /// Gets the . /// protected DiagnosticSource DiagnosticSource { get; } /// /// Gets the . /// protected ITempDataDictionaryFactory TempDataFactory { get; } /// /// Gets the default . /// protected IViewEngine ViewEngine { get; } /// /// Gets the . /// protected MvcViewOptions ViewOptions { get; } /// /// Gets the . /// protected IModelMetadataProvider ModelMetadataProvider { get; } /// /// Gets the . /// protected IHttpResponseStreamWriterFactory WriterFactory { get; } /// /// Executes a view asynchronously. /// /// The associated with the current request. /// The . /// The . /// The . /// /// The content-type header value to set in the response. If null, /// will be used. /// /// /// The HTTP status code to set in the response. May be null. /// /// A which will complete when view execution is completed. public virtual async Task ExecuteAsync( ActionContext actionContext, IView view, ViewDataDictionary viewData, ITempDataDictionary tempData, string contentType, int? statusCode) { if (actionContext == null) { throw new ArgumentNullException(nameof(actionContext)); } if (view == null) { throw new ArgumentNullException(nameof(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); } /// /// Executes a view asynchronously. /// /// The associated with the current request. /// /// The content-type header value to set in the response. If null, /// will be used. /// /// /// The HTTP status code to set in the response. May be null. /// /// A which will complete when view execution is completed. protected async Task ExecuteAsync( ViewContext viewContext, string contentType, int? statusCode) { if (viewContext == null) { throw new ArgumentNullException(nameof(viewContext)); } var response = viewContext.HttpContext.Response; ResponseContentTypeHelper.ResolveContentTypeAndEncoding( contentType, response.ContentType, DefaultContentType, out var resolvedContentType, out var resolvedContentTypeEncoding); response.ContentType = resolvedContentType; if (statusCode != null) { response.StatusCode = statusCode.Value; } OnExecuting(viewContext); using (var writer = WriterFactory.CreateWriter(response.Body, resolvedContentTypeEncoding)) { var view = viewContext.View; var oldWriter = viewContext.Writer; try { viewContext.Writer = writer; DiagnosticSource.BeforeView(view, viewContext); await view.RenderAsync(viewContext); DiagnosticSource.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 void OnExecuting(ViewContext viewContext) { var viewDataValuesProvider = viewContext.HttpContext.Features.Get(); if (viewDataValuesProvider != null) { viewDataValuesProvider.ProvideViewDataValues(viewContext.ViewData); } } } }