Search code examples
c#asp.net-core-webapiasp.net-web-api-routing

Extending API in one project fully exposes referenced API but routes clash


I'm trying to split a Core 2.1 WebAPI project into two in order that we can expose two different APIs according to circumstances. Simplified, we have one API and we want all the read-only (GET) requests in one API and the entire set in another (the "admin" API). Swagger is enabled in the projects.

I duplicated the project, renaming one (namespaces, etc.) and adding both to the same solution, then commented out all the non-GET controller methods in the read-only project and commented out all the GET methods in the admin project. I then added a reference to the read-only project in the admin project.

Running the read-only project, the swagger page came up fine, just the GETs. Running the admin project gave a 500 on the swagger page. Interestingly, during debugging, I found that removing All the controllers from the admin project, the underlying API from the read-only project was completely exposed straight through and appeared fully functional - not something I was expecting and a potential security issue for anyone not expecting it.

However, I then added one controller back and changed it to decend from one of the read-only controllers, over-riding the ancestor contructor, etc. - it still gave a 500.

Base class:

namespace InfoFeed.WebAPI.Features.Account
{
    /// <summary>
    /// Handle user account related tasks
    /// </summary>
    [Authorize]
    [Produces("application/json")]
    [Route("api/account")]
    public class AccountController : Controller
    {
        private readonly ILogger _log;
        protected readonly IMediator _mediator;

        public AccountController(ILogger<AccountController> log,
                                 IMediator mediator)
        {
            _log = log;
            _mediator = mediator;
        }

Descendent class:

namespace InfoFeedAdmin.WebAPI.Features.Account
{
    /// <summary>
    /// Handle user account related tasks
    /// </summary>
    [Authorize]
    [Produces("application/json")]
    [Route("api/account")]
    public class AccountAdminController 
        : InfoFeed.WebAPI.Features.Account.AccountController
    {
        public AccountAdminController(ILogger<AccountAdminController> log,
                                 IMediator mediator)
            : base(log, mediator)
        {
        }

I thought that perhaps the route might be causing a clash so I tried changing that to [Route("api/admin/account")] - this worked as long as there were no clashing method signatures. However, it means that there are two sets of routes exposed to the same underlying controller methods.

POST /api/account/signin
GET /api/account/signout

POST /api/admin/account/signin
GET /api/admin/account/signout

Does anyone know how I can hide (perhaps selectively) the routes from the ancestor class so that only the routes I choose to expose from the descendent class are visible/accessible?

Cheers


Solution

  • By default MVC will search the dependency tree and find controllers (even in other assemblies).
    You can use application parts to avoid looking for controllers in a particular assembly or location.

    If you have an assembly that contains controllers you don't want to be used, remove it from the ApplicationPartManager:

    services.AddMvc()
    .ConfigureApplicationPartManager(apm =>
    {
        var dependentLibrary = apm.ApplicationParts
            .FirstOrDefault(part => part.Name == "DependentLibrary");
    
        if (dependentLibrary != null)
        {
           p.ApplicationParts.Remove(dependentLibrary);
        }
    })
    

    Source: https://learn.microsoft.com/en-us/aspnet/core/mvc/advanced/app-parts?view=aspnetcore-2.1