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.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<IValueProviderFactory> 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;

View File

@ -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<MvcOptions> 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<ControllerActionInvoker>();
_notifier = notifier;
}
public int Order
@ -72,6 +75,7 @@ namespace Microsoft.AspNet.Mvc.Core
_valueProviderFactories,
_actionBindingContextAccessor,
_logger,
_notifier,
_maxModelValidationErrors);
}
}

View File

@ -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<IValueProviderFactory> _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<IValueProviderFactory> 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

View File

@ -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;

View File

@ -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
/// <inheritdoc />
public override async Task ExecuteResultAsync([NotNull] ActionContext context)
{
var viewEngine = ViewEngine ??
context.HttpContext.RequestServices.GetRequiredService<ICompositeViewEngine>();
var services = context.HttpContext.RequestServices;
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 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);

View File

@ -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<ControllerActionInvoker>(),
new Notifier(new ProxyNotifierMethodAdapter()),
maxAllowedErrorsInModelState);
return invoker;
@ -2104,6 +2106,7 @@ namespace Microsoft.AspNet.Mvc
new IValueProviderFactory[0],
new ActionBindingContextAccessor(),
new NullLoggerFactory().CreateLogger<ControllerActionInvoker>(),
new Notifier(new ProxyNotifierMethodAdapter()),
200);
// Act
@ -2204,6 +2207,7 @@ namespace Microsoft.AspNet.Mvc
IReadOnlyList<IValueProviderFactory> 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;

View File

@ -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(

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
{
public interface IActionDescriptor
public interface IProxyHttpContext
{
}
}

View File

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

View File

@ -3,7 +3,7 @@
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.
// 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<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.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<IServiceProvider>();
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<ViewResult>)))
@ -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<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()
{
var serviceProvider = new Mock<IServiceProvider>();
@ -241,6 +315,9 @@ namespace Microsoft.AspNet.Mvc
serviceProvider.Setup(s => s.GetService(typeof(IOptions<MvcViewOptions>)))
.Returns(optionsAccessor.Object);
serviceProvider.Setup(s => s.GetService(typeof(INotifier)))
.Returns(new Notifier(new ProxyNotifierMethodAdapter()));
var httpContext = new DefaultHttpContext();
httpContext.RequestServices = serviceProvider.Object;