Event Notification for MVC Prototype
Here's a first take on the pattern for publishing notifications from MVC.
This commit is contained in:
parent
a452b10ba4
commit
03571cc27b
|
|
@ -2,7 +2,6 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
|
|
@ -12,12 +11,13 @@ using Microsoft.AspNet.Routing;
|
|||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.Internal;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
using Microsoft.Framework.Notification;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
public class MvcRouteHandler : IRouter
|
||||
{
|
||||
private INotifier _notifier;
|
||||
private ILogger _logger;
|
||||
|
||||
public VirtualPathData GetVirtualPath([NotNull] VirtualPathContext context)
|
||||
|
|
@ -40,6 +40,8 @@ namespace Microsoft.AspNet.Mvc
|
|||
MvcServicesHelper.ThrowIfMvcNotRegistered(services);
|
||||
|
||||
EnsureLogger(context.HttpContext);
|
||||
EnsureNotifier(context.HttpContext);
|
||||
|
||||
var actionSelector = services.GetRequiredService<IActionSelector>();
|
||||
var actionDescriptor = await actionSelector.SelectAsync(context);
|
||||
|
||||
|
|
@ -69,6 +71,13 @@ namespace Microsoft.AspNet.Mvc
|
|||
{
|
||||
context.RouteData = newRouteData;
|
||||
|
||||
if (_notifier.ShouldNotify("Microsoft.AspNet.Mvc.ActionSelected"))
|
||||
{
|
||||
_notifier.Notify(
|
||||
"Microsoft.AspNet.Mvc.ActionSelected",
|
||||
new { actionDescriptor, httpContext = context.HttpContext, routeData = context.RouteData});
|
||||
}
|
||||
|
||||
using (_logger.BeginScope("ActionId: {ActionId}", actionDescriptor.Id))
|
||||
{
|
||||
_logger.LogVerbose("Executing action {ActionDisplayName}", actionDescriptor.DisplayName);
|
||||
|
|
@ -115,5 +124,13 @@ namespace Microsoft.AspNet.Mvc
|
|||
_logger = factory.CreateLogger<MvcRouteHandler>();
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureNotifier(HttpContext context)
|
||||
{
|
||||
if (_notifier == null)
|
||||
{
|
||||
_notifier = context.RequestServices.GetRequiredService<INotifier>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
"Microsoft.Framework.ClosedGenericMatcher.Sources": { "version": "1.0.0-*", "type": "build" },
|
||||
"Microsoft.Framework.CopyOnWriteDictionary.Sources": { "version": "1.0.0-*", "type": "build" },
|
||||
"Microsoft.Framework.Logging.Abstractions": "1.0.0-*",
|
||||
"Microsoft.Framework.Notification": "1.0.0-*",
|
||||
"Microsoft.Framework.NotNullAttribute.Sources": { "version": "1.0.0-*", "type": "build" },
|
||||
"Microsoft.Framework.PropertyActivator.Sources": { "version": "1.0.0-*", "type": "build" },
|
||||
"Microsoft.Framework.PropertyHelper.Sources": { "version": "1.0.0-*", "type": "build" },
|
||||
|
|
|
|||
|
|
@ -299,6 +299,7 @@ namespace Microsoft.Framework.DependencyInjection
|
|||
services.AddCors();
|
||||
services.AddAuthorization();
|
||||
services.AddWebEncoders();
|
||||
services.AddNotifier();
|
||||
services.Configure<RouteOptions>(
|
||||
routeOptions => routeOptions.ConstraintMap.Add("exists", typeof(KnownRouteValueConstraint)));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,11 @@ using System;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Mvc.Internal;
|
||||
using Microsoft.AspNet.Mvc.TestCommon.Notification;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.Framework.Logging.Testing;
|
||||
using Microsoft.Framework.Notification;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
|
@ -152,12 +154,38 @@ namespace Microsoft.AspNet.Mvc
|
|||
Assert.Equal(initialRouter, Assert.Single(actionRouteData.Routers));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RouteAsync_Notifies_ActionSelected()
|
||||
{
|
||||
// Arrange
|
||||
var listener = new TestNotificationListener();
|
||||
|
||||
var context = CreateRouteContext(notificationListener: listener);
|
||||
context.RouteData.Values.Add("tag", "value");
|
||||
|
||||
var handler = new MvcRouteHandler();
|
||||
|
||||
// Act
|
||||
await handler.RouteAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(listener?.ActionSelected.ActionDescriptor);
|
||||
Assert.NotNull(listener?.ActionSelected.HttpContext);
|
||||
|
||||
var routeValues = listener?.ActionSelected?.RouteData?.Values;
|
||||
Assert.NotNull(routeValues);
|
||||
|
||||
Assert.Equal(1, routeValues.Count);
|
||||
Assert.Contains(routeValues, kvp => kvp.Key == "tag" && string.Equals(kvp.Value, "value"));
|
||||
}
|
||||
|
||||
private RouteContext CreateRouteContext(
|
||||
ActionDescriptor actionDescriptor = null,
|
||||
IActionSelector actionSelector = null,
|
||||
IActionInvokerFactory invokerFactory = null,
|
||||
ILoggerFactory loggerFactory = null,
|
||||
IOptions<MvcOptions> optionsAccessor = null)
|
||||
IOptions<MvcOptions> optionsAccessor = null,
|
||||
object notificationListener = null)
|
||||
{
|
||||
var mockContextAccessor = new Mock<IScopedInstance<ActionContext>>();
|
||||
|
||||
|
|
@ -203,6 +231,12 @@ namespace Microsoft.AspNet.Mvc
|
|||
optionsAccessor = options.Object;
|
||||
}
|
||||
|
||||
var notifier = new Notifier(new NotifierMethodAdapter());
|
||||
if (notificationListener != null)
|
||||
{
|
||||
notifier.EnlistTarget(notificationListener);
|
||||
}
|
||||
|
||||
var httpContext = new Mock<HttpContext>();
|
||||
httpContext.Setup(h => h.RequestServices.GetService(typeof(IScopedInstance<ActionContext>)))
|
||||
.Returns(mockContextAccessor.Object);
|
||||
|
|
@ -216,6 +250,8 @@ namespace Microsoft.AspNet.Mvc
|
|||
.Returns(new MvcMarkerService());
|
||||
httpContext.Setup(h => h.RequestServices.GetService(typeof(IOptions<MvcOptions>)))
|
||||
.Returns(optionsAccessor);
|
||||
httpContext.Setup(h => h.RequestServices.GetService(typeof(INotifier)))
|
||||
.Returns(notifier);
|
||||
|
||||
return new RouteContext(httpContext.Object);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 IActionDescriptor
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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 IHttpContext
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.TestCommon.Notification
|
||||
{
|
||||
public interface IRouteData
|
||||
{
|
||||
IReadOnlyList<object> Routers { get; }
|
||||
IDictionary<string, object> DataTokens { get; }
|
||||
IDictionary<string, object> Values { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
// 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 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)
|
||||
{
|
||||
ActionSelected = new OnActionSelectedEventData()
|
||||
{
|
||||
ActionDescriptor = actionDescriptor,
|
||||
HttpContext = httpContext,
|
||||
RouteData = routeData,
|
||||
};
|
||||
}
|
||||
|
||||
public class OnActionSelectedEventData
|
||||
{
|
||||
public IActionDescriptor ActionDescriptor { get; set; }
|
||||
public IHttpContext HttpContext { get; set; }
|
||||
public IRouteData RouteData { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"version": "6.0.0-*",
|
||||
"shared": "*.cs",
|
||||
"shared": "**/*.cs",
|
||||
"dependencies": {
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue