Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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()
like image 424
Hannes Sachsenhofer Avatar asked Dec 16 '15 08:12

Hannes Sachsenhofer


1 Answers

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
{
    
}
like image 116
Stas Boyarincev Avatar answered Nov 17 '22 01:11

Stas Boyarincev