Add more event notification data points

Also includes some cleanup of the testing code that we're using with
proxies.
This commit is contained in:
Ryan Nowak 2015-08-16 16:21:41 -07:00
parent 07fabde92a
commit e384938425
15 changed files with 294 additions and 58 deletions

View File

@ -10,6 +10,7 @@ using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ModelBinding.Validation; using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Microsoft.Framework.Internal; using Microsoft.Framework.Internal;
using Microsoft.Framework.Logging; using Microsoft.Framework.Logging;
using Microsoft.Framework.Notification;
namespace Microsoft.AspNet.Mvc.Core namespace Microsoft.AspNet.Mvc.Core
{ {
@ -32,6 +33,7 @@ namespace Microsoft.AspNet.Mvc.Core
[NotNull] IReadOnlyList<IValueProviderFactory> valueProviderFactories, [NotNull] IReadOnlyList<IValueProviderFactory> valueProviderFactories,
[NotNull] IActionBindingContextAccessor actionBindingContextAccessor, [NotNull] IActionBindingContextAccessor actionBindingContextAccessor,
[NotNull] ILogger logger, [NotNull] ILogger logger,
[NotNull] INotifier notifier,
int maxModelValidationErrors) int maxModelValidationErrors)
: base( : base(
actionContext, actionContext,
@ -43,6 +45,7 @@ namespace Microsoft.AspNet.Mvc.Core
valueProviderFactories, valueProviderFactories,
actionBindingContextAccessor, actionBindingContextAccessor,
logger, logger,
notifier,
maxModelValidationErrors) maxModelValidationErrors)
{ {
_descriptor = descriptor; _descriptor = descriptor;

View File

@ -7,6 +7,7 @@ using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ModelBinding.Validation; using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Microsoft.Framework.Internal; using Microsoft.Framework.Internal;
using Microsoft.Framework.Logging; using Microsoft.Framework.Logging;
using Microsoft.Framework.Notification;
using Microsoft.Framework.OptionsModel; using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Mvc.Core namespace Microsoft.AspNet.Mvc.Core
@ -24,6 +25,7 @@ namespace Microsoft.AspNet.Mvc.Core
private readonly IActionBindingContextAccessor _actionBindingContextAccessor; private readonly IActionBindingContextAccessor _actionBindingContextAccessor;
private readonly int _maxModelValidationErrors; private readonly int _maxModelValidationErrors;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly INotifier _notifier;
public ControllerActionInvokerProvider( public ControllerActionInvokerProvider(
IControllerFactory controllerFactory, IControllerFactory controllerFactory,
@ -31,7 +33,8 @@ namespace Microsoft.AspNet.Mvc.Core
IControllerActionArgumentBinder argumentBinder, IControllerActionArgumentBinder argumentBinder,
IOptions<MvcOptions> optionsAccessor, IOptions<MvcOptions> optionsAccessor,
IActionBindingContextAccessor actionBindingContextAccessor, IActionBindingContextAccessor actionBindingContextAccessor,
ILoggerFactory loggerFactory) ILoggerFactory loggerFactory,
INotifier notifier)
{ {
_controllerFactory = controllerFactory; _controllerFactory = controllerFactory;
_filterProviders = filterProviders.OrderBy(item => item.Order).ToArray(); _filterProviders = filterProviders.OrderBy(item => item.Order).ToArray();
@ -43,8 +46,8 @@ namespace Microsoft.AspNet.Mvc.Core
_valueProviderFactories = optionsAccessor.Options.ValueProviderFactories.ToArray(); _valueProviderFactories = optionsAccessor.Options.ValueProviderFactories.ToArray();
_actionBindingContextAccessor = actionBindingContextAccessor; _actionBindingContextAccessor = actionBindingContextAccessor;
_maxModelValidationErrors = optionsAccessor.Options.MaxModelValidationErrors; _maxModelValidationErrors = optionsAccessor.Options.MaxModelValidationErrors;
_logger = loggerFactory.CreateLogger<ControllerActionInvoker>(); _logger = loggerFactory.CreateLogger<ControllerActionInvoker>();
_notifier = notifier;
} }
public int Order public int Order
@ -72,6 +75,7 @@ namespace Microsoft.AspNet.Mvc.Core
_valueProviderFactories, _valueProviderFactories,
_actionBindingContextAccessor, _actionBindingContextAccessor,
_logger, _logger,
_notifier,
_maxModelValidationErrors); _maxModelValidationErrors);
} }
} }

