Create a new routedata for each 'router' for MVC
This is the MVC companion to https://github.com/aspnet/Routing/pull/122 As routing flows, routes replace the route data and mutate a copy. This allows users to make changes that dirty the data without affecting undesired state changes. We also add the 'next' router for diagnostic purposes.
This commit is contained in:
parent
232deb47d0
commit
e9d8c845d6
|
|
@ -32,18 +32,16 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
public async Task RouteAsync([NotNull] RouteContext context)
|
public async Task RouteAsync([NotNull] RouteContext context)
|
||||||
{
|
{
|
||||||
|
// TODO: Throw an error here that's descriptive enough so that
|
||||||
|
// users understand they should call the per request scoped middleware
|
||||||
|
// or set HttpContext.Services manually
|
||||||
var services = context.HttpContext.RequestServices;
|
var services = context.HttpContext.RequestServices;
|
||||||
|
Contract.Assert(services != null);
|
||||||
|
|
||||||
// Verify if AddMvc was done before calling UseMvc
|
// Verify if AddMvc was done before calling UseMvc
|
||||||
// We use the MvcMarkerService to make sure if all the services were added.
|
// We use the MvcMarkerService to make sure if all the services were added.
|
||||||
MvcServicesHelper.ThrowIfMvcNotRegistered(services);
|
MvcServicesHelper.ThrowIfMvcNotRegistered(services);
|
||||||
|
|
||||||
Contract.Assert(services != null);
|
|
||||||
|
|
||||||
// TODO: Throw an error here that's descriptive enough so that
|
|
||||||
// users understand they should call the per request scoped middleware
|
|
||||||
// or set HttpContext.Services manually
|
|
||||||
|
|
||||||
EnsureLogger(context.HttpContext);
|
EnsureLogger(context.HttpContext);
|
||||||
using (_logger.BeginScope("MvcRouteHandler.RouteAsync"))
|
using (_logger.BeginScope("MvcRouteHandler.RouteAsync"))
|
||||||
{
|
{
|
||||||
|
|
@ -52,76 +50,75 @@ namespace Microsoft.AspNet.Mvc
|
||||||
|
|
||||||
if (actionDescriptor == null)
|
if (actionDescriptor == null)
|
||||||
{
|
{
|
||||||
if (_logger.IsEnabled(LogLevel.Verbose))
|
LogActionSelection(actionSelected: false, actionInvoked: false, handled: context.IsHandled);
|
||||||
{
|
|
||||||
_logger.WriteValues(new MvcRouteHandlerRouteAsyncValues()
|
|
||||||
{
|
|
||||||
ActionSelected = false,
|
|
||||||
ActionInvoked = false,
|
|
||||||
Handled = context.IsHandled
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Replacing the route data allows any code running here to dirty the route values or data-tokens
|
||||||
|
// without affecting something upstream.
|
||||||
|
var oldRouteData = context.RouteData;
|
||||||
|
var newRouteData = new RouteData(oldRouteData);
|
||||||
|
|
||||||
if (actionDescriptor.RouteValueDefaults != null)
|
if (actionDescriptor.RouteValueDefaults != null)
|
||||||
{
|
{
|
||||||
foreach (var kvp in actionDescriptor.RouteValueDefaults)
|
foreach (var kvp in actionDescriptor.RouteValueDefaults)
|
||||||
{
|
{
|
||||||
if (!context.RouteData.Values.ContainsKey(kvp.Key))
|
if (!newRouteData.Values.ContainsKey(kvp.Key))
|
||||||
{
|
{
|
||||||
context.RouteData.Values.Add(kvp.Key, kvp.Value);
|
newRouteData.Values.Add(kvp.Key, kvp.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var actionContext = new ActionContext(context.HttpContext, context.RouteData, actionDescriptor);
|
try
|
||||||
|
|
||||||
var optionsAccessor = services.GetRequiredService<IOptions<MvcOptions>>();
|
|
||||||
actionContext.ModelState.MaxAllowedErrors = optionsAccessor.Options.MaxModelValidationErrors;
|
|
||||||
|
|
||||||
var contextAccessor = services.GetRequiredService<IContextAccessor<ActionContext>>();
|
|
||||||
using (contextAccessor.SetContextSource(() => actionContext, PreventExchange))
|
|
||||||
{
|
{
|
||||||
var invokerFactory = services.GetRequiredService<IActionInvokerFactory>();
|
context.RouteData = newRouteData;
|
||||||
var invoker = invokerFactory.CreateInvoker(actionContext);
|
|
||||||
if (invoker == null)
|
|
||||||
{
|
|
||||||
var ex = new InvalidOperationException(
|
|
||||||
Resources.FormatActionInvokerFactory_CouldNotCreateInvoker(
|
|
||||||
actionDescriptor.DisplayName));
|
|
||||||
|
|
||||||
// Add tracing/logging (what do we think of this pattern of
|
await InvokeActionAsync(context, actionDescriptor);
|
||||||
// tacking on extra data on the exception?)
|
|
||||||
ex.Data.Add("AD", actionDescriptor);
|
|
||||||
|
|
||||||
if (_logger.IsEnabled(LogLevel.Verbose))
|
|
||||||
{
|
|
||||||
_logger.WriteValues(new MvcRouteHandlerRouteAsyncValues()
|
|
||||||
{
|
|
||||||
ActionSelected = true,
|
|
||||||
ActionInvoked = false,
|
|
||||||
Handled = context.IsHandled
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
|
|
||||||
await invoker.InvokeAsync();
|
|
||||||
context.IsHandled = true;
|
context.IsHandled = true;
|
||||||
|
}
|
||||||
if (_logger.IsEnabled(LogLevel.Verbose))
|
finally
|
||||||
|
{
|
||||||
|
if (!context.IsHandled)
|
||||||
{
|
{
|
||||||
_logger.WriteValues(new MvcRouteHandlerRouteAsyncValues()
|
context.RouteData = oldRouteData;
|
||||||
{
|
|
||||||
ActionSelected = true,
|
|
||||||
ActionInvoked = true,
|
|
||||||
Handled = context.IsHandled
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LogActionSelection(actionSelected: true, actionInvoked: true, handled: context.IsHandled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task InvokeActionAsync(RouteContext context, ActionDescriptor actionDescriptor)
|
||||||
|
{
|
||||||
|
var services = context.HttpContext.RequestServices;
|
||||||
|
Contract.Assert(services != null);
|
||||||
|
|
||||||
|
var actionContext = new ActionContext(context.HttpContext, context.RouteData, actionDescriptor);
|
||||||
|
|
||||||
|
var optionsAccessor = services.GetRequiredService<IOptions<MvcOptions>>();
|
||||||
|
actionContext.ModelState.MaxAllowedErrors = optionsAccessor.Options.MaxModelValidationErrors;
|
||||||
|
|
||||||
|
var contextAccessor = services.GetRequiredService<IContextAccessor<ActionContext>>();
|
||||||
|
using (contextAccessor.SetContextSource(() => actionContext, PreventExchange))
|
||||||
|
{
|
||||||
|
var invokerFactory = services.GetRequiredService<IActionInvokerFactory>();
|
||||||
|
var invoker = invokerFactory.CreateInvoker(actionContext);
|
||||||
|
if (invoker == null)
|
||||||
|
{
|
||||||
|
LogActionSelection(actionSelected: true, actionInvoked: false, handled: context.IsHandled);
|
||||||
|
|
||||||
|
var ex = new InvalidOperationException(
|
||||||
|
Resources.FormatActionInvokerFactory_CouldNotCreateInvoker(
|
||||||
|
actionDescriptor.DisplayName));
|
||||||
|
|
||||||
|
// Add tracing/logging (what do we think of this pattern of
|
||||||
|
// tacking on extra data on the exception?)
|
||||||
|
ex.Data.Add("AD", actionDescriptor);
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
await invoker.InvokeAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -138,5 +135,18 @@ namespace Microsoft.AspNet.Mvc
|
||||||
_logger = factory.Create<MvcRouteHandler>();
|
_logger = factory.Create<MvcRouteHandler>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void LogActionSelection(bool actionSelected, bool actionInvoked, bool handled)
|
||||||
|
{
|
||||||
|
if (_logger.IsEnabled(LogLevel.Verbose))
|
||||||
|
{
|
||||||
|
_logger.WriteValues(new MvcRouteHandlerRouteAsyncValues()
|
||||||
|
{
|
||||||
|
ActionSelected = actionSelected,
|
||||||
|
ActionInvoked = actionInvoked,
|
||||||
|
Handled = handled,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,22 +96,38 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
{
|
{
|
||||||
foreach (var route in _matchingRoutes)
|
foreach (var route in _matchingRoutes)
|
||||||
{
|
{
|
||||||
await route.RouteAsync(context);
|
var oldRouteData = context.RouteData;
|
||||||
|
|
||||||
|
var newRouteData = new RouteData(oldRouteData);
|
||||||
|
newRouteData.Routers.Add(route);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
context.RouteData = newRouteData;
|
||||||
|
await route.RouteAsync(context);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (!context.IsHandled)
|
||||||
|
{
|
||||||
|
context.RouteData = oldRouteData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (context.IsHandled)
|
if (context.IsHandled)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (_logger.IsEnabled(LogLevel.Verbose))
|
if (_logger.IsEnabled(LogLevel.Verbose))
|
||||||
|
{
|
||||||
|
_logger.WriteValues(new AttributeRouteRouteAsyncValues()
|
||||||
{
|
{
|
||||||
_logger.WriteValues(new AttributeRouteRouteAsyncValues()
|
MatchingRoutes = _matchingRoutes,
|
||||||
{
|
Handled = context.IsHandled
|
||||||
MatchingRoutes = _matchingRoutes,
|
});
|
||||||
Handled = context.IsHandled
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -163,6 +163,91 @@ namespace Microsoft.AspNet.Mvc
|
||||||
Assert.True(invoked);
|
Assert.True(invoked);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RouteAsync_CreatesNewRouteData()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
RouteData actionRouteData = null;
|
||||||
|
var invoker = new Mock<IActionInvoker>();
|
||||||
|
invoker
|
||||||
|
.Setup(i => i.InvokeAsync())
|
||||||
|
.Returns(Task.FromResult(true));
|
||||||
|
|
||||||
|
var invokerFactory = new Mock<IActionInvokerFactory>();
|
||||||
|
invokerFactory
|
||||||
|
.Setup(f => f.CreateInvoker(It.IsAny<ActionContext>()))
|
||||||
|
.Returns<ActionContext>((c) =>
|
||||||
|
{
|
||||||
|
actionRouteData = c.RouteData;
|
||||||
|
return invoker.Object;
|
||||||
|
});
|
||||||
|
|
||||||
|
var initialRouter = Mock.Of<IRouter>();
|
||||||
|
|
||||||
|
var context = CreateRouteContext(invokerFactory: invokerFactory.Object);
|
||||||
|
var handler = new MvcRouteHandler();
|
||||||
|
|
||||||
|
var originalRouteData = context.RouteData;
|
||||||
|
originalRouteData.Routers.Add(initialRouter);
|
||||||
|
originalRouteData.Values.Add("action", "Index");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await handler.RouteAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotSame(originalRouteData, context.RouteData);
|
||||||
|
Assert.NotSame(originalRouteData, actionRouteData);
|
||||||
|
Assert.Same(actionRouteData, context.RouteData);
|
||||||
|
|
||||||
|
// The new routedata is a copy
|
||||||
|
Assert.Equal("Index", context.RouteData.Values["action"]);
|
||||||
|
|
||||||
|
Assert.Equal(initialRouter, Assert.Single(context.RouteData.Routers));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RouteAsync_ResetsRouteDataOnException()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
RouteData actionRouteData = null;
|
||||||
|
var invoker = new Mock<IActionInvoker>();
|
||||||
|
invoker
|
||||||
|
.Setup(i => i.InvokeAsync())
|
||||||
|
.Throws(new Exception());
|
||||||
|
|
||||||
|
var invokerFactory = new Mock<IActionInvokerFactory>();
|
||||||
|
invokerFactory
|
||||||
|
.Setup(f => f.CreateInvoker(It.IsAny<ActionContext>()))
|
||||||
|
.Returns<ActionContext>((c) =>
|
||||||
|
{
|
||||||
|
actionRouteData = c.RouteData;
|
||||||
|
c.RouteData.Values.Add("action", "Index");
|
||||||
|
return invoker.Object;
|
||||||
|
});
|
||||||
|
|
||||||
|
var context = CreateRouteContext(invokerFactory: invokerFactory.Object);
|
||||||
|
var handler = new MvcRouteHandler();
|
||||||
|
|
||||||
|
var initialRouter = Mock.Of<IRouter>();
|
||||||
|
|
||||||
|
var originalRouteData = context.RouteData;
|
||||||
|
originalRouteData.Routers.Add(initialRouter);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await Assert.ThrowsAsync<Exception>(() => handler.RouteAsync(context));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(originalRouteData, context.RouteData);
|
||||||
|
Assert.NotSame(originalRouteData, actionRouteData);
|
||||||
|
Assert.NotSame(actionRouteData, context.RouteData);
|
||||||
|
|
||||||
|
// The new routedata is a copy
|
||||||
|
Assert.Null(context.RouteData.Values["action"]);
|
||||||
|
Assert.Equal("Index", actionRouteData.Values["action"]);
|
||||||
|
|
||||||
|
Assert.Equal(initialRouter, Assert.Single(actionRouteData.Routers));
|
||||||
|
}
|
||||||
|
|
||||||
private RouteContext CreateRouteContext(
|
private RouteContext CreateRouteContext(
|
||||||
IActionSelector actionSelector = null,
|
IActionSelector actionSelector = null,
|
||||||
IActionInvokerFactory invokerFactory = null,
|
IActionInvokerFactory invokerFactory = null,
|
||||||
|
|
|
||||||
|
|
@ -1099,6 +1099,131 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
Assert.Equal("Store", path);
|
Assert.Equal("Store", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AttributeRoute_CreatesNewRouteData()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
RouteData nestedRouteData = null;
|
||||||
|
var next = new Mock<IRouter>();
|
||||||
|
next
|
||||||
|
.Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
|
||||||
|
.Callback<RouteContext>((c) =>
|
||||||
|
{
|
||||||
|
nestedRouteData = c.RouteData;
|
||||||
|
c.IsHandled = true;
|
||||||
|
})
|
||||||
|
.Returns(Task.FromResult(true));
|
||||||
|
|
||||||
|
var entry = CreateMatchingEntry(next.Object, "api/Store", order: 0);
|
||||||
|
var route = CreateAttributeRoute(next.Object, entry);
|
||||||
|
|
||||||
|
var context = CreateRouteContext("/api/Store");
|
||||||
|
|
||||||
|
var originalRouteData = context.RouteData;
|
||||||
|
originalRouteData.Values.Add("action", "Index");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await route.RouteAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotSame(originalRouteData, context.RouteData);
|
||||||
|
Assert.NotSame(originalRouteData, nestedRouteData);
|
||||||
|
Assert.Same(nestedRouteData, context.RouteData);
|
||||||
|
|
||||||
|
// The new routedata is a copy
|
||||||
|
Assert.Equal("Index", context.RouteData.Values["action"]);
|
||||||
|
Assert.Single(context.RouteData.Values, kvp => kvp.Key == "test_route_group");
|
||||||
|
|
||||||
|
Assert.IsType<TemplateRoute>(context.RouteData.Routers[0]);
|
||||||
|
Assert.Same(next.Object, context.RouteData.Routers[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AttributeRoute_CreatesNewRouteData_ResetsWhenNotMatched()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
RouteData nestedRouteData = null;
|
||||||
|
var next = new Mock<IRouter>();
|
||||||
|
next
|
||||||
|
.Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
|
||||||
|
.Callback<RouteContext>((c) =>
|
||||||
|
{
|
||||||
|
nestedRouteData = c.RouteData;
|
||||||
|
c.IsHandled = false;
|
||||||
|
})
|
||||||
|
.Returns(Task.FromResult(true));
|
||||||
|
|
||||||
|
var entry = CreateMatchingEntry(next.Object, "api/Store", order: 0);
|
||||||
|
var route = CreateAttributeRoute(next.Object, entry);
|
||||||
|
|
||||||
|
var context = CreateRouteContext("/api/Store");
|
||||||
|
|
||||||
|
var originalRouteData = context.RouteData;
|
||||||
|
originalRouteData.Values.Add("action", "Index");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await route.RouteAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(originalRouteData, context.RouteData);
|
||||||
|
Assert.NotSame(originalRouteData, nestedRouteData);
|
||||||
|
Assert.NotSame(nestedRouteData, context.RouteData);
|
||||||
|
|
||||||
|
// The new routedata is a copy
|
||||||
|
Assert.Equal("Index", context.RouteData.Values["action"]);
|
||||||
|
Assert.Equal("Index", nestedRouteData.Values["action"]);
|
||||||
|
Assert.None(context.RouteData.Values, kvp => kvp.Key == "test_route_group");
|
||||||
|
Assert.Single(nestedRouteData.Values, kvp => kvp.Key == "test_route_group");
|
||||||
|
|
||||||
|
Assert.Empty(context.RouteData.Routers);
|
||||||
|
|
||||||
|
Assert.IsType<TemplateRoute>(nestedRouteData.Routers[0]);
|
||||||
|
Assert.Same(next.Object, nestedRouteData.Routers[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AttributeRoute_CreatesNewRouteData_ResetsWhenThrows()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
RouteData nestedRouteData = null;
|
||||||
|
var next = new Mock<IRouter>();
|
||||||
|
next
|
||||||
|
.Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
|
||||||
|
.Callback<RouteContext>((c) =>
|
||||||
|
{
|
||||||
|
nestedRouteData = c.RouteData;
|
||||||
|
c.IsHandled = false;
|
||||||
|
})
|
||||||
|
.Throws(new Exception());
|
||||||
|
|
||||||
|
var entry = CreateMatchingEntry(next.Object, "api/Store", order: 0);
|
||||||
|
var route = CreateAttributeRoute(next.Object, entry);
|
||||||
|
|
||||||
|
var context = CreateRouteContext("/api/Store");
|
||||||
|
|
||||||
|
var originalRouteData = context.RouteData;
|
||||||
|
originalRouteData.Values.Add("action", "Index");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await Assert.ThrowsAsync<Exception>(() => route.RouteAsync(context));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Same(originalRouteData, context.RouteData);
|
||||||
|
Assert.NotSame(originalRouteData, nestedRouteData);
|
||||||
|
Assert.NotSame(nestedRouteData, context.RouteData);
|
||||||
|
|
||||||
|
// The new routedata is a copy
|
||||||
|
Assert.Equal("Index", context.RouteData.Values["action"]);
|
||||||
|
Assert.Equal("Index", nestedRouteData.Values["action"]);
|
||||||
|
Assert.None(context.RouteData.Values, kvp => kvp.Key == "test_route_group");
|
||||||
|
Assert.Single(nestedRouteData.Values, kvp => kvp.Key == "test_route_group");
|
||||||
|
|
||||||
|
Assert.Empty(context.RouteData.Routers);
|
||||||
|
|
||||||
|
Assert.IsType<TemplateRoute>(nestedRouteData.Routers[0]);
|
||||||
|
Assert.Same(next.Object, nestedRouteData.Routers[1]);
|
||||||
|
}
|
||||||
|
|
||||||
private static RouteContext CreateRouteContext(string requestPath)
|
private static RouteContext CreateRouteContext(string requestPath)
|
||||||
{
|
{
|
||||||
var request = new Mock<HttpRequest>(MockBehavior.Strict);
|
var request = new Mock<HttpRequest>(MockBehavior.Strict);
|
||||||
|
|
@ -1241,6 +1366,15 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
NullLoggerFactory.Instance);
|
NullLoggerFactory.Instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static AttributeRoute CreateAttributeRoute(IRouter next, params AttributeRouteMatchingEntry[] entries)
|
||||||
|
{
|
||||||
|
return new AttributeRoute(
|
||||||
|
next,
|
||||||
|
entries,
|
||||||
|
Enumerable.Empty<AttributeRouteLinkGenerationEntry>(),
|
||||||
|
NullLoggerFactory.Instance);
|
||||||
|
}
|
||||||
|
|
||||||
private static AttributeRoute CreateRoutingAttributeRoute(ILoggerFactory loggerFactory = null, params AttributeRouteMatchingEntry[] entries)
|
private static AttributeRoute CreateRoutingAttributeRoute(ILoggerFactory loggerFactory = null, params AttributeRouteMatchingEntry[] entries)
|
||||||
{
|
{
|
||||||
loggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
|
loggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. 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.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Builder;
|
||||||
|
using Microsoft.AspNet.Mvc.Routing;
|
||||||
|
using Microsoft.AspNet.Routing;
|
||||||
|
using Microsoft.AspNet.Routing.Template;
|
||||||
|
using Microsoft.AspNet.TestHost;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
|
{
|
||||||
|
public class RouteDataTest
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _services = TestHelper.CreateServices(nameof(BasicWebSite));
|
||||||
|
private readonly Action<IApplicationBuilder> _app = new BasicWebSite.Startup().Configure;
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RouteData_Routers_ConventionalRoute()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync("http://localhost/Routing/Conventional");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<ResultData>(body);
|
||||||
|
|
||||||
|
Assert.Equal(new string[]
|
||||||
|
{
|
||||||
|
typeof(RouteCollection).FullName,
|
||||||
|
typeof(TemplateRoute).FullName,
|
||||||
|
typeof(MvcRouteHandler).FullName,
|
||||||
|
},
|
||||||
|
result.Routers);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RouteData_Routers_AttributeRoute()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync("http://localhost/Routing/Attribute");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
|
||||||
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
var result = JsonConvert.DeserializeObject<ResultData>(body);
|
||||||
|
|
||||||
|
Assert.Equal(new string[]
|
||||||
|
{
|
||||||
|
typeof(RouteCollection).FullName,
|
||||||
|
typeof(AttributeRoute).FullName,
|
||||||
|
typeof(TemplateRoute).FullName,
|
||||||
|
typeof(MvcRouteHandler).FullName,
|
||||||
|
},
|
||||||
|
result.Routers);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class ResultData
|
||||||
|
{
|
||||||
|
public string[] Routers { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
|
namespace BasicWebSite
|
||||||
|
{
|
||||||
|
public class RoutingController : Controller
|
||||||
|
{
|
||||||
|
public object Conventional()
|
||||||
|
{
|
||||||
|
return GetData();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("Routing/Attribute")]
|
||||||
|
public object Attribute()
|
||||||
|
{
|
||||||
|
return GetData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private object GetData()
|
||||||
|
{
|
||||||
|
var routers = ActionContext.RouteData.Routers.Select(r => r.GetType().FullName).ToArray();
|
||||||
|
return new { Routers = routers };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue