Restrict route to controller namespace in ASP.NET Core
I'm trying to restrict the controllers of my ASP.NET Core routes to a certain namespace.
In previous versions of ASP.NET MVC there was an overload that provided a string[] namespaces
parameter when adding routes. This is missing in ASP.NET MVC 6. So after some googling, I tried playing around with something like
app.UseMvc(routes => {
var dataTokens = new RouteValueDictionary {
{
"Namespaces", new[] {"ProjectA.SomeNamespace.Controllers"}
}
};
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}",
defaults: null,
constraints: null,
dataTokens: dataTokens
);
});
but it doesn't seem to do what I want. Is there a way to restrict the routing engine to a certain namespace?
Update
I just realized it may have to do something with the fact that I'm using attribute routing on each individual controller? Does attribute routing funk up the routes defined by app.UseMvc()
?
Update 2
More details:
I've two completely independent Web API projects. Incidentally, a few of the routes are identical in both (ie. ~/api/ping
). These projects are independent in Production, one is an endpoint for users, one is an endpoint for administrators.
I also have unit tests, using Microsoft.AspNet.TestHost
. A few of these unit tests require functionality of both of these Web API projects (ie. need "admin" endpoint to fully setup a test case for "user"). But when I reference both API projects, the TestHost gets confused because of the identical routes and it complains about "multiple matching routes":
Microsoft.AspNet.Diagnostics.DeveloperExceptionPageMiddleware: Error: An unhandled exception has occurred while executing the request
Microsoft.AspNet.Mvc.Infrastructure.AmbiguousActionException: Multiple actions matched. The following actions matched route data and had all constraints satisfied:
ProjectA.SomeNamespace.Controllers.PingController.Ping
ProjectB.SomeNamespace.Controllers.PingController.Ping
at Microsoft.AspNet.Mvc.Infrastructure.DefaultActionSelector.SelectAsync(RouteContext context)
at Microsoft.AspNet.Mvc.Infrastructure.MvcRouteHandler.<RouteAsync>d__6.MoveNext()
Update:
I've found solution through using ActionConstraint. You have to add custom Action Constraint attribute about duplicate actions.
Example with duplicate Index methods.
First HomeController
namespace WebApplication.Controllers
{
public class HomeController : Controller
{
[NamespaceConstraint]
public IActionResult Index()
{
return View();
}
}
}
Second HomeController
namespace WebApplication
{
public class HomeController : Controller
{
[NamespaceConstraint]
public IActionResult Index()
{
return View();
}
}
}
Configure routing
app.UseMvc(cR =>
cR.MapRoute("default", "{controller}/{action}", null, null,
new { Namespace = "WebApplication.Controllers.HomeController" }));
Action constraint
namespace WebApplication
{
public class NamespaceConstraint : ActionMethodSelectorAttribute
{
public override bool IsValidForRequest(RouteContext routeContext, ActionDescriptor action)
{
var dataTokenNamespace = (string)routeContext.RouteData.DataTokens.FirstOrDefault(dt => dt.Key == "Namespace").Value;
var actionNamespace = ((ControllerActionDescriptor)action).MethodInfo.DeclaringType.FullName;
return dataTokenNamespace == actionNamespace;
}
}
}
First answer:
Does attribute routing funk up the routes defined by app.UseMvc()?
Attribute routing and Convention-based routing (routes.MapRoute(...
) work independently. And attribute routes have advantage over convention routes.
but it doesn't seem to do what I want. Is there a way to restrict the routing engine to a certain namespace?
Answer from developers:
Instead of using a list of namespaces to group your controllers we recommend using Areas. You can attribute your controllers (regardless of which assembly they are in) with a specific Area and then create a route for that Area.
You can see a test website that shows an example of using Areas in MVC 6 here: https://github.com/aspnet/Mvc/tree/dev/test/WebSites/RoutingWebSite.
Example using Area with convention-based routing
Controller:
//Reached through /admin/users
//have to be located into: project_root/Areas/Admin/
[Area("Admin")]
public class UsersController : Controller
{
}
Configure convention-based routing:
app.UseMvc(routes =>
{
routes.MapRoute(
"areaRoute",
"{area:exists}/{controller}/{action}",
new { controller = "Home", action = "Index" });
}
Example using Area with attribute-based routing
//Reached through /admin/users
//have to be located into: project_root/Areas/Admin/
[Area("Admin")]
[Route("[area]/[controller]/[action]", Name = "[area]_[controller]_[action]")]
public class UsersController : Controller
{
}