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(
|
_telemetry.WriteTelemetry(
|
||||||
"Microsoft.AspNet.Mvc.AfterActionMethod",
|
"Microsoft.AspNet.Mvc.AfterActionMethod",
|
||||||
new { actionContext = ActionContext, result });
|
new { actionContext = ActionContext, result = result });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -789,7 +789,7 @@ namespace Microsoft.AspNet.Mvc.Controllers
|
||||||
{
|
{
|
||||||
_telemetry.WriteTelemetry(
|
_telemetry.WriteTelemetry(
|
||||||
"Microsoft.AspNet.Mvc.BeforeActionResult",
|
"Microsoft.AspNet.Mvc.BeforeActionResult",
|
||||||
new { actionContext = ActionContext, result });
|
new { actionContext = ActionContext, result = result });
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
@ -802,7 +802,7 @@ namespace Microsoft.AspNet.Mvc.Controllers
|
||||||
{
|
{
|
||||||
_telemetry.WriteTelemetry(
|
_telemetry.WriteTelemetry(
|
||||||
"Microsoft.AspNet.Mvc.AfterActionResult",
|
"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
|
// View Engine and related infrastructure
|
||||||
//
|
//
|
||||||
services.TryAddSingleton<ICompositeViewEngine, CompositeViewEngine>();
|
services.TryAddSingleton<ICompositeViewEngine, CompositeViewEngine>();
|
||||||
|
services.TryAddSingleton<ViewResultExecutor>();
|
||||||
|
services.TryAddSingleton<PartialViewResultExecutor>();
|
||||||
|
|
||||||
// Support for activating ViewDataDictionary
|
// Support for activating ViewDataDictionary
|
||||||
services.TryAddEnumerable(
|
services.TryAddEnumerable(
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,10 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
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.ViewEngines;
|
||||||
using Microsoft.AspNet.Mvc.ViewFeatures;
|
using Microsoft.AspNet.Mvc.ViewFeatures;
|
||||||
|
using Microsoft.Framework.DependencyInjection;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc
|
namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
|
|
@ -60,41 +58,16 @@ namespace Microsoft.AspNet.Mvc
|
||||||
throw new ArgumentNullException(nameof(context));
|
throw new ArgumentNullException(nameof(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
var viewEngine = ViewEngine ??
|
var services = context.HttpContext.RequestServices;
|
||||||
context.HttpContext.RequestServices.GetRequiredService<ICompositeViewEngine>();
|
var executor = services.GetRequiredService<PartialViewResultExecutor>();
|
||||||
|
|
||||||
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<PartialViewResult>>();
|
var result = executor.FindView(context, this);
|
||||||
|
result.EnsureSuccessful();
|
||||||
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 view = result.View;
|
||||||
using (view as IDisposable)
|
using (view as IDisposable)
|
||||||
{
|
{
|
||||||
await ViewExecutor.ExecuteAsync(
|
await executor.ExecuteAsync(context, view, this);
|
||||||
view,
|
|
||||||
context,
|
|
||||||
ViewData,
|
|
||||||
TempData,
|
|
||||||
options.Value.HtmlHelperOptions,
|
|
||||||
ContentType);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics.Tracing;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Mvc.Rendering;
|
using Microsoft.AspNet.Mvc.Rendering;
|
||||||
using Microsoft.AspNet.Mvc.ViewEngines;
|
using Microsoft.AspNet.Mvc.ViewEngines;
|
||||||
|
using Microsoft.Framework.OptionsModel;
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.ViewFeatures
|
namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Utility type for rendering a <see cref="IView"/> to the response.
|
/// Executes an <see cref="IView"/>.
|
||||||
/// </summary>
|
/// </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")
|
public static readonly MediaTypeHeaderValue DefaultContentType = new MediaTypeHeaderValue("text/html")
|
||||||
{
|
{
|
||||||
Encoding = Encoding.UTF8
|
Encoding = Encoding.UTF8
|
||||||
}.CopyAsReadOnly();
|
}.CopyAsReadOnly();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Asynchronously renders the specified <paramref name="view"/> to the response body.
|
/// Creates a new <see cref="ViewExecutor"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="view">The <see cref="IView"/> to render.</param>
|
/// <param name="viewOptions">The <see cref="IOptions{MvcViewOptions}"/>.</param>
|
||||||
/// <param name="actionContext">The <see cref="ActionContext"/> for the current executing action.</param>
|
/// <param name="viewEngine">The <see cref="ICompositeViewEngine"/>.</param>
|
||||||
/// <param name="viewData">The <see cref="ViewDataDictionary"/> for the view being rendered.</param>
|
/// <param name="telemetry">The <see cref="TelemetrySource"/>.</param>
|
||||||
/// <param name="tempData">The <see cref="ITempDataDictionary"/> for the view being rendered.</param>
|
public ViewExecutor(
|
||||||
/// <returns>A <see cref="Task"/> that represents the asynchronous rendering.</returns>
|
IOptions<MvcViewOptions> viewOptions,
|
||||||
public static async Task ExecuteAsync(
|
ICompositeViewEngine viewEngine,
|
||||||
IView view,
|
TelemetrySource telemetry)
|
||||||
ActionContext actionContext,
|
|
||||||
ViewDataDictionary viewData,
|
|
||||||
ITempDataDictionary tempData,
|
|
||||||
HtmlHelperOptions htmlHelperOptions,
|
|
||||||
MediaTypeHeaderValue contentType)
|
|
||||||
{
|
{
|
||||||
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)
|
if (actionContext == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(actionContext));
|
throw new ArgumentNullException(nameof(actionContext));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (view == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(view));
|
||||||
|
}
|
||||||
|
|
||||||
if (viewData == null)
|
if (viewData == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(viewData));
|
throw new ArgumentNullException(nameof(viewData));
|
||||||
|
|
@ -56,11 +113,6 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
throw new ArgumentNullException(nameof(tempData));
|
throw new ArgumentNullException(nameof(tempData));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (htmlHelperOptions == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(htmlHelperOptions));
|
|
||||||
}
|
|
||||||
|
|
||||||
var response = actionContext.HttpContext.Response;
|
var response = actionContext.HttpContext.Response;
|
||||||
|
|
||||||
if (contentType != null && contentType.Encoding == null)
|
if (contentType != null && contentType.Encoding == null)
|
||||||
|
|
@ -76,7 +128,13 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
// 3. ViewExecutor.DefaultContentType (sensible default)
|
// 3. ViewExecutor.DefaultContentType (sensible default)
|
||||||
response.ContentType = contentType?.ToString() ?? response.ContentType ?? DefaultContentType.ToString();
|
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(
|
var viewContext = new ViewContext(
|
||||||
actionContext,
|
actionContext,
|
||||||
|
|
@ -84,12 +142,27 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
viewData,
|
viewData,
|
||||||
tempData,
|
tempData,
|
||||||
writer,
|
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);
|
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
|
if (Telemetry.IsEnabled("Microsoft.AspNet.Mvc.AfterView"))
|
||||||
// as part of the Dispose which has a perf impact.
|
{
|
||||||
|
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();
|
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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics.Tracing;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Mvc.ViewEngines;
|
using Microsoft.AspNet.Mvc.ViewEngines;
|
||||||
using Microsoft.AspNet.Mvc.ViewFeatures;
|
using Microsoft.AspNet.Mvc.ViewFeatures;
|
||||||
using Microsoft.Framework.DependencyInjection;
|
using Microsoft.Framework.DependencyInjection;
|
||||||
using Microsoft.Framework.Logging;
|
|
||||||
using Microsoft.Framework.OptionsModel;
|
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc
|
namespace Microsoft.AspNet.Mvc
|
||||||
|
|
@ -62,60 +59,15 @@ namespace Microsoft.AspNet.Mvc
|
||||||
}
|
}
|
||||||
|
|
||||||
var services = context.HttpContext.RequestServices;
|
var services = context.HttpContext.RequestServices;
|
||||||
var viewEngine = ViewEngine ?? services.GetRequiredService<ICompositeViewEngine>();
|
var executor = services.GetRequiredService<ViewResultExecutor>();
|
||||||
|
|
||||||
var logger = services.GetRequiredService<ILogger<ViewResult>>();
|
var result = executor.FindView(context, this);
|
||||||
var telemetry = services.GetRequiredService<TelemetrySource>();
|
result.EnsureSuccessful();
|
||||||
|
|
||||||
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 view = result.View;
|
||||||
using (view as IDisposable)
|
using (view as IDisposable)
|
||||||
{
|
{
|
||||||
await ViewExecutor.ExecuteAsync(
|
await executor.ExecuteAsync(context, view, this);
|
||||||
view,
|
|
||||||
context,
|
|
||||||
ViewData,
|
|
||||||
TempData,
|
|
||||||
options.Value.HtmlHelperOptions,
|
|
||||||
ContentType);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 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 IProxyActionResult Result { get; set; }
|
||||||
public string ViewName { get; set; }
|
public string ViewName { get; set; }
|
||||||
public IProxyView View { get; set; }
|
public IProxyView View { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public OnViewResultViewFoundEventData ViewResultViewFound { get; set; }
|
public OnViewFoundEventData ViewFound { get; set; }
|
||||||
|
|
||||||
[TelemetryName("Microsoft.AspNet.Mvc.ViewResultViewFound")]
|
[TelemetryName("Microsoft.AspNet.Mvc.ViewFound")]
|
||||||
public virtual void OnViewResultViewFound(
|
public virtual void OnViewFound(
|
||||||
IProxyActionContext actionContext,
|
IProxyActionContext actionContext,
|
||||||
|
bool isPartial,
|
||||||
IProxyActionResult result,
|
IProxyActionResult result,
|
||||||
string viewName,
|
string viewName,
|
||||||
IProxyView view)
|
IProxyView view)
|
||||||
{
|
{
|
||||||
ViewResultViewFound = new OnViewResultViewFoundEventData()
|
ViewFound = new OnViewFoundEventData()
|
||||||
{
|
{
|
||||||
ActionContext = actionContext,
|
ActionContext = actionContext,
|
||||||
|
IsPartial = isPartial,
|
||||||
Result = result,
|
Result = result,
|
||||||
ViewName = viewName,
|
ViewName = viewName,
|
||||||
View = view,
|
View = view,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public class OnViewResultViewNotFoundEventData
|
public class OnViewNotFoundEventData
|
||||||
{
|
{
|
||||||
public IProxyActionContext ActionContext { get; set; }
|
public IProxyActionContext ActionContext { get; set; }
|
||||||
|
public bool IsPartial { get; set; }
|
||||||
public IProxyActionResult Result { get; set; }
|
public IProxyActionResult Result { get; set; }
|
||||||
public string ViewName { get; set; }
|
public string ViewName { get; set; }
|
||||||
public IEnumerable<string> SearchedLocations { get; set; }
|
public IEnumerable<string> SearchedLocations { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public OnViewResultViewNotFoundEventData ViewResultViewNotFound { get; set; }
|
public OnViewNotFoundEventData ViewNotFound { get; set; }
|
||||||
|
|
||||||
[TelemetryName("Microsoft.AspNet.Mvc.ViewResultViewNotFound")]
|
[TelemetryName("Microsoft.AspNet.Mvc.ViewNotFound")]
|
||||||
public virtual void OnViewResultViewNotFound(
|
public virtual void OnViewNotFound(
|
||||||
IProxyActionContext actionContext,
|
IProxyActionContext actionContext,
|
||||||
|
bool isPartial,
|
||||||
IProxyActionResult result,
|
IProxyActionResult result,
|
||||||
string viewName,
|
string viewName,
|
||||||
IEnumerable<string> searchedLocations)
|
IEnumerable<string> searchedLocations)
|
||||||
{
|
{
|
||||||
ViewResultViewNotFound = new OnViewResultViewNotFoundEventData()
|
ViewNotFound = new OnViewNotFoundEventData()
|
||||||
{
|
{
|
||||||
ActionContext = actionContext,
|
ActionContext = actionContext,
|
||||||
|
IsPartial = isPartial,
|
||||||
Result = result,
|
Result = result,
|
||||||
ViewName = viewName,
|
ViewName = viewName,
|
||||||
SearchedLocations = searchedLocations,
|
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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Text;
|
using System.Diagnostics.Tracing;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
using Microsoft.AspNet.Http.Internal;
|
using Microsoft.AspNet.Http.Internal;
|
||||||
using Microsoft.AspNet.Mvc.Abstractions;
|
using Microsoft.AspNet.Mvc.Abstractions;
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
using Microsoft.AspNet.Mvc.Rendering;
|
||||||
using Microsoft.AspNet.Mvc.ViewEngines;
|
using Microsoft.AspNet.Mvc.ViewEngines;
|
||||||
using Microsoft.AspNet.Mvc.ViewFeatures;
|
using Microsoft.AspNet.Mvc.ViewFeatures;
|
||||||
using Microsoft.AspNet.Routing;
|
using Microsoft.AspNet.Routing;
|
||||||
using Microsoft.Framework.Logging;
|
using Microsoft.Framework.DependencyInjection;
|
||||||
using Microsoft.Framework.OptionsModel;
|
using Microsoft.Framework.Logging.Testing;
|
||||||
using Microsoft.Net.Http.Headers;
|
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc
|
namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
|
// These tests cover the logic included in PartialViewResult.ExecuteResultAsync - see PartialViewResultExecutorTest
|
||||||
|
// and ViewExecutorTest for more comprehensive tests.
|
||||||
public class PartialViewResultTest
|
public class PartialViewResultTest
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ExecuteResultAsync_ReturnsError_IfViewCouldNotBeFound()
|
public async Task ExecuteResultAsync_Throws_IfViewCouldNotBeFound()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expected = string.Join(
|
var expected = string.Join(
|
||||||
|
|
@ -30,10 +32,9 @@ namespace Microsoft.AspNet.Mvc
|
||||||
"The view 'MyView' was not found. The following locations were searched:",
|
"The view 'MyView' was not found. The following locations were searched:",
|
||||||
"Location1",
|
"Location1",
|
||||||
"Location2.");
|
"Location2.");
|
||||||
var actionContext = new ActionContext(
|
|
||||||
GetHttpContext(),
|
var actionContext = GetActionContext();
|
||||||
new RouteData(),
|
|
||||||
new ActionDescriptor());
|
|
||||||
var viewEngine = new Mock<IViewEngine>();
|
var viewEngine = new Mock<IViewEngine>();
|
||||||
viewEngine
|
viewEngine
|
||||||
.Setup(v => v.FindPartialView(It.IsAny<ActionContext>(), It.IsAny<string>()))
|
.Setup(v => v.FindPartialView(It.IsAny<ActionContext>(), It.IsAny<string>()))
|
||||||
|
|
@ -56,17 +57,27 @@ namespace Microsoft.AspNet.Mvc
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task PartialViewResult_UsesFindPartialViewOnSpecifiedViewEngineToLocateViews()
|
public async Task ExecuteResultAsync_FindsAndExecutesView()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var viewName = "myview";
|
var viewName = "myview";
|
||||||
var context = new ActionContext(GetHttpContext(), new RouteData(), new ActionDescriptor());
|
var context = GetActionContext();
|
||||||
var viewEngine = new Mock<IViewEngine>();
|
|
||||||
var view = Mock.Of<IView>();
|
|
||||||
|
|
||||||
|
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
|
viewEngine
|
||||||
.Setup(e => e.FindPartialView(context, "myview"))
|
.Setup(e => e.FindPartialView(context, "myview"))
|
||||||
.Returns(ViewEngineResult.Found("myview", view))
|
.Returns(ViewEngineResult.Found("myview", view.Object))
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
|
|
||||||
var viewResult = new PartialViewResult
|
var viewResult = new PartialViewResult
|
||||||
|
|
@ -81,185 +92,29 @@ namespace Microsoft.AspNet.Mvc
|
||||||
await viewResult.ExecuteResultAsync(context);
|
await viewResult.ExecuteResultAsync(context);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
view.Verify();
|
||||||
viewEngine.Verify();
|
viewEngine.Verify();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TheoryData<MediaTypeHeaderValue, string> PartialViewResultContentTypeData
|
private ActionContext GetActionContext()
|
||||||
{
|
{
|
||||||
get
|
return new ActionContext(GetHttpContext(), new RouteData(), new ActionDescriptor());
|
||||||
{
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private HttpContext GetHttpContext()
|
private HttpContext GetHttpContext()
|
||||||
{
|
{
|
||||||
var serviceProvider = new Mock<IServiceProvider>();
|
var options = new TestOptionsManager<MvcViewOptions>();
|
||||||
serviceProvider.Setup(s => s.GetService(typeof(ILogger<PartialViewResult>)))
|
var viewExecutor = new PartialViewResultExecutor(
|
||||||
.Returns(new Mock<ILogger<PartialViewResult>>().Object);
|
options,
|
||||||
|
new CompositeViewEngine(options),
|
||||||
|
new TelemetryListener("Microsoft.AspNet"),
|
||||||
|
NullLoggerFactory.Instance);
|
||||||
|
|
||||||
serviceProvider.Setup(s => s.GetService(typeof(IOptions<MvcViewOptions>)))
|
var services = new ServiceCollection();
|
||||||
.Returns(() => {
|
services.AddInstance(viewExecutor);
|
||||||
var optionsAccessor = new Mock<IOptions<MvcViewOptions>>();
|
|
||||||
optionsAccessor.SetupGet(o => o.Value)
|
|
||||||
.Returns(new MvcViewOptions());
|
|
||||||
return optionsAccessor.Object;
|
|
||||||
});
|
|
||||||
|
|
||||||
var httpContext = new DefaultHttpContext();
|
var httpContext = new DefaultHttpContext();
|
||||||
httpContext.RequestServices = serviceProvider.Object;
|
httpContext.RequestServices = services.BuildServiceProvider();
|
||||||
|
|
||||||
return httpContext;
|
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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics.Tracing;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Http.Internal;
|
using Microsoft.AspNet.Http.Internal;
|
||||||
|
|
@ -19,59 +21,51 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
{
|
{
|
||||||
public class ViewExecutorTest
|
public class ViewExecutorTest
|
||||||
{
|
{
|
||||||
public static TheoryData<MediaTypeHeaderValue, string, string, byte[]> ViewExecutorSetsContentTypeAndEncodingData
|
public static TheoryData<MediaTypeHeaderValue, string, string> ViewExecutorSetsContentTypeAndEncodingData
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return new TheoryData<MediaTypeHeaderValue, string, string, byte[]>
|
return new TheoryData<MediaTypeHeaderValue, string, string>
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
"text/html; charset=utf-8",
|
"text/html; charset=utf-8"
|
||||||
new byte[] { 97, 98, 99, 100 }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
new MediaTypeHeaderValue("text/foo"),
|
new MediaTypeHeaderValue("text/foo"),
|
||||||
null,
|
null,
|
||||||
"text/foo; charset=utf-8",
|
"text/foo; charset=utf-8"
|
||||||
new byte[] { 97, 98, 99, 100 }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
MediaTypeHeaderValue.Parse("text/foo; p1=p1-value"),
|
MediaTypeHeaderValue.Parse("text/foo; p1=p1-value"),
|
||||||
null,
|
null,
|
||||||
"text/foo; p1=p1-value; charset=utf-8",
|
"text/foo; p1=p1-value; charset=utf-8"
|
||||||
new byte[] { 97, 98, 99, 100 }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
new MediaTypeHeaderValue("text/foo") { Charset = "us-ascii" },
|
new MediaTypeHeaderValue("text/foo") { Charset = "us-ascii" },
|
||||||
null,
|
null,
|
||||||
"text/foo; charset=us-ascii",
|
"text/foo; charset=us-ascii"
|
||||||
new byte[] { 97, 98, 99, 100 }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
null,
|
null,
|
||||||
"text/bar",
|
"text/bar",
|
||||||
"text/bar",
|
"text/bar"
|
||||||
new byte[] { 97, 98, 99, 100 }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
null,
|
null,
|
||||||
"application/xml; charset=us-ascii",
|
"application/xml; charset=us-ascii",
|
||||||
"application/xml; charset=us-ascii",
|
"application/xml; charset=us-ascii"
|
||||||
new byte[] { 97, 98, 99, 100 }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
null,
|
null,
|
||||||
"Invalid content type",
|
"Invalid content type",
|
||||||
"Invalid content type",
|
"Invalid content type"
|
||||||
new byte[] { 97, 98, 99, 100 }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
new MediaTypeHeaderValue("text/foo") { Charset = "us-ascii" },
|
new MediaTypeHeaderValue("text/foo") { Charset = "us-ascii" },
|
||||||
"text/bar",
|
"text/bar",
|
||||||
"text/foo; charset=us-ascii",
|
"text/foo; charset=us-ascii"
|
||||||
new byte[] { 97, 98, 99, 100 }
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -82,17 +76,13 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
public async Task ExecuteAsync_SetsContentTypeAndEncoding(
|
public async Task ExecuteAsync_SetsContentTypeAndEncoding(
|
||||||
MediaTypeHeaderValue contentType,
|
MediaTypeHeaderValue contentType,
|
||||||
string responseContentType,
|
string responseContentType,
|
||||||
string expectedContentType,
|
string expectedContentType)
|
||||||
byte[] expectedContentData)
|
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var view = new Mock<IView>();
|
var view = CreateView(async (v) =>
|
||||||
view.Setup(v => v.RenderAsync(It.IsAny<ViewContext>()))
|
{
|
||||||
.Callback((ViewContext v) =>
|
await v.Writer.WriteAsync("abcd");
|
||||||
{
|
});
|
||||||
v.Writer.Write("abcd");
|
|
||||||
})
|
|
||||||
.Returns(Task.FromResult(0));
|
|
||||||
|
|
||||||
var context = new DefaultHttpContext();
|
var context = new DefaultHttpContext();
|
||||||
var memoryStream = new MemoryStream();
|
var memoryStream = new MemoryStream();
|
||||||
|
|
@ -105,18 +95,99 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
new ActionDescriptor());
|
new ActionDescriptor());
|
||||||
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider());
|
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider());
|
||||||
|
|
||||||
|
var viewExecutor = CreateViewExecutor();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await ViewExecutor.ExecuteAsync(
|
await viewExecutor.ExecuteAsync(
|
||||||
view.Object,
|
|
||||||
actionContext,
|
actionContext,
|
||||||
|
view,
|
||||||
viewData,
|
viewData,
|
||||||
Mock.Of<ITempDataDictionary>(),
|
Mock.Of<ITempDataDictionary>(),
|
||||||
new HtmlHelperOptions(),
|
contentType,
|
||||||
contentType);
|
statusCode: null);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(expectedContentType, context.Response.ContentType);
|
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]
|
[Fact]
|
||||||
|
|
@ -142,15 +213,16 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
new ActionDescriptor());
|
new ActionDescriptor());
|
||||||
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider());
|
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider());
|
||||||
|
|
||||||
|
var viewExecutor = CreateViewExecutor();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await Record.ExceptionAsync(
|
await Record.ExceptionAsync(() => viewExecutor.ExecuteAsync(
|
||||||
() => ViewExecutor.ExecuteAsync(
|
actionContext,
|
||||||
view.Object,
|
view.Object,
|
||||||
actionContext,
|
viewData,
|
||||||
viewData,
|
Mock.Of<ITempDataDictionary>(),
|
||||||
null,
|
contentType: null,
|
||||||
new HtmlHelperOptions(),
|
statusCode: null));
|
||||||
contentType: null));
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(expectedLength, memoryStream.Length);
|
Assert.Equal(expectedLength, memoryStream.Length);
|
||||||
|
|
@ -163,14 +235,11 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
public async Task ExecuteAsync_AsynchronouslyFlushesToTheResponseStream_PriorToDispose(int writeLength)
|
public async Task ExecuteAsync_AsynchronouslyFlushesToTheResponseStream_PriorToDispose(int writeLength)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var view = new Mock<IView>();
|
var view = CreateView(async (v) =>
|
||||||
view.Setup(v => v.RenderAsync(It.IsAny<ViewContext>()))
|
{
|
||||||
.Returns((ViewContext v) =>
|
var text = new string('a', writeLength);
|
||||||
Task.Run(async () =>
|
await v.Writer.WriteAsync(text);
|
||||||
{
|
});
|
||||||
var text = new string('a', writeLength);
|
|
||||||
await v.Writer.WriteAsync(text);
|
|
||||||
}));
|
|
||||||
|
|
||||||
var context = new DefaultHttpContext();
|
var context = new DefaultHttpContext();
|
||||||
var stream = new Mock<Stream>();
|
var stream = new Mock<Stream>();
|
||||||
|
|
@ -182,18 +251,43 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
|
||||||
new ActionDescriptor());
|
new ActionDescriptor());
|
||||||
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider());
|
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider());
|
||||||
|
|
||||||
|
var viewExecutor = CreateViewExecutor();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await ViewExecutor.ExecuteAsync(
|
await viewExecutor.ExecuteAsync(
|
||||||
view.Object,
|
|
||||||
actionContext,
|
actionContext,
|
||||||
|
view,
|
||||||
viewData,
|
viewData,
|
||||||
Mock.Of<ITempDataDictionary>(),
|
Mock.Of<ITempDataDictionary>(),
|
||||||
new HtmlHelperOptions(),
|
contentType: null,
|
||||||
ViewExecutor.DefaultContentType);
|
statusCode: null);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
stream.Verify(s => s.FlushAsync(It.IsAny<CancellationToken>()), Times.Once());
|
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());
|
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;
|
||||||
using System.Diagnostics.Tracing;
|
using System.Diagnostics.Tracing;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
using Microsoft.AspNet.Http.Internal;
|
using Microsoft.AspNet.Http.Internal;
|
||||||
using Microsoft.AspNet.Mvc.Abstractions;
|
using Microsoft.AspNet.Mvc.Abstractions;
|
||||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
using Microsoft.AspNet.Mvc.Rendering;
|
||||||
using Microsoft.AspNet.Mvc.ViewEngines;
|
using Microsoft.AspNet.Mvc.ViewEngines;
|
||||||
using Microsoft.AspNet.Mvc.ViewFeatures;
|
using Microsoft.AspNet.Mvc.ViewFeatures;
|
||||||
using Microsoft.AspNet.Routing;
|
using Microsoft.AspNet.Routing;
|
||||||
using Microsoft.Framework.DependencyInjection;
|
using Microsoft.Framework.DependencyInjection;
|
||||||
using Microsoft.Framework.Logging;
|
using Microsoft.Framework.Logging;
|
||||||
using Microsoft.Framework.OptionsModel;
|
using Microsoft.Framework.Logging.Testing;
|
||||||
using Microsoft.Net.Http.Headers;
|
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc
|
namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
|
// These tests cover the logic included in ViewResult.ExecuteResultAsync - see ViewResultExecutorTest
|
||||||
|
// and ViewExecutorTest for more comprehensive tests.
|
||||||
public class ViewResultTest
|
public class ViewResultTest
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ExecuteResultAsync_ReturnsError_IfViewCouldNotBeFound()
|
public async Task ExecuteResultAsync_Throws_IfViewCouldNotBeFound()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expected = string.Join(Environment.NewLine,
|
var expected = string.Join(
|
||||||
"The view 'MyView' was not found. The following locations were searched:",
|
Environment.NewLine,
|
||||||
"Location1",
|
"The view 'MyView' was not found. The following locations were searched:",
|
||||||
"Location2.");
|
"Location1",
|
||||||
|
"Location2.");
|
||||||
var actionContext = new ActionContext(GetHttpContext(),
|
|
||||||
new RouteData(),
|
var actionContext = GetActionContext();
|
||||||
new ActionDescriptor());
|
|
||||||
var viewEngine = new Mock<IViewEngine>();
|
var viewEngine = new Mock<IViewEngine>();
|
||||||
viewEngine.Setup(v => v.FindView(It.IsAny<ActionContext>(), It.IsAny<string>()))
|
viewEngine
|
||||||
.Returns(ViewEngineResult.NotFound("MyView", new[] { "Location1", "Location2" }))
|
.Setup(v => v.FindView(It.IsAny<ActionContext>(), It.IsAny<string>()))
|
||||||
.Verifiable();
|
.Returns(ViewEngineResult.NotFound("MyView", new[] { "Location1", "Location2" }))
|
||||||
|
.Verifiable();
|
||||||
|
|
||||||
var viewResult = new ViewResult
|
var viewResult = new ViewResult
|
||||||
{
|
{
|
||||||
|
|
@ -56,17 +58,27 @@ namespace Microsoft.AspNet.Mvc
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ViewResult_UsesFindViewOnSpecifiedViewEngineToLocateViews()
|
public async Task ExecuteResultAsync_FindsAndExecutesView()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var viewName = "myview";
|
var viewName = "myview";
|
||||||
var context = new ActionContext(GetHttpContext(), new RouteData(), new ActionDescriptor());
|
var context = GetActionContext();
|
||||||
var viewEngine = new Mock<IViewEngine>();
|
|
||||||
var view = Mock.Of<IView>();
|
|
||||||
|
|
||||||
|
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
|
viewEngine
|
||||||
.Setup(e => e.FindView(context, "myview"))
|
.Setup(e => e.FindView(context, "myview"))
|
||||||
.Returns(ViewEngineResult.Found("myview", view))
|
.Returns(ViewEngineResult.Found("myview", view.Object))
|
||||||
.Verifiable();
|
.Verifiable();
|
||||||
|
|
||||||
var viewResult = new ViewResult
|
var viewResult = new ViewResult
|
||||||
|
|
@ -81,273 +93,29 @@ namespace Microsoft.AspNet.Mvc
|
||||||
await viewResult.ExecuteResultAsync(context);
|
await viewResult.ExecuteResultAsync(context);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
view.Verify();
|
||||||
viewEngine.Verify();
|
viewEngine.Verify();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TheoryData<MediaTypeHeaderValue, string> ViewResultContentTypeData
|
private ActionContext GetActionContext()
|
||||||
{
|
{
|
||||||
get
|
return new ActionContext(GetHttpContext(), new RouteData(), new ActionDescriptor());
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private HttpContext GetHttpContext()
|
private HttpContext GetHttpContext()
|
||||||
{
|
{
|
||||||
var serviceProvider = new Mock<IServiceProvider>();
|
var options = new TestOptionsManager<MvcViewOptions>();
|
||||||
serviceProvider.Setup(s => s.GetService(typeof(ILogger<ViewResult>)))
|
var viewExecutor = new ViewResultExecutor(
|
||||||
.Returns(new Mock<ILogger<ViewResult>>().Object);
|
options,
|
||||||
|
new CompositeViewEngine(options),
|
||||||
|
new TelemetryListener("Microsoft.AspNet"),
|
||||||
|
NullLoggerFactory.Instance);
|
||||||
|
|
||||||
var optionsAccessor = new Mock<IOptions<MvcViewOptions>>();
|
var services = new ServiceCollection();
|
||||||
optionsAccessor.SetupGet(o => o.Value)
|
services.AddInstance(viewExecutor);
|
||||||
.Returns(new MvcViewOptions());
|
|
||||||
|
|
||||||
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();
|
var httpContext = new DefaultHttpContext();
|
||||||
httpContext.RequestServices = serviceProvider.Object;
|
httpContext.RequestServices = services.BuildServiceProvider();
|
||||||
|
|
||||||
return httpContext;
|
return httpContext;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue