Separate ApiControllers from MVC Controllers in routing
This commit is contained in:
parent
3968df90e4
commit
9ad3d5e68f
|
|
@ -11,6 +11,7 @@ using Microsoft.Framework.DependencyInjection;
|
|||
|
||||
namespace System.Web.Http
|
||||
{
|
||||
[UseWebApiRoutes]
|
||||
[UseWebApiActionConventions]
|
||||
[UseWebApiOverloading]
|
||||
public abstract class ApiController : IDisposable
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
||||
{
|
||||
public interface IUseWebApiRoutes
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
|
||||
public class UseWebApiRoutesAttribute : Attribute, IUseWebApiRoutes
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
// 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.ApplicationModel;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
||||
{
|
||||
public class WebApiRoutesGlobalModelConvention : IGlobalModelConvention
|
||||
{
|
||||
private readonly string _area;
|
||||
|
||||
public WebApiRoutesGlobalModelConvention(string area)
|
||||
{
|
||||
_area = area;
|
||||
}
|
||||
|
||||
public void Apply(GlobalModel model)
|
||||
{
|
||||
foreach (var controller in model.Controllers)
|
||||
{
|
||||
if (IsConventionApplicable(controller))
|
||||
{
|
||||
Apply(controller);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsConventionApplicable(ControllerModel controller)
|
||||
{
|
||||
return controller.Attributes.OfType<IUseWebApiRoutes>().Any();
|
||||
}
|
||||
|
||||
private void Apply(ControllerModel model)
|
||||
{
|
||||
model.RouteConstraints.Add(new AreaAttribute(_area));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
// 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.Collections.Generic;
|
||||
using Microsoft.AspNet.Mvc.WebApiCompatShim;
|
||||
using Microsoft.AspNet.Routing.Constraints;
|
||||
|
||||
namespace Microsoft.AspNet.Routing
|
||||
{
|
||||
public static class RouteBuilderExtensions
|
||||
{
|
||||
public static IRouteBuilder MapWebApiRoute(
|
||||
this IRouteBuilder routeCollectionBuilder,
|
||||
string name,
|
||||
string template)
|
||||
{
|
||||
return MapWebApiRoute(routeCollectionBuilder, name, template, defaults: null);
|
||||
}
|
||||
|
||||
public static IRouteBuilder MapWebApiRoute(
|
||||
this IRouteBuilder routeCollectionBuilder,
|
||||
string name,
|
||||
string template,
|
||||
object defaults)
|
||||
{
|
||||
return MapWebApiRoute(routeCollectionBuilder, name, template, defaults, constraints: null);
|
||||
}
|
||||
|
||||
public static IRouteBuilder MapWebApiRoute(
|
||||
this IRouteBuilder routeCollectionBuilder,
|
||||
string name,
|
||||
string template,
|
||||
object defaults,
|
||||
object constraints)
|
||||
{
|
||||
return MapWebApiRoute(routeCollectionBuilder, name, template, defaults, constraints, dataTokens: null);
|
||||
}
|
||||
|
||||
public static IRouteBuilder MapWebApiRoute(
|
||||
this IRouteBuilder routeCollectionBuilder,
|
||||
string name,
|
||||
string template,
|
||||
object defaults,
|
||||
object constraints,
|
||||
object dataTokens)
|
||||
{
|
||||
var mutableDefaults = ObjectToDictionary(defaults);
|
||||
mutableDefaults.Add("area", WebApiCompatShimOptionsSetup.DefaultAreaName);
|
||||
|
||||
var mutableConstraints = ObjectToDictionary(constraints);
|
||||
mutableConstraints.Add("area", new RequiredRouteConstraint());
|
||||
|
||||
return routeCollectionBuilder.MapRoute(name, template, mutableDefaults, mutableConstraints, dataTokens);
|
||||
}
|
||||
|
||||
private static IDictionary<string, object> ObjectToDictionary(object value)
|
||||
{
|
||||
var dictionary = value as IDictionary<string, object>;
|
||||
if (dictionary != null)
|
||||
{
|
||||
return new RouteValueDictionary(dictionary);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new RouteValueDictionary(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,8 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
|||
{
|
||||
public class WebApiCompatShimOptionsSetup : IOptionsAction<MvcOptions>, IOptionsAction<WebApiCompatShimOptions>
|
||||
{
|
||||
public readonly static string DefaultAreaName = "api";
|
||||
|
||||
public int Order
|
||||
{
|
||||
// We want to run after the default MvcOptionsSetup.
|
||||
|
|
@ -21,6 +23,7 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
|||
// Add webapi behaviors to controllers with the appropriate attributes
|
||||
options.ApplicationModelConventions.Add(new WebApiActionConventionsGlobalModelConvention());
|
||||
options.ApplicationModelConventions.Add(new WebApiOverloadingGlobalModelConvention());
|
||||
options.ApplicationModelConventions.Add(new WebApiRoutesGlobalModelConvention(area: DefaultAreaName));
|
||||
|
||||
// Add a model binder to be able to bind HttpRequestMessage
|
||||
options.ModelBinders.Insert(0, new HttpRequestMessageModelBinder());
|
||||
|
|
|
|||
|
|
@ -303,7 +303,6 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
Assert.Equal(mediaType, response.Content.Headers.ContentType.MediaType);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task ApiController_CreateResponse_HardcodedFormatter()
|
||||
{
|
||||
|
|
@ -327,6 +326,42 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
Assert.Equal("Test User", user.Name);
|
||||
Assert.Equal("text/json", response.Content.Headers.ContentType.MediaType);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("http://localhost/Mvc/Index", HttpStatusCode.OK)]
|
||||
[InlineData("http://localhost/api/Blog/Mvc/Index", HttpStatusCode.NotFound)]
|
||||
public async Task WebApiRouting_AccessMvcController(string url, HttpStatusCode expected)
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_provider, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, response.StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("http://localhost/BasicApi/GenerateUrl", HttpStatusCode.NotFound)]
|
||||
[InlineData("http://localhost/api/Blog/BasicApi/GenerateUrl", HttpStatusCode.OK)]
|
||||
public async Task WebApiRouting_AccessWebApiController(string url, HttpStatusCode expected)
|
||||
{
|
||||
// Arrange
|
||||
var server = TestServer.Create(_provider, _app);
|
||||
var client = server.CreateClient();
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
|
||||
// Act
|
||||
var response = await client.SendAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, response.StatusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -225,6 +225,31 @@ namespace System.Web.Http
|
|||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetActions_AllWebApiActionsAreInWebApiArea()
|
||||
{
|
||||
// Arrange
|
||||
var provider = CreateProvider();
|
||||
|
||||
// Act
|
||||
var context = new ActionDescriptorProviderContext();
|
||||
provider.Invoke(context);
|
||||
|
||||
var results = context.Results.Cast<ControllerActionDescriptor>();
|
||||
|
||||
// Assert
|
||||
var controllerType = typeof(TestControllers.StoreController).GetTypeInfo();
|
||||
var actions = results
|
||||
.Where(ad => ad.ControllerDescriptor.ControllerTypeInfo == controllerType)
|
||||
.ToArray();
|
||||
|
||||
Assert.NotEmpty(actions);
|
||||
foreach (var action in actions)
|
||||
{
|
||||
Assert.Single(action.RouteConstraints, c => c.RouteKey == "area" && c.RouteValue == "api");
|
||||
}
|
||||
}
|
||||
|
||||
private INestedProviderManager<ActionDescriptorProviderContext> CreateProvider()
|
||||
{
|
||||
var assemblyProvider = new Mock<IControllerAssemblyProvider>();
|
||||
|
|
@ -240,8 +265,9 @@ namespace System.Web.Http
|
|||
var conventions = new NamespaceLimitedActionDiscoveryConventions();
|
||||
|
||||
var options = new MvcOptions();
|
||||
options.ApplicationModelConventions.Add(new WebApiActionConventionsGlobalModelConvention());
|
||||
options.ApplicationModelConventions.Add(new WebApiOverloadingGlobalModelConvention());
|
||||
|
||||
var setup = new WebApiCompatShimOptionsSetup();
|
||||
setup.Invoke(options);
|
||||
|
||||
var optionsAccessor = new Mock<IOptionsAccessor<MvcOptions>>();
|
||||
optionsAccessor
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
// 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 Microsoft.AspNet.Mvc;
|
||||
|
||||
namespace WebApiCompatShimWebSite
|
||||
{
|
||||
// This is reachable via our MVC routes, but not webapi routes
|
||||
public class MvcController : Controller
|
||||
{
|
||||
public string Index()
|
||||
{
|
||||
return "Hello, World!";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Mvc;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
|
||||
|
|
@ -23,12 +22,15 @@ namespace WebApiCompatShimWebSite
|
|||
|
||||
app.UseMvc(routes =>
|
||||
{
|
||||
// This route can't access any of our webapi controllers
|
||||
routes.MapRoute("default", "{controller}/{action}/{id?}");
|
||||
|
||||
// Tests include different styles of WebAPI conventional routing and action selection - the prefix keeps
|
||||
// them from matching too eagerly.
|
||||
routes.MapRoute("named-action", "api/Blog/{controller}/{action}/{id?}");
|
||||
routes.MapRoute("unnamed-action", "api/Admin/{controller}/{id?}");
|
||||
routes.MapRoute("name-as-parameter", "api/Store/{controller}/{name?}");
|
||||
routes.MapRoute("extra-parameter", "api/Support/{extra}/{controller}/{id?}");
|
||||
routes.MapWebApiRoute("named-action", "api/Blog/{controller}/{action}/{id?}");
|
||||
routes.MapWebApiRoute("unnamed-action", "api/Admin/{controller}/{id?}");
|
||||
routes.MapWebApiRoute("name-as-parameter", "api/Store/{controller}/{name?}");
|
||||
routes.MapWebApiRoute("extra-parameter", "api/Support/{extra}/{controller}/{id?}");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue