Minor cleanup/refactor of ViewResult
- Make ViewExecutor a service - Add facades for ViewResult/PartialViewResult - Add eventing for ViewFound/NotFound in PartialViewResult - Add eventing around view execution - Cleanup of some various eventing & our tests code
This commit is contained in:
parent
eef6c3883a
commit
306776ff63
|
|
@ -636,7 +636,7 @@ namespace Microsoft.AspNet.Mvc.Controllers
|
|||
{
|
||||
_telemetry.WriteTelemetry(
|
||||
"Microsoft.AspNet.Mvc.AfterActionMethod",
|
||||
new { actionContext = ActionContext, result });
|
||||
new { actionContext = ActionContext, result = result });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -789,7 +789,7 @@ namespace Microsoft.AspNet.Mvc.Controllers
|
|||
{
|
||||
_telemetry.WriteTelemetry(
|
||||
"Microsoft.AspNet.Mvc.BeforeActionResult",
|
||||
new { actionContext = ActionContext, result });
|
||||
new { actionContext = ActionContext, result = result });
|
||||
}
|
||||
|
||||
try
|
||||
|
|
@ -802,7 +802,7 @@ namespace Microsoft.AspNet.Mvc.Controllers
|
|||
{
|
||||
_telemetry.WriteTelemetry(
|
||||
"Microsoft.AspNet.Mvc.AfterActionResult",
|
||||
new { actionContext = ActionContext, result });
|
||||
new { actionContext = ActionContext, result = result });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,6 +88,8 @@ namespace Microsoft.Framework.DependencyInjection
|
|||
// View Engine and related infrastructure
|
||||
//
|
||||
services.TryAddSingleton<ICompositeViewEngine, CompositeViewEngine>();
|
||||
services.TryAddSingleton<ViewResultExecutor>();
|
||||
services.TryAddSingleton<PartialViewResultExecutor>();
|
||||
|
||||
// Support for activating ViewDataDictionary
|
||||
services.TryAddEnumerable(
|
||||
|
|
|
|||
|
|
@ -3,12 +3,10 @@
|
|||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
using Microsoft.AspNet.Mvc.ViewEngines;
|
||||
using Microsoft.AspNet.Mvc.ViewFeatures;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
|
|
@ -60,41 +58,16 @@ namespace Microsoft.AspNet.Mvc
|
|||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var viewEngine = ViewEngine ??
|
||||
context.HttpContext.RequestServices.GetRequiredService<ICompositeViewEngine>();
|
||||
var services = context.HttpContext.RequestServices;
|
||||
var executor = services.GetRequiredService<PartialViewResultExecutor>();
|
||||
|
||||
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<PartialViewResult>>();
|
||||
|
||||
var options = context.HttpContext.RequestServices.GetRequiredService<IOptions<MvcViewOptions>>();
|
||||
|
||||
var viewName = ViewName ?? context.ActionDescriptor.Name;
|
||||
var viewEngineResult = viewEngine.FindPartialView(context, viewName);
|
||||
if (!viewEngineResult.Success)
|
||||
{
|
||||
logger.LogError(
|
||||
"The partial view '{PartialViewName}' was not found. Searched locations: {SearchedViewLocations}",
|
||||
viewName,
|
||||
viewEngineResult.SearchedLocations);
|
||||
}
|
||||
|
||||
var view = viewEngineResult.EnsureSuccessful().View;
|
||||
|
||||
logger.LogVerbose("The partial view '{PartialViewName}' was found.", viewName);
|
||||
|
||||
if (StatusCode != null)
|
||||
{
|
||||
context.HttpContext.Response.StatusCode = StatusCode.Value;
|
||||
}
|
||||
var result = executor.FindView(context, this);
|
||||
result.EnsureSuccessful();
|
||||
|
||||
var view = result.View;
|
||||
using (view as IDisposable)
|
||||
{
|
||||
await ViewExecutor.ExecuteAsync(
|
||||
view,
|
||||
context,
|
||||
ViewData,
|
||||
TempData,
|
||||
options.Value.HtmlHelperOptions,
|
||||
ContentType);
|
||||
await executor.ExecuteAsync(context, view, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,143 @@
|
|||
// 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.Tracing;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Mvc.ViewEngines;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||
{
|
||||
/// <summary>
|
||||
/// Finds and executes an <see cref="IView"/> for a <see cref="PartialViewResult"/>.
|
||||
/// </summary>
|
||||
public class PartialViewResultExecutor : ViewExecutor
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="PartialViewResultExecutor"/>.
|
||||
/// </summary>
|
||||
/// <param name="viewOptions">The <see cref="IOptions{MvcViewOptions}"/>.</param>
|
||||
/// <param name="viewEngine">The <see cref="ICompositeViewEngine"/>.</param>
|
||||
/// <param name="telemetry">The <see cref="TelemetrySource"/>.</param>
|
||||
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
|
||||
public PartialViewResultExecutor(
|
||||
IOptions<MvcViewOptions> viewOptions,
|
||||
ICompositeViewEngine viewEngine,
|
||||
TelemetrySource telemetry,
|
||||
ILoggerFactory loggerFactory)
|
||||
: base(viewOptions, viewEngine, telemetry)
|
||||
{
|
||||
if (loggerFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(loggerFactory));
|
||||
}
|
||||
|
||||
Logger = loggerFactory.CreateLogger<PartialViewResultExecutor>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="ILogger"/>.
|
||||
/// </summary>
|
||||
protected ILogger Logger { get; }
|
||||
|
||||
/// <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="PartialViewResult"/>.</param>
|
||||
/// <returns>A <see cref="ViewEngineResult"/>.</returns>
|
||||
public virtual ViewEngineResult FindView(ActionContext actionContext, PartialViewResult 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 ?? actionContext.ActionDescriptor.Name;
|
||||
|
||||
var result = viewEngine.FindPartialView(actionContext, viewName);
|
||||
if (result.Success)
|
||||
{
|
||||
if (Telemetry.IsEnabled("Microsoft.AspNet.Mvc.ViewFound"))
|
||||
{
|
||||
Telemetry.WriteTelemetry(
|
||||
"Microsoft.AspNet.Mvc.ViewFound",
|
||||
new
|
||||
{
|
||||
actionContext = actionContext,
|
||||
isPartial = true,
|
||||
result = viewResult,
|
||||
viewName = viewName,
|
||||
view = result.View,
|
||||
});
|
||||
}
|
||||
|
||||
Logger.LogVerbose("The partial view '{PartialViewName}' was found.", viewName);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Telemetry.IsEnabled("Microsoft.AspNet.Mvc.ViewNotFound"))
|
||||
{
|
||||
Telemetry.WriteTelemetry(
|
||||
"Microsoft.AspNet.Mvc.ViewNotFound",
|
||||
new
|
||||
{
|
||||
actionContext = actionContext,
|
||||
isPartial = true,
|
||||
result = viewResult,
|
||||
viewName = viewName,
|
||||
searchedLocations = result.SearchedLocations
|
||||
});
|
||||
}
|
||||
|
||||
Logger.LogError(
|
||||
"The partial view '{PartialViewName}' was not found. Searched locations: {SearchedViewLocations}",
|
||||
viewName,
|
||||
result.SearchedLocations);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the <see cref="IView"/> 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="viewResult">The <see cref="PartialViewResult"/>.</param>
|
||||
/// <returns>A <see cref="Task"/> which will complete when view execution is completed.</returns>
|
||||
public virtual Task ExecuteAsync(ActionContext actionContext, IView view, PartialViewResult viewResult)
|
||||
{
|
||||
if (actionContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(actionContext));
|
||||
}
|
||||
|
||||
if (view == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(view));
|
||||
}
|
||||
|
||||
if (viewResult == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(viewResult));
|
||||
}
|
||||
|
||||
return ExecuteAsync(
|
||||
actionContext,
|
||||
view,
|
||||
viewResult.ViewData,
|
||||
viewResult.TempData,
|
||||
viewResult.ContentType,
|
||||
viewResult.StatusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,50 +2,107 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Tracing;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Mvc.Rendering;
|
||||
using Microsoft.AspNet.Mvc.ViewEngines;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility type for rendering a <see cref="IView"/> to the response.
|
||||
/// Executes an <see cref="IView"/>.
|
||||
/// </summary>
|
||||
public static class ViewExecutor
|
||||
public class ViewExecutor
|
||||
{
|
||||
/// <summary>
|
||||
/// The default content-type header value for views, <c>text/html; charset=utf8</c>.
|
||||
/// </summary>
|
||||
public static readonly MediaTypeHeaderValue DefaultContentType = new MediaTypeHeaderValue("text/html")
|
||||
{
|
||||
Encoding = Encoding.UTF8
|
||||
}.CopyAsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously renders the specified <paramref name="view"/> to the response body.
|
||||
/// Creates a new <see cref="ViewExecutor"/>.
|
||||
/// </summary>
|
||||
/// <param name="view">The <see cref="IView"/> to render.</param>
|
||||
/// <param name="actionContext">The <see cref="ActionContext"/> for the current executing action.</param>
|
||||
/// <param name="viewData">The <see cref="ViewDataDictionary"/> for the view being rendered.</param>
|
||||
/// <param name="tempData">The <see cref="ITempDataDictionary"/> for the view being rendered.</param>
|
||||
/// <returns>A <see cref="Task"/> that represents the asynchronous rendering.</returns>
|
||||
public static async Task ExecuteAsync(
|
||||
IView view,
|
||||
ActionContext actionContext,
|
||||
ViewDataDictionary viewData,
|
||||
ITempDataDictionary tempData,
|
||||
HtmlHelperOptions htmlHelperOptions,
|
||||
MediaTypeHeaderValue contentType)
|
||||
/// <param name="viewOptions">The <see cref="IOptions{MvcViewOptions}"/>.</param>
|
||||
/// <param name="viewEngine">The <see cref="ICompositeViewEngine"/>.</param>
|
||||
/// <param name="telemetry">The <see cref="TelemetrySource"/>.</param>
|
||||
public ViewExecutor(
|
||||
IOptions<MvcViewOptions> viewOptions,
|
||||
ICompositeViewEngine viewEngine,
|
||||
TelemetrySource telemetry)
|
||||
{
|
||||
if (view == null)
|
||||
if (viewOptions == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(view));
|
||||
throw new ArgumentNullException(nameof(viewOptions));
|
||||
}
|
||||
|
||||
if (viewEngine == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(viewEngine));
|
||||
}
|
||||
|
||||
if (telemetry == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(telemetry));
|
||||
}
|
||||
|
||||
ViewOptions = viewOptions.Value;
|
||||
ViewEngine = viewEngine;
|
||||
Telemetry = telemetry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="TelemetrySource"/>.
|
||||
/// </summary>
|
||||
protected TelemetrySource Telemetry { 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>
|
||||
/// 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,
|
||||
MediaTypeHeaderValue contentType,
|
||||
int? statusCode)
|
||||
{
|
||||
if (actionContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(actionContext));
|
||||
}
|
||||
|
||||
if (view == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(view));
|
||||
}
|
||||
|
||||
if (viewData == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(viewData));
|
||||
|
|
@ -56,11 +113,6 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
throw new ArgumentNullException(nameof(tempData));
|
||||
}
|
||||
|
||||
if (htmlHelperOptions == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(htmlHelperOptions));
|
||||
}
|
||||
|
||||
var response = actionContext.HttpContext.Response;
|
||||
|
||||
if (contentType != null && contentType.Encoding == null)
|
||||
|
|
@ -76,7 +128,13 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
// 3. ViewExecutor.DefaultContentType (sensible default)
|
||||
response.ContentType = contentType?.ToString() ?? response.ContentType ?? DefaultContentType.ToString();
|
||||
|
||||
using (var writer = new HttpResponseStreamWriter(response.Body, contentType?.Encoding ?? DefaultContentType.Encoding))
|
||||
if (statusCode != null)
|
||||
{
|
||||
response.StatusCode = statusCode.Value;
|
||||
}
|
||||
|
||||
var encoding = contentType?.Encoding ?? DefaultContentType.Encoding;
|
||||
using (var writer = new HttpResponseStreamWriter(response.Body, encoding))
|
||||
{
|
||||
var viewContext = new ViewContext(
|
||||
actionContext,
|
||||
|
|
@ -84,12 +142,27 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
viewData,
|
||||
tempData,
|
||||
writer,
|
||||
htmlHelperOptions);
|
||||
ViewOptions.HtmlHelperOptions);
|
||||
|
||||
if (Telemetry.IsEnabled("Microsoft.AspNet.Mvc.BeforeView"))
|
||||
{
|
||||
Telemetry.WriteTelemetry(
|
||||
"Microsoft.AspNet.Mvc.BeforeView",
|
||||
new { view = view, viewContext = viewContext, });
|
||||
}
|
||||
|
||||
await view.RenderAsync(viewContext);
|
||||
// Invoke FlushAsync to ensure any buffered content is asynchronously written to the underlying
|
||||
// response. In the absence of this line, the buffer gets synchronously written to the response
|
||||
// as part of the Dispose which has a perf impact.
|
||||
|
||||
if (Telemetry.IsEnabled("Microsoft.AspNet.Mvc.AfterView"))
|
||||
{
|
||||
Telemetry.WriteTelemetry(
|
||||
"Microsoft.AspNet.Mvc.AfterView",
|
||||
new { view = view, viewContext = viewContext, });
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,143 @@
|
|||
// 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.Tracing;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Mvc.ViewEngines;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||
{
|
||||
/// <summary>
|
||||
/// Finds and executes an <see cref="IView"/> for a <see cref="ViewResult"/>.
|
||||
/// </summary>
|
||||
public class ViewResultExecutor : ViewExecutor
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ViewResultExecutor"/>.
|
||||
/// </summary>
|
||||
/// <param name="viewOptions">The <see cref="IOptions{MvcViewOptions}"/>.</param>
|
||||
/// <param name="viewEngine">The <see cref="ICompositeViewEngine"/>.</param>
|
||||
/// <param name="telemetry">The <see cref="TelemetrySource"/>.</param>
|
||||
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
|
||||
public ViewResultExecutor(
|
||||
IOptions<MvcViewOptions> viewOptions,
|
||||
ICompositeViewEngine viewEngine,
|
||||
TelemetrySource telemetry,
|
||||
ILoggerFactory loggerFactory)
|
||||
: base(viewOptions, viewEngine, telemetry)
|
||||
{
|
||||
if (loggerFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(loggerFactory));
|
||||
}
|
||||
|
||||
Logger = loggerFactory.CreateLogger<ViewResultExecutor>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="ILogger"/>.
|
||||
/// </summary>
|
||||
protected ILogger Logger { get; }
|
||||
|
||||
/// <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>
|
||||
public virtual 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 ?? actionContext.ActionDescriptor.Name;
|
||||
|
||||
var result = viewEngine.FindView(actionContext, viewName);
|
||||
if (result.Success)
|
||||
{
|
||||
if (Telemetry.IsEnabled("Microsoft.AspNet.Mvc.ViewFound"))
|
||||
{
|
||||
Telemetry.WriteTelemetry(
|
||||
"Microsoft.AspNet.Mvc.ViewFound",
|
||||
new
|
||||
{
|
||||
actionContext = actionContext,
|
||||
isPartial = false,
|
||||
result = viewResult,
|
||||
viewName = viewName,
|
||||
view = result.View,
|
||||
});
|
||||
}
|
||||
|
||||
Logger.LogVerbose("The view '{ViewName}' was found.", viewName);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Telemetry.IsEnabled("Microsoft.AspNet.Mvc.ViewNotFound"))
|
||||
{
|
||||
Telemetry.WriteTelemetry(
|
||||
"Microsoft.AspNet.Mvc.ViewNotFound",
|
||||
new
|
||||
{
|
||||
actionContext = actionContext,
|
||||
isPartial = false,
|
||||
result = viewResult,
|
||||
viewName = viewName,
|
||||
searchedLocations = result.SearchedLocations
|
||||
});
|
||||
}
|
||||
|
||||
Logger.LogError(
|
||||
"The view '{ViewName}' was not found. Searched locations: {SearchedViewLocations}",
|
||||
viewName,
|
||||
result.SearchedLocations);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the <see cref="IView"/> 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="viewResult">The <see cref="ViewResult"/>.</param>
|
||||
/// <returns>A <see cref="Task"/> which will complete when view execution is completed.</returns>
|
||||
public virtual Task ExecuteAsync(ActionContext actionContext, IView view, ViewResult viewResult)
|
||||
{
|
||||
if (actionContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(actionContext));
|
||||
}
|
||||
|
||||
if (view == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(view));
|
||||
}
|
||||
|
||||
if (viewResult == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(viewResult));
|
||||
}
|
||||
|
||||
return ExecuteAsync(
|
||||
actionContext,
|
||||
view,
|
||||
viewResult.ViewData,
|
||||
viewResult.TempData,
|
||||
viewResult.ContentType,
|
||||
viewResult.StatusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,13 +2,10 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Tracing;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Mvc.ViewEngines;
|
||||
using Microsoft.AspNet.Mvc.ViewFeatures;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
|
|
@ -62,60 +59,15 @@ namespace Microsoft.AspNet.Mvc
|
|||
}
|
||||
|
||||
var services = context.HttpContext.RequestServices;
|
||||
var viewEngine = ViewEngine ?? services.GetRequiredService<ICompositeViewEngine>();
|
||||
var executor = services.GetRequiredService<ViewResultExecutor>();
|
||||
|
||||
var logger = services.GetRequiredService<ILogger<ViewResult>>();
|
||||
var telemetry = services.GetRequiredService<TelemetrySource>();
|
||||
|
||||
var options = services.GetRequiredService<IOptions<MvcViewOptions>>();
|
||||
|
||||
var viewName = ViewName ?? context.ActionDescriptor.Name;
|
||||
var viewEngineResult = viewEngine.FindView(context, viewName);
|
||||
if (!viewEngineResult.Success)
|
||||
{
|
||||
if (telemetry.IsEnabled("Microsoft.AspNet.Mvc.ViewResultViewNotFound"))
|
||||
{
|
||||
telemetry.WriteTelemetry(
|
||||
"Microsoft.AspNet.Mvc.ViewResultViewNotFound",
|
||||
new
|
||||
{
|
||||
actionContext = context,
|
||||
result = this,
|
||||
viewName = viewName,
|
||||
searchedLocations = viewEngineResult.SearchedLocations
|
||||
});
|
||||
}
|
||||
|
||||
logger.LogError(
|
||||
"The view '{ViewName}' was not found. Searched locations: {SearchedViewLocations}",
|
||||
viewName,
|
||||
viewEngineResult.SearchedLocations);
|
||||
}
|
||||
|
||||
var view = viewEngineResult.EnsureSuccessful().View;
|
||||
if (telemetry.IsEnabled("Microsoft.AspNet.Mvc.ViewResultViewFound"))
|
||||
{
|
||||
telemetry.WriteTelemetry(
|
||||
"Microsoft.AspNet.Mvc.ViewResultViewFound",
|
||||
new { actionContext = context, result = this, viewName, view = view });
|
||||
}
|
||||
|
||||
logger.LogVerbose("The view '{ViewName}' was found.", viewName);
|
||||
|
||||
if (StatusCode != null)
|
||||
{
|
||||
context.HttpContext.Response.StatusCode = StatusCode.Value;
|
||||
}
|
||||
var result = executor.FindView(context, this);
|
||||
result.EnsureSuccessful();
|
||||
|
||||
var view = result.View;
|
||||
using (view as IDisposable)
|
||||
{
|
||||
await ViewExecutor.ExecuteAsync(
|
||||
view,
|
||||
context,
|
||||
ViewData,
|
||||
TempData,
|
||||
options.Value.HtmlHelperOptions,
|
||||
ContentType);
|
||||
await executor.ExecuteAsync(context, view, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
public interface IProxyViewContext
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -51,56 +51,174 @@ namespace Microsoft.AspNet.Mvc
|
|||
};
|
||||
}
|
||||
|
||||
public class OnViewResultViewFoundEventData
|
||||
public class OnBeforeActionMethodEventData
|
||||
{
|
||||
public IProxyActionContext ActionContext { get; set; }
|
||||
public IReadOnlyDictionary<string, object> Arguments { get; set; }
|
||||
}
|
||||
|
||||
public OnBeforeActionMethodEventData BeforeActionMethod { get; set; }
|
||||
|
||||
[TelemetryName("Microsoft.AspNet.Mvc.BeforeActionMethod")]
|
||||
public virtual void OnBeforeActionMethod(
|
||||
IProxyActionContext actionContext,
|
||||
IReadOnlyDictionary<string, object> arguments)
|
||||
{
|
||||
BeforeActionMethod = new OnBeforeActionMethodEventData()
|
||||
{
|
||||
ActionContext = actionContext,
|
||||
Arguments = arguments,
|
||||
};
|
||||
}
|
||||
|
||||
public class OnAfterActionMethodEventData
|
||||
{
|
||||
public IProxyActionContext ActionContext { get; set; }
|
||||
public IProxyActionResult Result { get; set; }
|
||||
}
|
||||
|
||||
public OnAfterActionMethodEventData AfterActionMethod { get; set; }
|
||||
|
||||
[TelemetryName("Microsoft.AspNet.Mvc.AfterActionMethod")]
|
||||
public virtual void OnAfterActionMethod(
|
||||
IProxyActionContext actionContext,
|
||||
IProxyActionResult result)
|
||||
{
|
||||
AfterActionMethod = new OnAfterActionMethodEventData()
|
||||
{
|
||||
ActionContext = actionContext,
|
||||
Result = result,
|
||||
};
|
||||
}
|
||||
|
||||
public class OnBeforeActionResultEventData
|
||||
{
|
||||
public IProxyActionContext ActionContext { get; set; }
|
||||
public IProxyActionResult Result { get; set; }
|
||||
}
|
||||
|
||||
public OnBeforeActionResultEventData BeforeActionResult { get; set; }
|
||||
|
||||
[TelemetryName("Microsoft.AspNet.Mvc.BeforeActionResult")]
|
||||
public virtual void OnBeforeActionResult(IProxyActionContext actionContext, IProxyActionResult result)
|
||||
{
|
||||
BeforeActionResult = new OnBeforeActionResultEventData()
|
||||
{
|
||||
ActionContext = actionContext,
|
||||
Result = result,
|
||||
};
|
||||
}
|
||||
|
||||
public class OnAfterActionResultEventData
|
||||
{
|
||||
public IProxyActionContext ActionContext { get; set; }
|
||||
public IProxyActionResult Result { get; set; }
|
||||
}
|
||||
|
||||
public OnAfterActionResultEventData AfterActionResult { get; set; }
|
||||
|
||||
[TelemetryName("Microsoft.AspNet.Mvc.AfterActionResult")]
|
||||
public virtual void OnAfterActionResult(IProxyActionContext actionContext, IProxyActionResult result)
|
||||
{
|
||||
AfterActionResult = new OnAfterActionResultEventData()
|
||||
{
|
||||
ActionContext = actionContext,
|
||||
Result = result,
|
||||
};
|
||||
}
|
||||
|
||||
public class OnViewFoundEventData
|
||||
{
|
||||
public IProxyActionContext ActionContext { get; set; }
|
||||
public bool IsPartial { get; set; }
|
||||
public IProxyActionResult Result { get; set; }
|
||||
public string ViewName { get; set; }
|
||||
public IProxyView View { get; set; }
|
||||
}
|
||||
|
||||
public OnViewResultViewFoundEventData ViewResultViewFound { get; set; }
|
||||
public OnViewFoundEventData ViewFound { get; set; }
|
||||
|
||||
[TelemetryName("Microsoft.AspNet.Mvc.ViewResultViewFound")]
|
||||
public virtual void OnViewResultViewFound(
|
||||
[TelemetryName("Microsoft.AspNet.Mvc.ViewFound")]
|
||||
public virtual void OnViewFound(
|
||||
IProxyActionContext actionContext,
|
||||
bool isPartial,
|
||||
IProxyActionResult result,
|
||||
string viewName,
|
||||
IProxyView view)
|
||||
{
|
||||
ViewResultViewFound = new OnViewResultViewFoundEventData()
|
||||
ViewFound = new OnViewFoundEventData()
|
||||
{
|
||||
ActionContext = actionContext,
|
||||
IsPartial = isPartial,
|
||||
Result = result,
|
||||
ViewName = viewName,
|
||||
View = view,
|
||||
};
|
||||
}
|
||||
|
||||
public class OnViewResultViewNotFoundEventData
|
||||
public class OnViewNotFoundEventData
|
||||
{
|
||||
public IProxyActionContext ActionContext { get; set; }
|
||||
public bool IsPartial { get; set; }
|
||||
public IProxyActionResult Result { get; set; }
|
||||
public string ViewName { get; set; }
|
||||
public IEnumerable<string> SearchedLocations { get; set; }
|
||||
}
|
||||
|
||||
public OnViewResultViewNotFoundEventData ViewResultViewNotFound { get; set; }
|
||||
public OnViewNotFoundEventData ViewNotFound { get; set; }
|
||||
|
||||
[TelemetryName("Microsoft.AspNet.Mvc.ViewResultViewNotFound")]
|
||||
public virtual void OnViewResultViewNotFound(
|
||||
[TelemetryName("Microsoft.AspNet.Mvc.ViewNotFound")]
|
||||
public virtual void OnViewNotFound(
|
||||
IProxyActionContext actionContext,
|
||||
bool isPartial,
|
||||
IProxyActionResult result,
|
||||
string viewName,
|
||||
IEnumerable<string> searchedLocations)
|
||||
{
|
||||
ViewResultViewNotFound = new OnViewResultViewNotFoundEventData()
|
||||
ViewNotFound = new OnViewNotFoundEventData()
|
||||
{
|
||||
ActionContext = actionContext,
|
||||
IsPartial = isPartial,
|
||||
Result = result,
|
||||
ViewName = viewName,
|
||||
SearchedLocations = searchedLocations,
|
||||
};
|
||||
}
|
||||
|
||||
public class OnBeforeViewEventData
|
||||
{
|
||||
public IProxyView View { get; set; }
|
||||
public IProxyViewContext ViewContext { get; set; }
|
||||
}
|
||||
|
||||
public OnBeforeViewEventData BeforeView { get; set; }
|
||||
|
||||
[TelemetryName("Microsoft.AspNet.Mvc.BeforeView")]
|
||||
public virtual void OnBeforeView(IProxyView view, IProxyViewContext viewContext)
|
||||
{
|
||||
BeforeView = new OnBeforeViewEventData()
|
||||
{
|
||||
View = view,
|
||||
ViewContext = viewContext,
|
||||
};
|
||||
}
|
||||
|
||||
public class OnAfterViewEventData
|
||||
{
|
||||
public IProxyView View { get; set; }
|
||||
public IProxyViewContext ViewContext { get; set; }
|
||||
}
|
||||
|
||||
public OnAfterViewEventData AfterView { get; set; }
|
||||
|
||||
[TelemetryName("Microsoft.AspNet.Mvc.AfterView")]
|
||||
public virtual void OnAfterView(IProxyView view, IProxyViewContext viewContext)
|
||||
{
|
||||
AfterView = new OnAfterViewEventData()
|
||||
{
|
||||
View = view,
|
||||
ViewContext = viewContext,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,27 +2,29 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Diagnostics.Tracing;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Internal;
|
||||
using Microsoft.AspNet.Mvc.Abstractions;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
using Microsoft.AspNet.Mvc.Rendering;
|
||||
using Microsoft.AspNet.Mvc.ViewEngines;
|
||||
using Microsoft.AspNet.Mvc.ViewFeatures;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.Logging.Testing;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
// These tests cover the logic included in PartialViewResult.ExecuteResultAsync - see PartialViewResultExecutorTest
|
||||
// and ViewExecutorTest for more comprehensive tests.
|
||||
public class PartialViewResultTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task ExecuteResultAsync_ReturnsError_IfViewCouldNotBeFound()
|
||||
public async Task ExecuteResultAsync_Throws_IfViewCouldNotBeFound()
|
||||
{
|
||||
// Arrange
|
||||
var expected = string.Join(
|
||||
|
|
@ -30,10 +32,9 @@ namespace Microsoft.AspNet.Mvc
|
|||
"The view 'MyView' was not found. The following locations were searched:",
|
||||
"Location1",
|
||||
"Location2.");
|
||||
var actionContext = new ActionContext(
|
||||
GetHttpContext(),
|
||||
new RouteData(),
|
||||
new ActionDescriptor());
|
||||
|
||||
var actionContext = GetActionContext();
|
||||
|
||||
var viewEngine = new Mock<IViewEngine>();
|
||||
viewEngine
|
||||
.Setup(v => v.FindPartialView(It.IsAny<ActionContext>(), It.IsAny<string>()))
|
||||
|
|
@ -56,17 +57,27 @@ namespace Microsoft.AspNet.Mvc
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PartialViewResult_UsesFindPartialViewOnSpecifiedViewEngineToLocateViews()
|
||||
public async Task ExecuteResultAsync_FindsAndExecutesView()
|
||||
{
|
||||
// Arrange
|
||||
var viewName = "myview";
|
||||
var context = new ActionContext(GetHttpContext(), new RouteData(), new ActionDescriptor());
|
||||
var viewEngine = new Mock<IViewEngine>();
|
||||
var view = Mock.Of<IView>();
|
||||
var context = GetActionContext();
|
||||
|
||||
var view = new Mock<IView>(MockBehavior.Strict);
|
||||
view
|
||||
.Setup(v => v.RenderAsync(It.IsAny<ViewContext>()))
|
||||
.Returns(Task.FromResult(0))
|
||||
.Verifiable();
|
||||
|
||||
view
|
||||
.As<IDisposable>()
|
||||
.Setup(v => v.Dispose())
|
||||
.Verifiable();
|
||||
|
||||
var viewEngine = new Mock<IViewEngine>(MockBehavior.Strict);
|
||||
viewEngine
|
||||
.Setup(e => e.FindPartialView(context, "myview"))
|
||||
.Returns(ViewEngineResult.Found("myview", view))
|
||||
.Returns(ViewEngineResult.Found("myview", view.Object))
|
||||
.Verifiable();
|
||||
|
||||
var viewResult = new PartialViewResult
|
||||
|
|
@ -81,185 +92,29 @@ namespace Microsoft.AspNet.Mvc
|
|||
await viewResult.ExecuteResultAsync(context);
|
||||
|
||||
// Assert
|
||||
view.Verify();
|
||||
viewEngine.Verify();
|
||||
}
|
||||
|
||||
public static TheoryData<MediaTypeHeaderValue, string> PartialViewResultContentTypeData
|
||||
private ActionContext GetActionContext()
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<MediaTypeHeaderValue, string>
|
||||
{
|
||||
{
|
||||
null,
|
||||
"text/html; charset=utf-8"
|
||||
},
|
||||
{
|
||||
new MediaTypeHeaderValue("text/foo"),
|
||||
"text/foo; charset=utf-8"
|
||||
},
|
||||
{
|
||||
new MediaTypeHeaderValue("text/foo") { Encoding = Encoding.ASCII },
|
||||
"text/foo; charset=us-ascii"
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(PartialViewResultContentTypeData))]
|
||||
public async Task PartialViewResult_SetsContentTypeHeader(
|
||||
MediaTypeHeaderValue contentType,
|
||||
string expectedContentTypeHeaderValue)
|
||||
{
|
||||
// Arrange
|
||||
var viewName = "myview";
|
||||
var httpContext = GetHttpContext();
|
||||
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
||||
var viewEngine = new Mock<IViewEngine>();
|
||||
var view = Mock.Of<IView>();
|
||||
|
||||
viewEngine
|
||||
.Setup(e => e.FindPartialView(context, "myview"))
|
||||
.Returns(ViewEngineResult.Found("myview", view));
|
||||
|
||||
var viewResult = new PartialViewResult
|
||||
{
|
||||
ViewName = viewName,
|
||||
ViewEngine = viewEngine.Object,
|
||||
ContentType = contentType,
|
||||
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
|
||||
TempData = Mock.Of<ITempDataDictionary>(),
|
||||
};
|
||||
|
||||
// Act
|
||||
await viewResult.ExecuteResultAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedContentTypeHeaderValue, httpContext.Response.ContentType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PartialViewResult_SetsStatusCode()
|
||||
{
|
||||
// Arrange
|
||||
var viewName = "myview";
|
||||
var httpContext = GetHttpContext();
|
||||
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
||||
var viewEngine = new Mock<IViewEngine>();
|
||||
var view = Mock.Of<IView>();
|
||||
|
||||
viewEngine
|
||||
.Setup(e => e.FindPartialView(context, "myview"))
|
||||
.Returns(ViewEngineResult.Found("myview", view));
|
||||
|
||||
var viewResult = new PartialViewResult
|
||||
{
|
||||
ViewName = viewName,
|
||||
ViewEngine = viewEngine.Object,
|
||||
StatusCode = 404,
|
||||
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
|
||||
TempData = Mock.Of<ITempDataDictionary>(),
|
||||
};
|
||||
|
||||
// Act
|
||||
await viewResult.ExecuteResultAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(404, httpContext.Response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteResultAsync_UsesActionDescriptorName_IfViewNameIsNull()
|
||||
{
|
||||
// Arrange
|
||||
var viewName = "some-view-name";
|
||||
var context = new ActionContext(
|
||||
GetHttpContext(),
|
||||
new RouteData(),
|
||||
new ActionDescriptor { Name = viewName });
|
||||
var viewEngine = new Mock<ICompositeViewEngine>();
|
||||
viewEngine
|
||||
.Setup(e => e.FindPartialView(context, viewName))
|
||||
.Returns(ViewEngineResult.Found(viewName, Mock.Of<IView>()))
|
||||
.Verifiable();
|
||||
|
||||
var viewResult = new PartialViewResult
|
||||
{
|
||||
ViewEngine = viewEngine.Object,
|
||||
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
|
||||
TempData = Mock.Of<ITempDataDictionary>(),
|
||||
};
|
||||
|
||||
// Act
|
||||
await viewResult.ExecuteResultAsync(context);
|
||||
|
||||
// Assert
|
||||
viewEngine.Verify();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteResultAsync_UsesCompositeViewEngineFromServices_IfViewEngineIsNotSpecified()
|
||||
{
|
||||
// Arrange
|
||||
var viewName = "partial-view-name";
|
||||
var context = new ActionContext(
|
||||
new DefaultHttpContext(),
|
||||
new RouteData(),
|
||||
new ActionDescriptor { Name = viewName });
|
||||
var viewEngine = new Mock<ICompositeViewEngine>();
|
||||
viewEngine
|
||||
.Setup(e => e.FindPartialView(It.IsAny<ActionContext>(), viewName))
|
||||
.Returns(ViewEngineResult.Found(viewName, Mock.Of<IView>()))
|
||||
.Verifiable();
|
||||
|
||||
var serviceProvider = new Mock<IServiceProvider>();
|
||||
serviceProvider
|
||||
.Setup(p => p.GetService(typeof(ICompositeViewEngine)))
|
||||
.Returns(viewEngine.Object);
|
||||
serviceProvider
|
||||
.Setup(p => p.GetService(typeof(ILogger<PartialViewResult>)))
|
||||
.Returns(new Mock<ILogger<PartialViewResult>>().Object);
|
||||
serviceProvider.Setup(s => s.GetService(typeof(IOptions<MvcViewOptions>)))
|
||||
.Returns(() => {
|
||||
var optionsAccessor = new Mock<IOptions<MvcViewOptions>>();
|
||||
optionsAccessor.SetupGet(o => o.Value)
|
||||
.Returns(new MvcViewOptions());
|
||||
return optionsAccessor.Object;
|
||||
});
|
||||
context.HttpContext.RequestServices = serviceProvider.Object;
|
||||
|
||||
var viewResult = new PartialViewResult
|
||||
{
|
||||
ViewName = viewName,
|
||||
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
|
||||
TempData = Mock.Of<ITempDataDictionary>(),
|
||||
};
|
||||
|
||||
// Act
|
||||
await viewResult.ExecuteResultAsync(context);
|
||||
|
||||
// Assert
|
||||
viewEngine.Verify();
|
||||
return new ActionContext(GetHttpContext(), new RouteData(), new ActionDescriptor());
|
||||
}
|
||||
|
||||
private HttpContext GetHttpContext()
|
||||
{
|
||||
var serviceProvider = new Mock<IServiceProvider>();
|
||||
serviceProvider.Setup(s => s.GetService(typeof(ILogger<PartialViewResult>)))
|
||||
.Returns(new Mock<ILogger<PartialViewResult>>().Object);
|
||||
var options = new TestOptionsManager<MvcViewOptions>();
|
||||
var viewExecutor = new PartialViewResultExecutor(
|
||||
options,
|
||||
new CompositeViewEngine(options),
|
||||
new TelemetryListener("Microsoft.AspNet"),
|
||||
NullLoggerFactory.Instance);
|
||||
|
||||
serviceProvider.Setup(s => s.GetService(typeof(IOptions<MvcViewOptions>)))
|
||||
.Returns(() => {
|
||||
var optionsAccessor = new Mock<IOptions<MvcViewOptions>>();
|
||||
optionsAccessor.SetupGet(o => o.Value)
|
||||
.Returns(new MvcViewOptions());
|
||||
return optionsAccessor.Object;
|
||||
});
|
||||
var services = new ServiceCollection();
|
||||
services.AddInstance(viewExecutor);
|
||||
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.RequestServices = serviceProvider.Object;
|
||||
|
||||
httpContext.RequestServices = services.BuildServiceProvider();
|
||||
return httpContext;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,227 @@
|
|||
// 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.Diagnostics.Tracing;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http.Internal;
|
||||
using Microsoft.AspNet.Mvc.Abstractions;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
using Microsoft.AspNet.Mvc.ViewEngines;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.Framework.Logging.Testing;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||
{
|
||||
public class PartialViewResultExecutorTest
|
||||
{
|
||||
[Fact]
|
||||
public void FindView_UsesViewEngine_FromPartialViewResult()
|
||||
{
|
||||
// Arrange
|
||||
var context = GetActionContext();
|
||||
var executor = GetViewExecutor();
|
||||
|
||||
var viewName = "my-view";
|
||||
var viewEngine = new Mock<ICompositeViewEngine>();
|
||||
viewEngine
|
||||
.Setup(e => e.FindPartialView(context, viewName))
|
||||
.Returns(ViewEngineResult.Found(viewName, Mock.Of<IView>()))
|
||||
.Verifiable();
|
||||
|
||||
var viewResult = new PartialViewResult
|
||||
{
|
||||
ViewEngine = viewEngine.Object,
|
||||
ViewName = viewName,
|
||||
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
|
||||
TempData = Mock.Of<ITempDataDictionary>(),
|
||||
};
|
||||
|
||||
// Act
|
||||
var viewEngineResult = executor.FindView(context, viewResult);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(viewName, viewEngineResult.ViewName);
|
||||
viewEngine.Verify();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindView_UsesActionDescriptorName_IfViewNameIsNull()
|
||||
{
|
||||
// Arrange
|
||||
var context = GetActionContext();
|
||||
var executor = GetViewExecutor();
|
||||
|
||||
var viewName = "some-view-name";
|
||||
context.ActionDescriptor.Name = viewName;
|
||||
|
||||
var viewResult = new PartialViewResult
|
||||
{
|
||||
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
|
||||
TempData = Mock.Of<ITempDataDictionary>(),
|
||||
};
|
||||
|
||||
// Act
|
||||
var viewEngineResult = executor.FindView(context, viewResult);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(viewName, viewEngineResult.ViewName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindView_Notifies_ViewFound()
|
||||
{
|
||||
// Arrange
|
||||
var telemetry = new TelemetryListener("Test");
|
||||
var listener = new TestTelemetryListener();
|
||||
telemetry.SubscribeWithAdapter(listener);
|
||||
|
||||
var context = GetActionContext();
|
||||
var executor = GetViewExecutor(telemetry);
|
||||
|
||||
var viewName = "myview";
|
||||
var viewResult = new PartialViewResult
|
||||
{
|
||||
ViewName = viewName,
|
||||
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
|
||||
TempData = Mock.Of<ITempDataDictionary>(),
|
||||
};
|
||||
|
||||
// Act
|
||||
var viewEngineResult = executor.FindView(context, viewResult);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(viewName, viewEngineResult.ViewName);
|
||||
|
||||
Assert.NotNull(listener.ViewFound);
|
||||
Assert.NotNull(listener.ViewFound.ActionContext);
|
||||
Assert.NotNull(listener.ViewFound.Result);
|
||||
Assert.NotNull(listener.ViewFound.View);
|
||||
Assert.True(listener.ViewFound.IsPartial);
|
||||
Assert.Equal("myview", listener.ViewFound.ViewName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindView_Notifies_ViewNotFound()
|
||||
{
|
||||
// Arrange
|
||||
var telemetry = new TelemetryListener("Test");
|
||||
var listener = new TestTelemetryListener();
|
||||
telemetry.SubscribeWithAdapter(listener);
|
||||
|
||||
var context = GetActionContext();
|
||||
var executor = GetViewExecutor(telemetry);
|
||||
|
||||
var viewName = "myview";
|
||||
var viewEngine = new Mock<IViewEngine>(MockBehavior.Strict);
|
||||
viewEngine
|
||||
.Setup(e => e.FindPartialView(context, "myview"))
|
||||
.Returns(ViewEngineResult.NotFound("myview", new string[] { "location/myview" }));
|
||||
|
||||
var viewResult = new PartialViewResult
|
||||
{
|
||||
ViewName = viewName,
|
||||
ViewEngine = viewEngine.Object,
|
||||
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
|
||||
TempData = Mock.Of<ITempDataDictionary>(),
|
||||
};
|
||||
|
||||
// Act
|
||||
var viewEngineResult = executor.FindView(context, viewResult);
|
||||
|
||||
// Assert
|
||||
Assert.False(viewEngineResult.Success);
|
||||
|
||||
Assert.NotNull(listener.ViewNotFound);
|
||||
Assert.NotNull(listener.ViewNotFound.ActionContext);
|
||||
Assert.NotNull(listener.ViewNotFound.Result);
|
||||
Assert.Equal(new string[] { "location/myview" }, listener.ViewNotFound.SearchedLocations);
|
||||
Assert.Equal("myview", listener.ViewNotFound.ViewName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_UsesContentType_FromPartialViewResult()
|
||||
{
|
||||
// Arrange
|
||||
var context = GetActionContext();
|
||||
var executor = GetViewExecutor();
|
||||
|
||||
var contentType = MediaTypeHeaderValue.Parse("application/x-my-content-type");
|
||||
|
||||
var viewResult = new PartialViewResult
|
||||
{
|
||||
ViewName = "my-view",
|
||||
ContentType = contentType,
|
||||
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
|
||||
TempData = Mock.Of<ITempDataDictionary>(),
|
||||
};
|
||||
|
||||
// Act
|
||||
await executor.ExecuteAsync(context, Mock.Of<IView>(), viewResult);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("application/x-my-content-type; charset=utf-8", context.HttpContext.Response.ContentType);
|
||||
|
||||
// Check if the original instance provided by the user has not changed.
|
||||
// Since we do not have access to the new instance created within the view executor,
|
||||
// check if at least the content is the same.
|
||||
Assert.Null(contentType.Encoding);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_UsesStatusCode_FromPartialViewResult()
|
||||
{
|
||||
// Arrange
|
||||
var context = GetActionContext();
|
||||
var executor = GetViewExecutor();
|
||||
|
||||
var contentType = MediaTypeHeaderValue.Parse("application/x-my-content-type");
|
||||
|
||||
var viewResult = new PartialViewResult
|
||||
{
|
||||
ViewName = "my-view",
|
||||
StatusCode = 404,
|
||||
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
|
||||
TempData = Mock.Of<ITempDataDictionary>(),
|
||||
};
|
||||
|
||||
// Act
|
||||
await executor.ExecuteAsync(context, Mock.Of<IView>(), viewResult);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(404, context.HttpContext.Response.StatusCode);
|
||||
}
|
||||
|
||||
private ActionContext GetActionContext()
|
||||
{
|
||||
return new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
|
||||
}
|
||||
|
||||
private PartialViewResultExecutor GetViewExecutor(TelemetrySource telemetry = null)
|
||||
{
|
||||
if (telemetry == null)
|
||||
{
|
||||
telemetry = new TelemetryListener("Test");
|
||||
}
|
||||
|
||||
var viewEngine = new Mock<IViewEngine>(MockBehavior.Strict);
|
||||
viewEngine
|
||||
.Setup(e => e.FindPartialView(It.IsAny<ActionContext>(), It.IsAny<string>()))
|
||||
.Returns<ActionContext, string>((_, name) => ViewEngineResult.Found(name, Mock.Of<IView>()));
|
||||
|
||||
var options = new TestOptionsManager<MvcViewOptions>();
|
||||
options.Value.ViewEngines.Add(viewEngine.Object);
|
||||
|
||||
var viewExecutor = new PartialViewResultExecutor(
|
||||
options,
|
||||
new CompositeViewEngine(options),
|
||||
telemetry,
|
||||
NullLoggerFactory.Instance);
|
||||
|
||||
return viewExecutor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,9 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.Tracing;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http.Internal;
|
||||
|
|
@ -19,59 +21,51 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
{
|
||||
public class ViewExecutorTest
|
||||
{
|
||||
public static TheoryData<MediaTypeHeaderValue, string, string, byte[]> ViewExecutorSetsContentTypeAndEncodingData
|
||||
public static TheoryData<MediaTypeHeaderValue, string, string> ViewExecutorSetsContentTypeAndEncodingData
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<MediaTypeHeaderValue, string, string, byte[]>
|
||||
return new TheoryData<MediaTypeHeaderValue, string, string>
|
||||
{
|
||||
{
|
||||
null,
|
||||
null,
|
||||
"text/html; charset=utf-8",
|
||||
new byte[] { 97, 98, 99, 100 }
|
||||
"text/html; charset=utf-8"
|
||||
},
|
||||
{
|
||||
new MediaTypeHeaderValue("text/foo"),
|
||||
null,
|
||||
"text/foo; charset=utf-8",
|
||||
new byte[] { 97, 98, 99, 100 }
|
||||
"text/foo; charset=utf-8"
|
||||
},
|
||||
{
|
||||
MediaTypeHeaderValue.Parse("text/foo; p1=p1-value"),
|
||||
null,
|
||||
"text/foo; p1=p1-value; charset=utf-8",
|
||||
new byte[] { 97, 98, 99, 100 }
|
||||
"text/foo; p1=p1-value; charset=utf-8"
|
||||
},
|
||||
{
|
||||
new MediaTypeHeaderValue("text/foo") { Charset = "us-ascii" },
|
||||
null,
|
||||
"text/foo; charset=us-ascii",
|
||||
new byte[] { 97, 98, 99, 100 }
|
||||
"text/foo; charset=us-ascii"
|
||||
},
|
||||
{
|
||||
null,
|
||||
"text/bar",
|
||||
"text/bar",
|
||||
new byte[] { 97, 98, 99, 100 }
|
||||
"text/bar"
|
||||
},
|
||||
{
|
||||
null,
|
||||
"application/xml; charset=us-ascii",
|
||||
"application/xml; charset=us-ascii",
|
||||
new byte[] { 97, 98, 99, 100 }
|
||||
"application/xml; charset=us-ascii"
|
||||
},
|
||||
{
|
||||
null,
|
||||
"Invalid content type",
|
||||
"Invalid content type",
|
||||
new byte[] { 97, 98, 99, 100 }
|
||||
"Invalid content type"
|
||||
},
|
||||
{
|
||||
new MediaTypeHeaderValue("text/foo") { Charset = "us-ascii" },
|
||||
"text/bar",
|
||||
"text/foo; charset=us-ascii",
|
||||
new byte[] { 97, 98, 99, 100 }
|
||||
"text/foo; charset=us-ascii"
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -82,17 +76,13 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
public async Task ExecuteAsync_SetsContentTypeAndEncoding(
|
||||
MediaTypeHeaderValue contentType,
|
||||
string responseContentType,
|
||||
string expectedContentType,
|
||||
byte[] expectedContentData)
|
||||
string expectedContentType)
|
||||
{
|
||||
// Arrange
|
||||
var view = new Mock<IView>();
|
||||
view.Setup(v => v.RenderAsync(It.IsAny<ViewContext>()))
|
||||
.Callback((ViewContext v) =>
|
||||
{
|
||||
v.Writer.Write("abcd");
|
||||
})
|
||||
.Returns(Task.FromResult(0));
|
||||
var view = CreateView(async (v) =>
|
||||
{
|
||||
await v.Writer.WriteAsync("abcd");
|
||||
});
|
||||
|
||||
var context = new DefaultHttpContext();
|
||||
var memoryStream = new MemoryStream();
|
||||
|
|
@ -105,18 +95,99 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
new ActionDescriptor());
|
||||
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider());
|
||||
|
||||
var viewExecutor = CreateViewExecutor();
|
||||
|
||||
// Act
|
||||
await ViewExecutor.ExecuteAsync(
|
||||
view.Object,
|
||||
await viewExecutor.ExecuteAsync(
|
||||
actionContext,
|
||||
view,
|
||||
viewData,
|
||||
Mock.Of<ITempDataDictionary>(),
|
||||
new HtmlHelperOptions(),
|
||||
contentType);
|
||||
contentType,
|
||||
statusCode: null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedContentType, context.Response.ContentType);
|
||||
Assert.Equal(expectedContentData, memoryStream.ToArray());
|
||||
Assert.Equal("abcd", Encoding.UTF8.GetString(memoryStream.ToArray()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_SetsStatusCode()
|
||||
{
|
||||
// Arrange
|
||||
var view = CreateView(async (v) =>
|
||||
{
|
||||
await v.Writer.WriteAsync("abcd");
|
||||
});
|
||||
|
||||
var context = new DefaultHttpContext();
|
||||
var memoryStream = new MemoryStream();
|
||||
context.Response.Body = memoryStream;
|
||||
|
||||
var actionContext = new ActionContext(
|
||||
context,
|
||||
new RouteData(),
|
||||
new ActionDescriptor());
|
||||
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider());
|
||||
|
||||
var viewExecutor = CreateViewExecutor();
|
||||
|
||||
// Act
|
||||
await viewExecutor.ExecuteAsync(
|
||||
actionContext,
|
||||
view,
|
||||
viewData,
|
||||
Mock.Of<ITempDataDictionary>(),
|
||||
contentType: null,
|
||||
statusCode: 500);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(500, context.Response.StatusCode);
|
||||
Assert.Equal("abcd", Encoding.UTF8.GetString(memoryStream.ToArray()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_WritesTelemetry()
|
||||
{
|
||||
// Arrange
|
||||
var view = CreateView(async (v) =>
|
||||
{
|
||||
await v.Writer.WriteAsync("abcd");
|
||||
});
|
||||
|
||||
var context = new DefaultHttpContext();
|
||||
var memoryStream = new MemoryStream();
|
||||
context.Response.Body = memoryStream;
|
||||
|
||||
var actionContext = new ActionContext(
|
||||
context,
|
||||
new RouteData(),
|
||||
new ActionDescriptor());
|
||||
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider());
|
||||
|
||||
var adapter = new TestTelemetryListener();
|
||||
|
||||
var telemetryListener = new TelemetryListener("Test");
|
||||
telemetryListener.SubscribeWithAdapter(adapter);
|
||||
|
||||
var viewExecutor = CreateViewExecutor(telemetryListener);
|
||||
|
||||
// Act
|
||||
await viewExecutor.ExecuteAsync(
|
||||
actionContext,
|
||||
view,
|
||||
viewData,
|
||||
Mock.Of<ITempDataDictionary>(),
|
||||
contentType: null,
|
||||
statusCode: null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("abcd", Encoding.UTF8.GetString(memoryStream.ToArray()));
|
||||
|
||||
Assert.NotNull(adapter.BeforeView?.View);
|
||||
Assert.NotNull(adapter.BeforeView?.ViewContext);
|
||||
Assert.NotNull(adapter.AfterView?.View);
|
||||
Assert.NotNull(adapter.AfterView?.ViewContext);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -142,15 +213,16 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
new ActionDescriptor());
|
||||
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider());
|
||||
|
||||
var viewExecutor = CreateViewExecutor();
|
||||
|
||||
// Act
|
||||
await Record.ExceptionAsync(
|
||||
() => ViewExecutor.ExecuteAsync(
|
||||
view.Object,
|
||||
actionContext,
|
||||
viewData,
|
||||
null,
|
||||
new HtmlHelperOptions(),
|
||||
contentType: null));
|
||||
await Record.ExceptionAsync(() => viewExecutor.ExecuteAsync(
|
||||
actionContext,
|
||||
view.Object,
|
||||
viewData,
|
||||
Mock.Of<ITempDataDictionary>(),
|
||||
contentType: null,
|
||||
statusCode: null));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedLength, memoryStream.Length);
|
||||
|
|
@ -163,14 +235,11 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
public async Task ExecuteAsync_AsynchronouslyFlushesToTheResponseStream_PriorToDispose(int writeLength)
|
||||
{
|
||||
// Arrange
|
||||
var view = new Mock<IView>();
|
||||
view.Setup(v => v.RenderAsync(It.IsAny<ViewContext>()))
|
||||
.Returns((ViewContext v) =>
|
||||
Task.Run(async () =>
|
||||
{
|
||||
var text = new string('a', writeLength);
|
||||
await v.Writer.WriteAsync(text);
|
||||
}));
|
||||
var view = CreateView(async (v) =>
|
||||
{
|
||||
var text = new string('a', writeLength);
|
||||
await v.Writer.WriteAsync(text);
|
||||
});
|
||||
|
||||
var context = new DefaultHttpContext();
|
||||
var stream = new Mock<Stream>();
|
||||
|
|
@ -182,18 +251,43 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
|||
new ActionDescriptor());
|
||||
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider());
|
||||
|
||||
var viewExecutor = CreateViewExecutor();
|
||||
|
||||
// Act
|
||||
await ViewExecutor.ExecuteAsync(
|
||||
view.Object,
|
||||
await viewExecutor.ExecuteAsync(
|
||||
actionContext,
|
||||
view,
|
||||
viewData,
|
||||
Mock.Of<ITempDataDictionary>(),
|
||||
new HtmlHelperOptions(),
|
||||
ViewExecutor.DefaultContentType);
|
||||
contentType: null,
|
||||
statusCode: null);
|
||||
|
||||
// Assert
|
||||
stream.Verify(s => s.FlushAsync(It.IsAny<CancellationToken>()), Times.Once());
|
||||
stream.Verify(s => s.Write(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>()), Times.Never());
|
||||
}
|
||||
|
||||
private IView CreateView(Func<ViewContext, Task> action)
|
||||
{
|
||||
var view = new Mock<IView>(MockBehavior.Strict);
|
||||
view
|
||||
.Setup(v => v.RenderAsync(It.IsAny<ViewContext>()))
|
||||
.Returns(action);
|
||||
|
||||
return view.Object;
|
||||
}
|
||||
|
||||
private ViewExecutor CreateViewExecutor(TelemetryListener listener = null)
|
||||
{
|
||||
if (listener == null)
|
||||
{
|
||||
listener = new TelemetryListener("Test");
|
||||
}
|
||||
|
||||
return new ViewExecutor(
|
||||
new TestOptionsManager<MvcViewOptions>(),
|
||||
new Mock<ICompositeViewEngine>(MockBehavior.Strict).Object,
|
||||
listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,226 @@
|
|||
// 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.Diagnostics.Tracing;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http.Internal;
|
||||
using Microsoft.AspNet.Mvc.Abstractions;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
using Microsoft.AspNet.Mvc.ViewEngines;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using Microsoft.Framework.Logging.Testing;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||
{
|
||||
public class ViewResultExecutorTest
|
||||
{
|
||||
[Fact]
|
||||
public void FindView_UsesViewEngine_FromViewResult()
|
||||
{
|
||||
// Arrange
|
||||
var context = GetActionContext();
|
||||
var executor = GetViewExecutor();
|
||||
|
||||
var viewName = "my-view";
|
||||
var viewEngine = new Mock<ICompositeViewEngine>();
|
||||
viewEngine
|
||||
.Setup(e => e.FindView(context, viewName))
|
||||
.Returns(ViewEngineResult.Found(viewName, Mock.Of<IView>()))
|
||||
.Verifiable();
|
||||
|
||||
var viewResult = new ViewResult
|
||||
{
|
||||
ViewEngine = viewEngine.Object,
|
||||
ViewName = viewName,
|
||||
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
|
||||
TempData = Mock.Of<ITempDataDictionary>(),
|
||||
};
|
||||
|
||||
// Act
|
||||
var viewEngineResult = executor.FindView(context, viewResult);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(viewName, viewEngineResult.ViewName);
|
||||
viewEngine.Verify();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindView_UsesActionDescriptorName_IfViewNameIsNull()
|
||||
{
|
||||
// Arrange
|
||||
var context = GetActionContext();
|
||||
var executor = GetViewExecutor();
|
||||
|
||||
var viewName = "some-view-name";
|
||||
context.ActionDescriptor.Name = viewName;
|
||||
|
||||
var viewResult = new ViewResult
|
||||
{
|
||||
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
|
||||
TempData = Mock.Of<ITempDataDictionary>(),
|
||||
};
|
||||
|
||||
// Act
|
||||
var viewEngineResult = executor.FindView(context, viewResult);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(viewName, viewEngineResult.ViewName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindView_Notifies_ViewFound()
|
||||
{
|
||||
// Arrange
|
||||
var telemetry = new TelemetryListener("Test");
|
||||
var listener = new TestTelemetryListener();
|
||||
telemetry.SubscribeWithAdapter(listener);
|
||||
|
||||
var context = GetActionContext();
|
||||
var executor = GetViewExecutor(telemetry);
|
||||
|
||||
var viewName = "myview";
|
||||
var viewResult = new ViewResult
|
||||
{
|
||||
ViewName = viewName,
|
||||
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
|
||||
TempData = Mock.Of<ITempDataDictionary>(),
|
||||
};
|
||||
|
||||
// Act
|
||||
var viewEngineResult = executor.FindView(context, viewResult);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(viewName, viewEngineResult.ViewName);
|
||||
|
||||
Assert.NotNull(listener.ViewFound);
|
||||
Assert.NotNull(listener.ViewFound.ActionContext);
|
||||
Assert.NotNull(listener.ViewFound.Result);
|
||||
Assert.NotNull(listener.ViewFound.View);
|
||||
Assert.False(listener.ViewFound.IsPartial);
|
||||
Assert.Equal("myview", listener.ViewFound.ViewName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindView_Notifies_ViewNotFound()
|
||||
{
|
||||
// Arrange
|
||||
var telemetry = new TelemetryListener("Test");
|
||||
var listener = new TestTelemetryListener();
|
||||
telemetry.SubscribeWithAdapter(listener);
|
||||
|
||||
var context = GetActionContext();
|
||||
var executor = GetViewExecutor(telemetry);
|
||||
|
||||
var viewName = "myview";
|
||||
var viewEngine = new Mock<IViewEngine>();
|
||||
viewEngine
|
||||
.Setup(e => e.FindView(context, "myview"))
|
||||
.Returns(ViewEngineResult.NotFound("myview", new string[] { "location/myview" }));
|
||||
|
||||
var viewResult = new ViewResult
|
||||
{
|
||||
ViewName = viewName,
|
||||
ViewEngine = viewEngine.Object,
|
||||
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
|
||||
TempData = Mock.Of<ITempDataDictionary>(),
|
||||
};
|
||||
|
||||
// Act
|
||||
var viewEngineResult = executor.FindView(context, viewResult);
|
||||
|
||||
// Assert
|
||||
Assert.False(viewEngineResult.Success);
|
||||
|
||||
Assert.NotNull(listener.ViewNotFound);
|
||||
Assert.NotNull(listener.ViewNotFound.ActionContext);
|
||||
Assert.NotNull(listener.ViewNotFound.Result);
|
||||
Assert.Equal(new string[] { "location/myview" }, listener.ViewNotFound.SearchedLocations);
|
||||
Assert.Equal("myview", listener.ViewNotFound.ViewName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_UsesContentType_FromViewResult()
|
||||
{
|
||||
// Arrange
|
||||
var context = GetActionContext();
|
||||
var executor = GetViewExecutor();
|
||||
|
||||
var contentType = MediaTypeHeaderValue.Parse("application/x-my-content-type");
|
||||
|
||||
var viewResult = new ViewResult
|
||||
{
|
||||
ViewName = "my-view",
|
||||
ContentType = contentType,
|
||||
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
|
||||
TempData = Mock.Of<ITempDataDictionary>(),
|
||||
};
|
||||
|
||||
// Act
|
||||
await executor.ExecuteAsync(context, Mock.Of<IView>(), viewResult);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("application/x-my-content-type; charset=utf-8", context.HttpContext.Response.ContentType);
|
||||
|
||||
// Check if the original instance provided by the user has not changed.
|
||||
// Since we do not have access to the new instance created within the view executor,
|
||||
// check if at least the content is the same.
|
||||
Assert.Null(contentType.Encoding);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_UsesStatusCode_FromViewResult()
|
||||
{
|
||||
// Arrange
|
||||
var context = GetActionContext();
|
||||
var executor = GetViewExecutor();
|
||||
|
||||
var contentType = MediaTypeHeaderValue.Parse("application/x-my-content-type");
|
||||
|
||||
var viewResult = new ViewResult
|
||||
{
|
||||
ViewName = "my-view",
|
||||
StatusCode = 404,
|
||||
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
|
||||
TempData = Mock.Of<ITempDataDictionary>(),
|
||||
};
|
||||
|
||||
// Act
|
||||
await executor.ExecuteAsync(context, Mock.Of<IView>(), viewResult);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(404, context.HttpContext.Response.StatusCode);
|
||||
}
|
||||
|
||||
private ActionContext GetActionContext()
|
||||
{
|
||||
return new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
|
||||
}
|
||||
|
||||
private ViewResultExecutor GetViewExecutor(TelemetrySource telemetry = null)
|
||||
{
|
||||
if (telemetry == null)
|
||||
{
|
||||
telemetry = new TelemetryListener("Test");
|
||||
}
|
||||
|
||||
var viewEngine = new Mock<IViewEngine>(MockBehavior.Strict);
|
||||
viewEngine
|
||||
.Setup(e => e.FindView(It.IsAny<ActionContext>(), It.IsAny<string>()))
|
||||
.Returns<ActionContext, string>((_, name) => ViewEngineResult.Found(name, Mock.Of<IView>()));
|
||||
|
||||
var options = new TestOptionsManager<MvcViewOptions>();
|
||||
options.Value.ViewEngines.Add(viewEngine.Object);
|
||||
|
||||
var viewExecutor = new ViewResultExecutor(
|
||||
options,
|
||||
new CompositeViewEngine(options),
|
||||
telemetry,
|
||||
NullLoggerFactory.Instance);
|
||||
|
||||
return viewExecutor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,42 +3,44 @@
|
|||
|
||||
using System;
|
||||
using System.Diagnostics.Tracing;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Http.Internal;
|
||||
using Microsoft.AspNet.Mvc.Abstractions;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
using Microsoft.AspNet.Mvc.Rendering;
|
||||
using Microsoft.AspNet.Mvc.ViewEngines;
|
||||
using Microsoft.AspNet.Mvc.ViewFeatures;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Microsoft.Framework.Logging.Testing;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
// These tests cover the logic included in ViewResult.ExecuteResultAsync - see ViewResultExecutorTest
|
||||
// and ViewExecutorTest for more comprehensive tests.
|
||||
public class ViewResultTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task ExecuteResultAsync_ReturnsError_IfViewCouldNotBeFound()
|
||||
public async Task ExecuteResultAsync_Throws_IfViewCouldNotBeFound()
|
||||
{
|
||||
// Arrange
|
||||
var expected = string.Join(Environment.NewLine,
|
||||
"The view 'MyView' was not found. The following locations were searched:",
|
||||
"Location1",
|
||||
"Location2.");
|
||||
|
||||
var actionContext = new ActionContext(GetHttpContext(),
|
||||
new RouteData(),
|
||||
new ActionDescriptor());
|
||||
var expected = string.Join(
|
||||
Environment.NewLine,
|
||||
"The view 'MyView' was not found. The following locations were searched:",
|
||||
"Location1",
|
||||
"Location2.");
|
||||
|
||||
var actionContext = GetActionContext();
|
||||
|
||||
var viewEngine = new Mock<IViewEngine>();
|
||||
viewEngine.Setup(v => v.FindView(It.IsAny<ActionContext>(), It.IsAny<string>()))
|
||||
.Returns(ViewEngineResult.NotFound("MyView", new[] { "Location1", "Location2" }))
|
||||
.Verifiable();
|
||||
viewEngine
|
||||
.Setup(v => v.FindView(It.IsAny<ActionContext>(), It.IsAny<string>()))
|
||||
.Returns(ViewEngineResult.NotFound("MyView", new[] { "Location1", "Location2" }))
|
||||
.Verifiable();
|
||||
|
||||
var viewResult = new ViewResult
|
||||
{
|
||||
|
|
@ -56,17 +58,27 @@ namespace Microsoft.AspNet.Mvc
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ViewResult_UsesFindViewOnSpecifiedViewEngineToLocateViews()
|
||||
public async Task ExecuteResultAsync_FindsAndExecutesView()
|
||||
{
|
||||
// Arrange
|
||||
var viewName = "myview";
|
||||
var context = new ActionContext(GetHttpContext(), new RouteData(), new ActionDescriptor());
|
||||
var viewEngine = new Mock<IViewEngine>();
|
||||
var view = Mock.Of<IView>();
|
||||
var context = GetActionContext();
|
||||
|
||||
var view = new Mock<IView>(MockBehavior.Strict);
|
||||
view
|
||||
.Setup(v => v.RenderAsync(It.IsAny<ViewContext>()))
|
||||
.Returns(Task.FromResult(0))
|
||||
.Verifiable();
|
||||
|
||||
view
|
||||
.As<IDisposable>()
|
||||
.Setup(v => v.Dispose())
|
||||
.Verifiable();
|
||||
|
||||
var viewEngine = new Mock<IViewEngine>(MockBehavior.Strict);
|
||||
viewEngine
|
||||
.Setup(e => e.FindView(context, "myview"))
|
||||
.Returns(ViewEngineResult.Found("myview", view))
|
||||
.Returns(ViewEngineResult.Found("myview", view.Object))
|
||||
.Verifiable();
|
||||
|
||||
var viewResult = new ViewResult
|
||||
|
|
@ -81,273 +93,29 @@ namespace Microsoft.AspNet.Mvc
|
|||
await viewResult.ExecuteResultAsync(context);
|
||||
|
||||
// Assert
|
||||
view.Verify();
|
||||
viewEngine.Verify();
|
||||
}
|
||||
|
||||
public static TheoryData<MediaTypeHeaderValue, string> ViewResultContentTypeData
|
||||
private ActionContext GetActionContext()
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<MediaTypeHeaderValue, string>
|
||||
{
|
||||
{
|
||||
null,
|
||||
"text/html; charset=utf-8"
|
||||
},
|
||||
{
|
||||
new MediaTypeHeaderValue("text/foo"),
|
||||
"text/foo; charset=utf-8"
|
||||
},
|
||||
{
|
||||
MediaTypeHeaderValue.Parse("text/foo;p1=p1-value"),
|
||||
"text/foo; p1=p1-value; charset=utf-8"
|
||||
},
|
||||
{
|
||||
new MediaTypeHeaderValue("text/foo") { Encoding = Encoding.ASCII },
|
||||
"text/foo; charset=us-ascii"
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ViewResultContentTypeData))]
|
||||
public async Task ViewResult_SetsContentTypeHeader(
|
||||
MediaTypeHeaderValue contentType,
|
||||
string expectedContentTypeHeaderValue)
|
||||
{
|
||||
// Arrange
|
||||
var viewName = "myview";
|
||||
var httpContext = GetHttpContext();
|
||||
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
||||
var viewEngine = new Mock<IViewEngine>();
|
||||
var view = Mock.Of<IView>();
|
||||
var contentTypeBeforeViewResultExecution = contentType?.ToString();
|
||||
|
||||
viewEngine.Setup(e => e.FindView(context, "myview"))
|
||||
.Returns(ViewEngineResult.Found("myview", view));
|
||||
|
||||
var viewResult = new ViewResult
|
||||
{
|
||||
ViewName = viewName,
|
||||
ViewEngine = viewEngine.Object,
|
||||
ContentType = contentType,
|
||||
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
|
||||
TempData = Mock.Of<ITempDataDictionary>(),
|
||||
};
|
||||
|
||||
// Act
|
||||
await viewResult.ExecuteResultAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedContentTypeHeaderValue, httpContext.Response.ContentType);
|
||||
|
||||
// Check if the original instance provided by the user has not changed.
|
||||
// Since we do not have access to the new instance created within the view executor,
|
||||
// check if at least the content is the same.
|
||||
var contentTypeAfterViewResultExecution = contentType?.ToString();
|
||||
Assert.Equal(contentTypeBeforeViewResultExecution, contentTypeAfterViewResultExecution);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ViewResult_SetsStatusCode()
|
||||
{
|
||||
// Arrange
|
||||
var viewName = "myview";
|
||||
var httpContext = GetHttpContext();
|
||||
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
||||
var viewEngine = new Mock<IViewEngine>();
|
||||
var view = Mock.Of<IView>();
|
||||
|
||||
viewEngine.Setup(e => e.FindView(context, "myview"))
|
||||
.Returns(ViewEngineResult.Found("myview", view));
|
||||
|
||||
var viewResult = new ViewResult
|
||||
{
|
||||
ViewName = viewName,
|
||||
ViewEngine = viewEngine.Object,
|
||||
StatusCode = 404,
|
||||
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
|
||||
TempData = Mock.Of<ITempDataDictionary>(),
|
||||
};
|
||||
|
||||
// Act
|
||||
await viewResult.ExecuteResultAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(404, httpContext.Response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteResultAsync_UsesActionDescriptorName_IfViewNameIsNull()
|
||||
{
|
||||
// Arrange
|
||||
var viewName = "some-view-name";
|
||||
var context = new ActionContext(GetHttpContext(),
|
||||
new RouteData(),
|
||||
new ActionDescriptor { Name = viewName });
|
||||
var viewEngine = new Mock<ICompositeViewEngine>();
|
||||
viewEngine.Setup(e => e.FindView(context, viewName))
|
||||
.Returns(ViewEngineResult.Found(viewName, Mock.Of<IView>()))
|
||||
.Verifiable();
|
||||
|
||||
var viewResult = new ViewResult
|
||||
{
|
||||
ViewEngine = viewEngine.Object,
|
||||
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
|
||||
TempData = Mock.Of<ITempDataDictionary>(),
|
||||
};
|
||||
|
||||
// Act
|
||||
await viewResult.ExecuteResultAsync(context);
|
||||
|
||||
// Assert
|
||||
viewEngine.Verify();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteResultAsync_UsesCompositeViewEngineFromServices_IfViewEngineIsNotSpecified()
|
||||
{
|
||||
// Arrange
|
||||
var viewName = "some-view-name";
|
||||
var context = new ActionContext(new DefaultHttpContext(),
|
||||
new RouteData(),
|
||||
new ActionDescriptor { Name = viewName });
|
||||
var viewEngine = new Mock<ICompositeViewEngine>();
|
||||
viewEngine.Setup(e => e.FindView(context, viewName))
|
||||
.Returns(ViewEngineResult.Found(viewName, Mock.Of<IView>()))
|
||||
.Verifiable();
|
||||
|
||||
var serviceProvider = new Mock<IServiceProvider>();
|
||||
|
||||
var telemetry = new TelemetryListener("Microsoft.AspNet");
|
||||
serviceProvider
|
||||
.Setup(s => s.GetService(typeof(TelemetrySource)))
|
||||
.Returns(telemetry);
|
||||
serviceProvider
|
||||
.Setup(s => s.GetService(typeof(TelemetryListener)))
|
||||
.Returns(telemetry);
|
||||
serviceProvider.Setup(p => p.GetService(typeof(ICompositeViewEngine)))
|
||||
.Returns(viewEngine.Object);
|
||||
serviceProvider.Setup(p => p.GetService(typeof(ILogger<ViewResult>)))
|
||||
.Returns(new Mock<ILogger<ViewResult>>().Object);
|
||||
serviceProvider.Setup(s => s.GetService(typeof(IOptions<MvcViewOptions>)))
|
||||
.Returns(() => {
|
||||
var optionsAccessor = new Mock<IOptions<MvcViewOptions>>();
|
||||
optionsAccessor.SetupGet(o => o.Value)
|
||||
.Returns(new MvcViewOptions());
|
||||
return optionsAccessor.Object;
|
||||
});
|
||||
context.HttpContext.RequestServices = serviceProvider.Object;
|
||||
|
||||
var viewResult = new ViewResult
|
||||
{
|
||||
ViewName = viewName,
|
||||
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
|
||||
TempData = Mock.Of<ITempDataDictionary>(),
|
||||
};
|
||||
|
||||
// Act
|
||||
await viewResult.ExecuteResultAsync(context);
|
||||
|
||||
// Assert
|
||||
viewEngine.Verify();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ViewResult_NotifiesViewFound()
|
||||
{
|
||||
// Arrange
|
||||
var viewName = "myview";
|
||||
var httpContext = GetHttpContext();
|
||||
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
||||
|
||||
var listener = new TestTelemetryListener();
|
||||
httpContext.RequestServices.GetRequiredService<TelemetryListener>().SubscribeWithAdapter(listener);
|
||||
|
||||
var viewEngine = new Mock<IViewEngine>();
|
||||
var view = Mock.Of<IView>();
|
||||
|
||||
viewEngine.Setup(e => e.FindView(context, "myview"))
|
||||
.Returns(ViewEngineResult.Found("myview", view));
|
||||
|
||||
var viewResult = new ViewResult
|
||||
{
|
||||
ViewName = viewName,
|
||||
ViewEngine = viewEngine.Object,
|
||||
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
|
||||
TempData = Mock.Of<ITempDataDictionary>(),
|
||||
};
|
||||
|
||||
// Act
|
||||
await viewResult.ExecuteResultAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(listener.ViewResultViewFound);
|
||||
Assert.NotNull(listener.ViewResultViewFound.ActionContext);
|
||||
Assert.NotNull(listener.ViewResultViewFound.Result);
|
||||
Assert.NotNull(listener.ViewResultViewFound.View);
|
||||
Assert.Equal("myview", listener.ViewResultViewFound.ViewName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ViewResult_NotifiesViewNotFound()
|
||||
{
|
||||
// Arrange
|
||||
var viewName = "myview";
|
||||
var httpContext = GetHttpContext();
|
||||
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
||||
|
||||
var listener = new TestTelemetryListener();
|
||||
httpContext.RequestServices.GetRequiredService<TelemetryListener>().SubscribeWithAdapter(listener);
|
||||
|
||||
var viewEngine = new Mock<IViewEngine>();
|
||||
var view = Mock.Of<IView>();
|
||||
|
||||
viewEngine.Setup(e => e.FindView(context, "myview"))
|
||||
.Returns(ViewEngineResult.NotFound("myview", new string[] { "location/myview" }));
|
||||
|
||||
var viewResult = new ViewResult
|
||||
{
|
||||
ViewName = viewName,
|
||||
ViewEngine = viewEngine.Object,
|
||||
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
|
||||
TempData = Mock.Of<ITempDataDictionary>(),
|
||||
};
|
||||
|
||||
// Act
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
async () => await viewResult.ExecuteResultAsync(context));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(listener.ViewResultViewNotFound);
|
||||
Assert.NotNull(listener.ViewResultViewNotFound.ActionContext);
|
||||
Assert.NotNull(listener.ViewResultViewNotFound.Result);
|
||||
Assert.Equal(new string[] { "location/myview" }, listener.ViewResultViewNotFound.SearchedLocations);
|
||||
Assert.Equal("myview", listener.ViewResultViewNotFound.ViewName);
|
||||
return new ActionContext(GetHttpContext(), new RouteData(), new ActionDescriptor());
|
||||
}
|
||||
|
||||
private HttpContext GetHttpContext()
|
||||
{
|
||||
var serviceProvider = new Mock<IServiceProvider>();
|
||||
serviceProvider.Setup(s => s.GetService(typeof(ILogger<ViewResult>)))
|
||||
.Returns(new Mock<ILogger<ViewResult>>().Object);
|
||||
var options = new TestOptionsManager<MvcViewOptions>();
|
||||
var viewExecutor = new ViewResultExecutor(
|
||||
options,
|
||||
new CompositeViewEngine(options),
|
||||
new TelemetryListener("Microsoft.AspNet"),
|
||||
NullLoggerFactory.Instance);
|
||||
|
||||
var optionsAccessor = new Mock<IOptions<MvcViewOptions>>();
|
||||
optionsAccessor.SetupGet(o => o.Value)
|
||||
.Returns(new MvcViewOptions());
|
||||
var services = new ServiceCollection();
|
||||
services.AddInstance(viewExecutor);
|
||||
|
||||
serviceProvider.Setup(s => s.GetService(typeof(IOptions<MvcViewOptions>)))
|
||||
.Returns(optionsAccessor.Object);
|
||||
|
||||
var telemetry = new TelemetryListener("Microsoft.AspNet");
|
||||
serviceProvider.Setup(s => s.GetService(typeof(TelemetryListener)))
|
||||
.Returns(telemetry);
|
||||
serviceProvider.Setup(s => s.GetService(typeof(TelemetrySource)))
|
||||
.Returns(telemetry);
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.RequestServices = serviceProvider.Object;
|
||||
|
||||
httpContext.RequestServices = services.BuildServiceProvider();
|
||||
return httpContext;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue