diff --git a/src/Microsoft.AspNet.Mvc.Core/ControllerActionInvoker.cs b/src/Microsoft.AspNet.Mvc.Core/ControllerActionInvoker.cs index 2feb17ab07..895801a598 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ControllerActionInvoker.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ControllerActionInvoker.cs @@ -10,6 +10,7 @@ using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding.Validation; using Microsoft.Framework.Internal; using Microsoft.Framework.Logging; +using Microsoft.Framework.Notification; namespace Microsoft.AspNet.Mvc.Core { @@ -32,6 +33,7 @@ namespace Microsoft.AspNet.Mvc.Core [NotNull] IReadOnlyList valueProviderFactories, [NotNull] IActionBindingContextAccessor actionBindingContextAccessor, [NotNull] ILogger logger, + [NotNull] INotifier notifier, int maxModelValidationErrors) : base( actionContext, @@ -43,6 +45,7 @@ namespace Microsoft.AspNet.Mvc.Core valueProviderFactories, actionBindingContextAccessor, logger, + notifier, maxModelValidationErrors) { _descriptor = descriptor; diff --git a/src/Microsoft.AspNet.Mvc.Core/ControllerActionInvokerProvider.cs b/src/Microsoft.AspNet.Mvc.Core/ControllerActionInvokerProvider.cs index 943e8acd26..8617f725fd 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ControllerActionInvokerProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ControllerActionInvokerProvider.cs @@ -7,6 +7,7 @@ using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding.Validation; using Microsoft.Framework.Internal; using Microsoft.Framework.Logging; +using Microsoft.Framework.Notification; using Microsoft.Framework.OptionsModel; namespace Microsoft.AspNet.Mvc.Core @@ -24,6 +25,7 @@ namespace Microsoft.AspNet.Mvc.Core private readonly IActionBindingContextAccessor _actionBindingContextAccessor; private readonly int _maxModelValidationErrors; private readonly ILogger _logger; + private readonly INotifier _notifier; public ControllerActionInvokerProvider( IControllerFactory controllerFactory, @@ -31,7 +33,8 @@ namespace Microsoft.AspNet.Mvc.Core IControllerActionArgumentBinder argumentBinder, IOptions optionsAccessor, IActionBindingContextAccessor actionBindingContextAccessor, - ILoggerFactory loggerFactory) + ILoggerFactory loggerFactory, + INotifier notifier) { _controllerFactory = controllerFactory; _filterProviders = filterProviders.OrderBy(item => item.Order).ToArray(); @@ -43,8 +46,8 @@ namespace Microsoft.AspNet.Mvc.Core _valueProviderFactories = optionsAccessor.Options.ValueProviderFactories.ToArray(); _actionBindingContextAccessor = actionBindingContextAccessor; _maxModelValidationErrors = optionsAccessor.Options.MaxModelValidationErrors; - _logger = loggerFactory.CreateLogger(); + _notifier = notifier; } public int Order @@ -72,6 +75,7 @@ namespace Microsoft.AspNet.Mvc.Core _valueProviderFactories, _actionBindingContextAccessor, _logger, + _notifier, _maxModelValidationErrors); } } diff --git a/src/Microsoft.AspNet.Mvc.Core/FilterActionInvoker.cs b/src/Microsoft.AspNet.Mvc.Core/FilterActionInvoker.cs index 7700a7c6d3..873eed3fa6 100644 --- a/src/Microsoft.AspNet.Mvc.Core/FilterActionInvoker.cs +++ b/src/Microsoft.AspNet.Mvc.Core/FilterActionInvoker.cs @@ -11,6 +11,7 @@ using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding.Validation; using Microsoft.Framework.Internal; using Microsoft.Framework.Logging; +using Microsoft.Framework.Notification; namespace Microsoft.AspNet.Mvc.Core { @@ -24,6 +25,7 @@ namespace Microsoft.AspNet.Mvc.Core private readonly IReadOnlyList _valueProviderFactories; private readonly IActionBindingContextAccessor _actionBindingContextAccessor; private readonly ILogger _logger; + private readonly INotifier _notifier; private readonly int _maxModelValidationErrors; private IFilterMetadata[] _filters; @@ -63,6 +65,7 @@ namespace Microsoft.AspNet.Mvc.Core [NotNull] IReadOnlyList valueProviderFactories, [NotNull] IActionBindingContextAccessor actionBindingContextAccessor, [NotNull] ILogger logger, + [NotNull] INotifier notifier, int maxModelValidationErrors) { ActionContext = actionContext; @@ -75,6 +78,7 @@ namespace Microsoft.AspNet.Mvc.Core _valueProviderFactories = valueProviderFactories; _actionBindingContextAccessor = actionBindingContextAccessor; _logger = logger; + _notifier = notifier; _maxModelValidationErrors = maxModelValidationErrors; } @@ -127,7 +131,7 @@ namespace Microsoft.AspNet.Mvc.Core Debug.Assert(_authorizationContext != null); if (_authorizationContext.Result != null) { - await _authorizationContext.Result.ExecuteResultAsync(ActionContext); + await InvokeResultAsync(_authorizationContext.Result); return; } @@ -281,7 +285,7 @@ namespace Microsoft.AspNet.Mvc.Core ResourceFilterShortCircuitLogMessage, item.FilterAsync.GetType().FullName); - await _resourceExecutingContext.Result.ExecuteResultAsync(ActionContext); + await InvokeResultAsync(_resourceExecutingContext.Result); } _resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters) @@ -301,7 +305,7 @@ namespace Microsoft.AspNet.Mvc.Core _logger.LogVerbose(ResourceFilterShortCircuitLogMessage, item.Filter.GetType().FullName); - await _resourceExecutingContext.Result.ExecuteResultAsync(ActionContext); + await InvokeResultAsync(_resourceExecutingContext.Result); _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. // 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) { @@ -558,12 +562,35 @@ namespace Microsoft.AspNet.Mvc.Core else { // 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( _actionExecutingContext, _filters, Instance) { - Result = await InvokeActionAsync(_actionExecutingContext), + Result = result }; } } @@ -683,7 +710,15 @@ namespace Microsoft.AspNet.Mvc.Core } 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); _resultExecutedContext = new ResultExecutedContext( @@ -708,17 +743,28 @@ namespace Microsoft.AspNet.Mvc.Core return _resultExecutedContext; } - private async Task InvokeResultAsync() + private async Task InvokeResultAsync(IActionResult result) { - _cursor.SetStage(FilterStage.ActionResult); - - // The empty result is always flowed back as the 'executed' result - if (_resultExecutingContext.Result == null) + if (_notifier.ShouldNotify("Microsoft.AspNet.Mvc.BeforeActionResult")) { - _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 diff --git a/src/Microsoft.AspNet.Mvc.Core/MvcRouteHandler.cs b/src/Microsoft.AspNet.Mvc.Core/MvcRouteHandler.cs index 488e2c7cc6..82ece340b5 100644 --- a/src/Microsoft.AspNet.Mvc.Core/MvcRouteHandler.cs +++ b/src/Microsoft.AspNet.Mvc.Core/MvcRouteHandler.cs @@ -70,10 +70,10 @@ namespace Microsoft.AspNet.Mvc { context.RouteData = newRouteData; - if (_notifier.ShouldNotify("Microsoft.AspNet.Mvc.ActionSelected")) + if (_notifier.ShouldNotify("Microsoft.AspNet.Mvc.BeforeAction")) { _notifier.Notify( - "Microsoft.AspNet.Mvc.ActionSelected", + "Microsoft.AspNet.Mvc.BeforeAction", new { actionDescriptor, httpContext = context.HttpContext, routeData = context.RouteData}); } @@ -84,16 +84,16 @@ namespace Microsoft.AspNet.Mvc await InvokeActionAsync(context, actionDescriptor); context.IsHandled = true; } - - if (_notifier.ShouldNotify("Microsoft.AspNet.Mvc.ActionInvoked")) - { - _notifier.Notify( - "Microsoft.AspNet.Mvc.ActionInvoked", - new { actionDescriptor, httpContext = context.HttpContext }); - } } finally { + if (_notifier.ShouldNotify("Microsoft.AspNet.Mvc.AfterAction")) + { + _notifier.Notify( + "Microsoft.AspNet.Mvc.AfterAction", + new { actionDescriptor, httpContext = context.HttpContext }); + } + if (!context.IsHandled) { context.RouteData = oldRouteData; diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewResult.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewResult.cs index 625d3f2c0a..d261e40ca9 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewResult.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewResult.cs @@ -9,6 +9,7 @@ using Microsoft.Framework.Internal; using Microsoft.Framework.Logging; using Microsoft.Net.Http.Headers; using Microsoft.Framework.OptionsModel; +using Microsoft.Framework.Notification; namespace Microsoft.AspNet.Mvc { @@ -55,17 +56,31 @@ namespace Microsoft.AspNet.Mvc /// public override async Task ExecuteResultAsync([NotNull] ActionContext context) { - var viewEngine = ViewEngine ?? - context.HttpContext.RequestServices.GetRequiredService(); + var services = context.HttpContext.RequestServices; + var viewEngine = ViewEngine ?? services.GetRequiredService(); - var logger = context.HttpContext.RequestServices.GetRequiredService>(); + var logger = services.GetRequiredService>(); + var notifier = services.GetRequiredService(); - var options = context.HttpContext.RequestServices.GetRequiredService>(); + var options = services.GetRequiredService>(); var viewName = ViewName ?? context.ActionDescriptor.Name; var viewEngineResult = viewEngine.FindView(context, viewName); 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( "The view '{ViewName}' was not found. Searched locations: {SearchedViewLocations}", viewName, @@ -73,6 +88,12 @@ namespace Microsoft.AspNet.Mvc } 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); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionInvokerTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionInvokerTest.cs index 577dae6013..0cc1370996 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionInvokerTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionInvokerTest.cs @@ -17,6 +17,7 @@ using Microsoft.AspNet.Testing; using Microsoft.Framework.Internal; using Microsoft.Framework.Logging; using Microsoft.Framework.Logging.Testing; +using Microsoft.Framework.Notification; using Microsoft.Framework.OptionsModel; using Microsoft.Net.Http.Headers; using Moq; @@ -2042,6 +2043,7 @@ namespace Microsoft.AspNet.Mvc new IValueProviderFactory[0], new ActionBindingContextAccessor(), new NullLoggerFactory().CreateLogger(), + new Notifier(new ProxyNotifierMethodAdapter()), maxAllowedErrorsInModelState); return invoker; @@ -2104,6 +2106,7 @@ namespace Microsoft.AspNet.Mvc new IValueProviderFactory[0], new ActionBindingContextAccessor(), new NullLoggerFactory().CreateLogger(), + new Notifier(new ProxyNotifierMethodAdapter()), 200); // Act @@ -2204,6 +2207,7 @@ namespace Microsoft.AspNet.Mvc IReadOnlyList valueProviderFactories, IActionBindingContextAccessor actionBindingContext, ILogger logger, + INotifier notifier, int maxAllowedErrorsInModelState) : base( actionContext, @@ -2218,6 +2222,7 @@ namespace Microsoft.AspNet.Mvc valueProviderFactories, actionBindingContext, logger, + notifier, maxAllowedErrorsInModelState) { ControllerFactory = controllerFactory; diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/MvcRouteHandlerTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/MvcRouteHandlerTests.cs index 088820ea5f..e4d91d4c69 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/MvcRouteHandlerTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/MvcRouteHandlerTests.cs @@ -169,10 +169,10 @@ namespace Microsoft.AspNet.Mvc await handler.RouteAsync(context); // Assert - Assert.NotNull(listener?.ActionSelected.ActionDescriptor); - Assert.NotNull(listener?.ActionSelected.HttpContext); + Assert.NotNull(listener.BeforeAction?.ActionDescriptor); + Assert.NotNull(listener.BeforeAction?.HttpContext); - var routeValues = listener?.ActionSelected?.RouteData?.Values; + var routeValues = listener.BeforeAction?.RouteData?.Values; Assert.NotNull(routeValues); Assert.Equal(1, routeValues.Count); @@ -193,8 +193,8 @@ namespace Microsoft.AspNet.Mvc await handler.RouteAsync(context); // Assert - Assert.NotNull(listener?.ActionInvoked.ActionDescriptor); - Assert.NotNull(listener?.ActionInvoked.HttpContext); + Assert.NotNull(listener.AfterAction?.ActionDescriptor); + Assert.NotNull(listener.AfterAction?.HttpContext); } private RouteContext CreateRouteContext( diff --git a/test/Microsoft.AspNet.Mvc.TestCommon/Notification/IProxyActionContext.cs b/test/Microsoft.AspNet.Mvc.TestCommon/Notification/IProxyActionContext.cs new file mode 100644 index 0000000000..43ff07a224 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.TestCommon/Notification/IProxyActionContext.cs @@ -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 + { + } +} diff --git a/test/Microsoft.AspNet.Mvc.TestCommon/Notification/IProxyActionDescriptor.cs b/test/Microsoft.AspNet.Mvc.TestCommon/Notification/IProxyActionDescriptor.cs new file mode 100644 index 0000000000..093a8c4faf --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.TestCommon/Notification/IProxyActionDescriptor.cs @@ -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 + { + } +} diff --git a/test/Microsoft.AspNet.Mvc.TestCommon/Notification/IProxyActionResult.cs b/test/Microsoft.AspNet.Mvc.TestCommon/Notification/IProxyActionResult.cs new file mode 100644 index 0000000000..ba950170d6 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.TestCommon/Notification/IProxyActionResult.cs @@ -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 + { + } +} diff --git a/test/Microsoft.AspNet.Mvc.TestCommon/Notification/IActionDescriptor.cs b/test/Microsoft.AspNet.Mvc.TestCommon/Notification/IProxyHttpContext.cs similarity index 86% rename from test/Microsoft.AspNet.Mvc.TestCommon/Notification/IActionDescriptor.cs rename to test/Microsoft.AspNet.Mvc.TestCommon/Notification/IProxyHttpContext.cs index 7fa0726735..d2b1ba6ab9 100644 --- a/test/Microsoft.AspNet.Mvc.TestCommon/Notification/IActionDescriptor.cs +++ b/test/Microsoft.AspNet.Mvc.TestCommon/Notification/IProxyHttpContext.cs @@ -3,7 +3,7 @@ namespace Microsoft.AspNet.Mvc.TestCommon.Notification { - public interface IActionDescriptor + public interface IProxyHttpContext { } } diff --git a/test/Microsoft.AspNet.Mvc.TestCommon/Notification/IRouteData.cs b/test/Microsoft.AspNet.Mvc.TestCommon/Notification/IProxyRouteData.cs similarity index 92% rename from test/Microsoft.AspNet.Mvc.TestCommon/Notification/IRouteData.cs rename to test/Microsoft.AspNet.Mvc.TestCommon/Notification/IProxyRouteData.cs index 25d1bae7ac..07062d4a31 100644 --- a/test/Microsoft.AspNet.Mvc.TestCommon/Notification/IRouteData.cs +++ b/test/Microsoft.AspNet.Mvc.TestCommon/Notification/IProxyRouteData.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; namespace Microsoft.AspNet.Mvc.TestCommon.Notification { - public interface IRouteData + public interface IProxyRouteData { IReadOnlyList Routers { get; } IDictionary DataTokens { get; } diff --git a/test/Microsoft.AspNet.Mvc.TestCommon/Notification/IHttpContext.cs b/test/Microsoft.AspNet.Mvc.TestCommon/Notification/IProxyView.cs similarity index 87% rename from test/Microsoft.AspNet.Mvc.TestCommon/Notification/IHttpContext.cs rename to test/Microsoft.AspNet.Mvc.TestCommon/Notification/IProxyView.cs index 46d68faf9b..dbd20f325a 100644 --- a/test/Microsoft.AspNet.Mvc.TestCommon/Notification/IHttpContext.cs +++ b/test/Microsoft.AspNet.Mvc.TestCommon/Notification/IProxyView.cs @@ -3,7 +3,7 @@ namespace Microsoft.AspNet.Mvc.TestCommon.Notification { - public interface IHttpContext + public interface IProxyView { } } diff --git a/test/Microsoft.AspNet.Mvc.TestCommon/Notification/TestNotificationListener.cs b/test/Microsoft.AspNet.Mvc.TestCommon/Notification/TestNotificationListener.cs index e40dd22cc3..27f6db58c9 100644 --- a/test/Microsoft.AspNet.Mvc.TestCommon/Notification/TestNotificationListener.cs +++ b/test/Microsoft.AspNet.Mvc.TestCommon/Notification/TestNotificationListener.cs @@ -1,21 +1,29 @@ // 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.Collections.Generic; using Microsoft.Framework.Notification; namespace Microsoft.AspNet.Mvc.TestCommon.Notification { public class TestNotificationListener { - public OnActionSelectedEventData ActionSelected { get; set; } - - [NotificationName("Microsoft.AspNet.Mvc.ActionSelected")] - public virtual void OnActionSelected( - IHttpContext httpContext, - IRouteData routeData, - IActionDescriptor actionDescriptor) + public class OnBeforeActionEventData { - 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, HttpContext = httpContext, @@ -23,31 +31,76 @@ namespace Microsoft.AspNet.Mvc.TestCommon.Notification }; } - public OnActionInvokedEventData ActionInvoked { get; set; } - - [NotificationName("Microsoft.AspNet.Mvc.ActionInvoked")] - public virtual void OnActionInvoked( - IHttpContext httpContext, - IActionDescriptor actionDescriptor) + public class OnAfterActionEventData { - 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, HttpContext = httpContext, }; } - public class OnActionSelectedEventData + public class OnViewResultViewFoundEventData { - public IActionDescriptor ActionDescriptor { get; set; } - public IHttpContext HttpContext { get; set; } - public IRouteData RouteData { get; set; } + public IProxyActionContext ActionContext { get; set; } + public IProxyActionResult Result { 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; } - public IHttpContext HttpContext { get; set; } + ViewResultViewFound = new OnViewResultViewFoundEventData() + { + 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 SearchedLocations { get; set; } + } + + public OnViewResultViewNotFoundEventData ViewResultViewNotFound { get; set; } + + [NotificationName("Microsoft.AspNet.Mvc.ViewResultViewNotFound")] + public virtual void OnViewResultViewNotFound( + IProxyActionContext actionContext, + IProxyActionResult result, + string viewName, + IEnumerable searchedLocations) + { + ViewResultViewNotFound = new OnViewResultViewNotFoundEventData() + { + ActionContext = actionContext, + Result = result, + ViewName = viewName, + SearchedLocations = searchedLocations, + }; } } } diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewResultTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewResultTest.cs index 2cdc5b640c..a8851770e9 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewResultTest.cs @@ -7,8 +7,11 @@ using System.Threading.Tasks; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Rendering; +using Microsoft.AspNet.Mvc.TestCommon.Notification; using Microsoft.AspNet.Routing; +using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.Logging; +using Microsoft.Framework.Notification; using Microsoft.Framework.OptionsModel; using Microsoft.Net.Http.Headers; using Moq; @@ -203,6 +206,8 @@ namespace Microsoft.AspNet.Mvc .Verifiable(); var serviceProvider = new Mock(); + serviceProvider.Setup(s => s.GetService(typeof(INotifier))) + .Returns(new Notifier(new ProxyNotifierMethodAdapter())); serviceProvider.Setup(p => p.GetService(typeof(ICompositeViewEngine))) .Returns(viewEngine.Object); serviceProvider.Setup(p => p.GetService(typeof(ILogger))) @@ -228,6 +233,75 @@ namespace Microsoft.AspNet.Mvc 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().EnlistTarget(listener); + + var viewEngine = new Mock(); + var view = Mock.Of(); + + 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().EnlistTarget(listener); + + var viewEngine = new Mock(); + var view = Mock.Of(); + + 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( + 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() { var serviceProvider = new Mock(); @@ -241,6 +315,9 @@ namespace Microsoft.AspNet.Mvc serviceProvider.Setup(s => s.GetService(typeof(IOptions))) .Returns(optionsAccessor.Object); + serviceProvider.Setup(s => s.GetService(typeof(INotifier))) + .Returns(new Notifier(new ProxyNotifierMethodAdapter())); + var httpContext = new DefaultHttpContext(); httpContext.RequestServices = serviceProvider.Object;