View File

@ -11,6 +11,7 @@ using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ModelBinding.Validation; using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Microsoft.Framework.Internal; using Microsoft.Framework.Internal;
using Microsoft.Framework.Logging; using Microsoft.Framework.Logging;
using Microsoft.Framework.Notification;
namespace Microsoft.AspNet.Mvc.Core namespace Microsoft.AspNet.Mvc.Core
{ {
@ -24,6 +25,7 @@ namespace Microsoft.AspNet.Mvc.Core
private readonly IReadOnlyList<IValueProviderFactory> _valueProviderFactories; private readonly IReadOnlyList<IValueProviderFactory> _valueProviderFactories;
private readonly IActionBindingContextAccessor _actionBindingContextAccessor; private readonly IActionBindingContextAccessor _actionBindingContextAccessor;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly INotifier _notifier;
private readonly int _maxModelValidationErrors; private readonly int _maxModelValidationErrors;
private IFilterMetadata[] _filters; private IFilterMetadata[] _filters;
@ -63,6 +65,7 @@ namespace Microsoft.AspNet.Mvc.Core
[NotNull] IReadOnlyList<IValueProviderFactory> valueProviderFactories, [NotNull] IReadOnlyList<IValueProviderFactory> valueProviderFactories,
[NotNull] IActionBindingContextAccessor actionBindingContextAccessor, [NotNull] IActionBindingContextAccessor actionBindingContextAccessor,
[NotNull] ILogger logger, [NotNull] ILogger logger,
[NotNull] INotifier notifier,
int maxModelValidationErrors) int maxModelValidationErrors)
{ {
ActionContext = actionContext; ActionContext = actionContext;
@ -75,6 +78,7 @@ namespace Microsoft.AspNet.Mvc.Core
_valueProviderFactories = valueProviderFactories; _valueProviderFactories = valueProviderFactories;
_actionBindingContextAccessor = actionBindingContextAccessor; _actionBindingContextAccessor = actionBindingContextAccessor;
_logger = logger; _logger = logger;
_notifier = notifier;
_maxModelValidationErrors = maxModelValidationErrors; _maxModelValidationErrors = maxModelValidationErrors;
} }
@ -127,7 +131,7 @@ namespace Microsoft.AspNet.Mvc.Core
Debug.Assert(_authorizationContext != null); Debug.Assert(_authorizationContext != null);
if (_authorizationContext.Result != null) if (_authorizationContext.Result != null)
{ {
await _authorizationContext.Result.ExecuteResultAsync(ActionContext); await InvokeResultAsync(_authorizationContext.Result);
return; return;
} }
@ -281,7 +285,7 @@ namespace Microsoft.AspNet.Mvc.Core
ResourceFilterShortCircuitLogMessage, ResourceFilterShortCircuitLogMessage,
item.FilterAsync.GetType().FullName); item.FilterAsync.GetType().FullName);
await _resourceExecutingContext.Result.ExecuteResultAsync(ActionContext); await InvokeResultAsync(_resourceExecutingContext.Result);
} }
_resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters) _resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters)
@ -301,7 +305,7 @@ namespace Microsoft.AspNet.Mvc.Core
_logger.LogVerbose(ResourceFilterShortCircuitLogMessage, item.Filter.GetType().FullName); _logger.LogVerbose(ResourceFilterShortCircuitLogMessage, item.Filter.GetType().FullName);
await _resourceExecutingContext.Result.ExecuteResultAsync(ActionContext); await InvokeResultAsync(_resourceExecutingContext.Result);
_resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters) _resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters)
{ {
@ -343,7 +347,7 @@ namespace Microsoft.AspNet.Mvc.Core
{ {
// This means that exception filters returned a result to 'handle' an error. // This means that exception filters returned a result to 'handle' an error.
// We're not interested in seeing the exception details since it was handled. // We're not interested in seeing the exception details since it was handled.
await _exceptionContext.Result.ExecuteResultAsync(ActionContext); await InvokeResultAsync(_exceptionContext.Result);
_resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters) _resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters)
{ {
@ -558,12 +562,35 @@ namespace Microsoft.AspNet.Mvc.Core
else else
{ {
// All action filters have run, execute the action method. // All action filters have run, execute the action method.
IActionResult result = null;
try
{
if (_notifier.ShouldNotify("Microsoft.AspNet.Mvc.BeforeActionMethod"))
{
_notifier.Notify(
"Microsoft.AspNet.Mvc.BeforeActionMethod",
new { actionContext = ActionContext, arguments = _actionExecutingContext.ActionArguments });
}
result = await InvokeActionAsync(_actionExecutingContext);
}
finally
{
if (_notifier.ShouldNotify("Microsoft.AspNet.Mvc.AfterActionMethod"))
{
_notifier.Notify(
"Microsoft.AspNet.Mvc.AfterActionMethod",
new { actionContext = ActionContext, result });
}
}
_actionExecutedContext = new ActionExecutedContext( _actionExecutedContext = new ActionExecutedContext(
_actionExecutingContext, _actionExecutingContext,
_filters, _filters,
Instance) Instance)
{ {
Result = await InvokeActionAsync(_actionExecutingContext), Result = result
}; };
} }
} }
@ -683,7 +710,15 @@ namespace Microsoft.AspNet.Mvc.Core
} }
else else
{ {
await InvokeResultAsync(); _cursor.SetStage(FilterStage.ActionResult);
// The empty result is always flowed back as the 'executed' result
if (_resultExecutingContext.Result == null)
{
_resultExecutingContext.Result = new EmptyResult();
}
await InvokeResultAsync(_resultExecutingContext.Result);
Debug.Assert(_resultExecutedContext == null); Debug.Assert(_resultExecutedContext == null);
_resultExecutedContext = new ResultExecutedContext( _resultExecutedContext = new ResultExecutedContext(
@ -708,17 +743,28 @@ namespace Microsoft.AspNet.Mvc.Core
return _resultExecutedContext; return _resultExecutedContext;
} }
private async Task InvokeResultAsync() private async Task InvokeResultAsync(IActionResult result)
{ {
_cursor.SetStage(FilterStage.ActionResult); if (_notifier.ShouldNotify("Microsoft.AspNet.Mvc.BeforeActionResult"))
// The empty result is always flowed back as the 'executed' result
if (_resultExecutingContext.Result == null)
{ {
_resultExecutingContext.Result = new EmptyResult(); _notifier.Notify(
"Microsoft.AspNet.Mvc.BeforeActionResult",
new { actionContext = ActionContext, result });
} }
await _resultExecutingContext.Result.ExecuteResultAsync(_resultExecutingContext); try
{
await result.ExecuteResultAsync(ActionContext);
}
finally
{
if (_notifier.ShouldNotify("Microsoft.AspNet.Mvc.AfterActionResult"))
{
_notifier.Notify(
"Microsoft.AspNet.Mvc.AfterActionResult",
new { actionContext = ActionContext, result });
}
}
} }
private enum FilterStage private enum FilterStage

View File

@ -70,10 +70,10 @@ namespace Microsoft.AspNet.Mvc
{ {
context.RouteData = newRouteData; context.RouteData = newRouteData;
if (_notifier.ShouldNotify("Microsoft.AspNet.Mvc.ActionSelected")) if (_notifier.ShouldNotify("Microsoft.AspNet.Mvc.BeforeAction"))
{ {
_notifier.Notify( _notifier.Notify(
"Microsoft.AspNet.Mvc.ActionSelected", "Microsoft.AspNet.Mvc.BeforeAction",
new { actionDescriptor, httpContext = context.HttpContext, routeData = context.RouteData}); new { actionDescriptor, httpContext = context.HttpContext, routeData = context.RouteData});
} }
@ -84,16 +84,16 @@ namespace Microsoft.AspNet.Mvc
await InvokeActionAsync(context, actionDescriptor); await InvokeActionAsync(context, actionDescriptor);
context.IsHandled = true; context.IsHandled = true;
} }
if (_notifier.ShouldNotify("Microsoft.AspNet.Mvc.ActionInvoked"))
{
_notifier.Notify(
"Microsoft.AspNet.Mvc.ActionInvoked",
new { actionDescriptor, httpContext = context.HttpContext });
}
} }
finally finally
{ {
if (_notifier.ShouldNotify("Microsoft.AspNet.Mvc.AfterAction"))
{
_notifier.Notify(
"Microsoft.AspNet.Mvc.AfterAction",
new { actionDescriptor, httpContext = context.HttpContext });
}
if (!context.IsHandled) if (!context.IsHandled)
{ {
context.RouteData = oldRouteData; context.RouteData = oldRouteData;

View File

@ -9,6 +9,7 @@ using Microsoft.Framework.Internal;
using Microsoft.Framework.Logging; using Microsoft.Framework.Logging;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
using Microsoft.Framework.OptionsModel; using Microsoft.Framework.OptionsModel;
using Microsoft.Framework.Notification;
namespace Microsoft.AspNet.Mvc namespace Microsoft.AspNet.Mvc
{ {
@ -55,17 +56,31 @@ namespace Microsoft.AspNet.Mvc
/// <inheritdoc /> /// <inheritdoc />
public override async Task ExecuteResultAsync([NotNull] ActionContext context) public override async Task ExecuteResultAsync([NotNull] ActionContext context)
{ {
var viewEngine = ViewEngine ?? var services = context.HttpContext.RequestServices;
context.HttpContext.RequestServices.GetRequiredService<ICompositeViewEngine>(); var viewEngine = ViewEngine ?? services.GetRequiredService<ICompositeViewEngine>();
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<ViewResult>>(); var logger = services.GetRequiredService<ILogger<ViewResult>>();
var notifier = services.GetRequiredService<INotifier>();
var options = context.HttpContext.RequestServices.GetRequiredService<IOptions<MvcViewOptions>>(); var options = services.GetRequiredService<IOptions<MvcViewOptions>>();
var viewName = ViewName ?? context.ActionDescriptor.Name; var viewName = ViewName ?? context.ActionDescriptor.Name;
var viewEngineResult = viewEngine.FindView(context, viewName); var viewEngineResult = viewEngine.FindView(context, viewName);
if(!viewEngineResult.Success) if(!viewEngineResult.Success)
{ {
if (notifier.ShouldNotify("Microsoft.AspNet.Mvc.ViewResultViewNotFound"))
{
notifier.Notify(
"Microsoft.AspNet.Mvc.ViewResultViewNotFound",
new
{
actionContext = context,
result = this,
viewName = viewName,
searchedLocations = viewEngineResult.SearchedLocations
});
}
logger.LogError( logger.LogError(
"The view '{ViewName}' was not found. Searched locations: {SearchedViewLocations}", "The view '{ViewName}' was not found. Searched locations: {SearchedViewLocations}",
viewName, viewName,
@ -73,6 +88,12 @@ namespace Microsoft.AspNet.Mvc
} }
var view = viewEngineResult.EnsureSuccessful().View; var view = viewEngineResult.EnsureSuccessful().View;
if (notifier.ShouldNotify("Microsoft.AspNet.Mvc.ViewResultViewFound"))
{
notifier.Notify(
"Microsoft.AspNet.Mvc.ViewResultViewFound",
new { actionContext = context, result = this, viewName, view = view });
}
logger.LogVerbose("The view '{ViewName}' was found.", viewName); logger.LogVerbose("The view '{ViewName}' was found.", viewName);

View File

@ -17,6 +17,7 @@ using Microsoft.AspNet.Testing;
using Microsoft.Framework.Internal; using Microsoft.Framework.Internal;
using Microsoft.Framework.Logging; using Microsoft.Framework.Logging;
using Microsoft.Framework.Logging.Testing; using Microsoft.Framework.Logging.Testing;
using Microsoft.Framework.Notification;
using Microsoft.Framework.OptionsModel; using Microsoft.Framework.OptionsModel;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
using Moq; using Moq;
@ -2042,6 +2043,7 @@ namespace Microsoft.AspNet.Mvc
new IValueProviderFactory[0], new IValueProviderFactory[0],
new ActionBindingContextAccessor(), new ActionBindingContextAccessor(),
new NullLoggerFactory().CreateLogger<ControllerActionInvoker>(), new NullLoggerFactory().CreateLogger<ControllerActionInvoker>(),
new Notifier(new ProxyNotifierMethodAdapter()),
maxAllowedErrorsInModelState); maxAllowedErrorsInModelState);
return invoker; return invoker;
@ -2104,6 +2106,7 @@ namespace Microsoft.AspNet.Mvc
new IValueProviderFactory[0], new IValueProviderFactory[0],
new ActionBindingContextAccessor(), new ActionBindingContextAccessor(),
new NullLoggerFactory().CreateLogger<ControllerActionInvoker>(), new NullLoggerFactory().CreateLogger<ControllerActionInvoker>(),
new Notifier(new ProxyNotifierMethodAdapter()),
200); 200);
// Act // Act
@ -2204,6 +2207,7 @@ namespace Microsoft.AspNet.Mvc
IReadOnlyList<IValueProviderFactory> valueProviderFactories, IReadOnlyList<IValueProviderFactory> valueProviderFactories,
IActionBindingContextAccessor actionBindingContext, IActionBindingContextAccessor actionBindingContext,
ILogger logger, ILogger logger,
INotifier notifier,
int maxAllowedErrorsInModelState) int maxAllowedErrorsInModelState)
: base( : base(
actionContext, actionContext,
@ -2218,6 +2222,7 @@ namespace Microsoft.AspNet.Mvc
valueProviderFactories, valueProviderFactories,
actionBindingContext, actionBindingContext,
logger, logger,
notifier,
maxAllowedErrorsInModelState) maxAllowedErrorsInModelState)
{ {
ControllerFactory = controllerFactory; ControllerFactory = controllerFactory;

View File

@ -169,10 +169,10 @@ namespace Microsoft.AspNet.Mvc
await handler.RouteAsync(context); await handler.RouteAsync(context);
// Assert // Assert
Assert.NotNull(listener?.ActionSelected.ActionDescriptor); Assert.NotNull(listener.BeforeAction?.ActionDescriptor);
Assert.NotNull(listener?.ActionSelected.HttpContext); Assert.NotNull(listener.BeforeAction?.HttpContext);
var routeValues = listener?.ActionSelected?.RouteData?.Values; var routeValues = listener.BeforeAction?.RouteData?.Values;
Assert.NotNull(routeValues); Assert.NotNull(routeValues);
Assert.Equal(1, routeValues.Count); Assert.Equal(1, routeValues.Count);
@ -193,8 +193,8 @@ namespace Microsoft.AspNet.Mvc
await handler.RouteAsync(context); await handler.RouteAsync(context);
// Assert // Assert
Assert.NotNull(listener?.ActionInvoked.ActionDescriptor); Assert.NotNull(listener.AfterAction?.ActionDescriptor);
Assert.NotNull(listener?.ActionInvoked.HttpContext); Assert.NotNull(listener.AfterAction?.HttpContext);
} }
private RouteContext CreateRouteContext( private RouteContext CreateRouteContext(

View File

@ -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.TestCommon.Notification
{
public interface IProxyActionContext
{
}
}

View File

@ -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.TestCommon.Notification
{
public interface IProxyActionDescriptor
{
}
}

View File

@ -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.TestCommon.Notification
{
public interface IProxyActionResult
{
}
}

View File

@ -3,7 +3,7 @@
namespace Microsoft.AspNet.Mvc.TestCommon.Notification namespace Microsoft.AspNet.Mvc.TestCommon.Notification
{ {
public interface IActionDescriptor public interface IProxyHttpContext
{ {
} }
} }

View File

@ -5,7 +5,7 @@ using System.Collections.Generic;
namespace Microsoft.AspNet.Mvc.TestCommon.Notification namespace Microsoft.AspNet.Mvc.TestCommon.Notification
{ {
public interface IRouteData public interface IProxyRouteData
{ {
IReadOnlyList<object> Routers { get; } IReadOnlyList<object> Routers { get; }
IDictionary<string, object> DataTokens { get; } IDictionary<string, object> DataTokens { get; }

View File

@ -3,7 +3,7 @@
namespace Microsoft.AspNet.Mvc.TestCommon.Notification namespace Microsoft.AspNet.Mvc.TestCommon.Notification
{ {
public interface IHttpContext public interface IProxyView
{ {
} }
} }

View File

@ -1,21 +1,29 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.Framework.Notification; using Microsoft.Framework.Notification;
namespace Microsoft.AspNet.Mvc.TestCommon.Notification namespace Microsoft.AspNet.Mvc.TestCommon.Notification
{ {
public class TestNotificationListener public class TestNotificationListener
{ {
public OnActionSelectedEventData ActionSelected { get; set; } public class OnBeforeActionEventData
[NotificationName("Microsoft.AspNet.Mvc.ActionSelected")]
public virtual void OnActionSelected(
IHttpContext httpContext,
IRouteData routeData,
IActionDescriptor actionDescriptor)
{ {
ActionSelected = new OnActionSelectedEventData() public IProxyActionDescriptor ActionDescriptor { get; set; }
public IProxyHttpContext HttpContext { get; set; }
public IProxyRouteData RouteData { get; set; }
}
public OnBeforeActionEventData BeforeAction { get; set; }
[NotificationName("Microsoft.AspNet.Mvc.BeforeAction")]
public virtual void OnBeforeAction(
IProxyHttpContext httpContext,
IProxyRouteData routeData,
IProxyActionDescriptor actionDescriptor)
{
BeforeAction = new OnBeforeActionEventData()
{ {
ActionDescriptor = actionDescriptor, ActionDescriptor = actionDescriptor,
HttpContext = httpContext, HttpContext = httpContext,
@ -23,31 +31,76 @@ namespace Microsoft.AspNet.Mvc.TestCommon.Notification
}; };
} }
public OnActionInvokedEventData ActionInvoked { get; set; } public class OnAfterActionEventData
[NotificationName("Microsoft.AspNet.Mvc.ActionInvoked")]
public virtual void OnActionInvoked(
IHttpContext httpContext,
IActionDescriptor actionDescriptor)
{ {
ActionInvoked = new OnActionInvokedEventData() public IProxyActionDescriptor ActionDescriptor { get; set; }
public IProxyHttpContext HttpContext { get; set; }
}
public OnAfterActionEventData AfterAction { get; set; }
[NotificationName("Microsoft.AspNet.Mvc.AfterAction")]
public virtual void OnAfterAction(
IProxyHttpContext httpContext,
IProxyActionDescriptor actionDescriptor)
{
AfterAction = new OnAfterActionEventData()
{ {
ActionDescriptor = actionDescriptor, ActionDescriptor = actionDescriptor,
HttpContext = httpContext, HttpContext = httpContext,
}; };
} }
public class OnActionSelectedEventData public class OnViewResultViewFoundEventData
{ {
public IActionDescriptor ActionDescriptor { get; set; } public IProxyActionContext ActionContext { get; set; }
public IHttpContext HttpContext { get; set; } public IProxyActionResult Result { get; set; }
public IRouteData RouteData { get; set; } public string ViewName { get; set; }
public IProxyView View { get; set; }
} }
public class OnActionInvokedEventData public OnViewResultViewFoundEventData ViewResultViewFound { get; set; }
[NotificationName("Microsoft.AspNet.Mvc.ViewResultViewFound")]
public virtual void OnViewResultViewFound(
IProxyActionContext actionContext,
IProxyActionResult result,
string viewName,
IProxyView view)
{ {
public IActionDescriptor ActionDescriptor { get; set; } ViewResultViewFound = new OnViewResultViewFoundEventData()
public IHttpContext HttpContext { get; set; } {
ActionContext = actionContext,
Result = result,
ViewName = viewName,
View = view,
};
}
public class OnViewResultViewNotFoundEventData
{
public IProxyActionContext ActionContext { get; set; }
public IProxyActionResult Result { get; set; }
public string ViewName { get; set; }
public IEnumerable<string> SearchedLocations { get; set; }
}
public OnViewResultViewNotFoundEventData ViewResultViewNotFound { get; set; }
[NotificationName("Microsoft.AspNet.Mvc.ViewResultViewNotFound")]
public virtual void OnViewResultViewNotFound(
IProxyActionContext actionContext,
IProxyActionResult result,
string viewName,
IEnumerable<string> searchedLocations)
{
ViewResultViewNotFound = new OnViewResultViewNotFoundEventData()
{
ActionContext = actionContext,
Result = result,
ViewName = viewName,
SearchedLocations = searchedLocations,
};
} }
} }
} }

View File

@ -7,8 +7,11 @@ 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.Rendering; using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Mvc.TestCommon.Notification;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Logging; using Microsoft.Framework.Logging;
using Microsoft.Framework.Notification;
using Microsoft.Framework.OptionsModel; using Microsoft.Framework.OptionsModel;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
using Moq; using Moq;
@ -203,6 +206,8 @@ namespace Microsoft.AspNet.Mvc
.Verifiable(); .Verifiable();
var serviceProvider = new Mock<IServiceProvider>(); var serviceProvider = new Mock<IServiceProvider>();
serviceProvider.Setup(s => s.GetService(typeof(INotifier)))
.Returns(new Notifier(new ProxyNotifierMethodAdapter()));
serviceProvider.Setup(p => p.GetService(typeof(ICompositeViewEngine))) serviceProvider.Setup(p => p.GetService(typeof(ICompositeViewEngine)))
.Returns(viewEngine.Object); .Returns(viewEngine.Object);
serviceProvider.Setup(p => p.GetService(typeof(ILogger<ViewResult>))) serviceProvider.Setup(p => p.GetService(typeof(ILogger<ViewResult>)))
@ -228,6 +233,75 @@ namespace Microsoft.AspNet.Mvc
viewEngine.Verify(); 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 TestNotificationListener();
httpContext.RequestServices.GetRequiredService<INotifier>().EnlistTarget(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,
};
// 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 TestNotificationListener();
httpContext.RequestServices.GetRequiredService<INotifier>().EnlistTarget(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,
};
// 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 serviceProvider = new Mock<IServiceProvider>();
@ -241,6 +315,9 @@ namespace Microsoft.AspNet.Mvc
serviceProvider.Setup(s => s.GetService(typeof(IOptions<MvcViewOptions>))) serviceProvider.Setup(s => s.GetService(typeof(IOptions<MvcViewOptions>)))
.Returns(optionsAccessor.Object); .Returns(optionsAccessor.Object);
serviceProvider.Setup(s => s.GetService(typeof(INotifier)))
.Returns(new Notifier(new ProxyNotifierMethodAdapter()));
var httpContext = new DefaultHttpContext(); var httpContext = new DefaultHttpContext();
httpContext.RequestServices = serviceProvider.Object; httpContext.RequestServices = serviceProvider.Object;