Search code examples
c#asp.net-core.net-coreroutesasp.net-core-mvc

ASP.NET Core routing - mapping only specific controllers


Per documentation it seems like it's only possible to add either single routes, one by one, or add all routes in annotated (attribute routing) controllers

DOCS: Routing to controller actions in ASP.NET Core

Is it possible to add only all routes belonging to single Controller?

Using UseEndpoints(e => e.MapControllers()) will add all controllers that are annotated, using UseEndpoints(e => e.MapControllerRoute(...)) seems to be able to add only single controller/action route, not all routes that are annotated in given controller

Sample controller:

using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("[controller]")]
public class MyApiController
{

  [Route("/")]
  [Route("[action]")]
  [HttpGet]
  public ResponseType Index()
  {
    // ...
  }

  [Route("[action]")]
  public ResponseType GetListing()
  {
    // ...
  }

}

Solution

  • One solution I found is to build a custom MVC feature provider and implement an extension method that allows you to specify exactly which controllers you want registered.

     public static class MvcExtensions
     {
        /// <summary>
        /// Finds the appropriate controllers
        /// </summary>
        /// <param name="partManager">The manager for the parts</param>
        /// <param name="controllerTypes">The controller types that are allowed. </param>
        public static void UseSpecificControllers(this ApplicationPartManager partManager, params Type[] controllerTypes)
        {
           partManager.FeatureProviders.Add(new InternalControllerFeatureProvider());
           partManager.ApplicationParts.Clear();
           partManager.ApplicationParts.Add(new SelectedControllersApplicationParts(controllerTypes));
        }
     
        /// <summary>
        /// Only allow selected controllers
        /// </summary>
        /// <param name="mvcCoreBuilder">The builder that configures mvc core</param>
        /// <param name="controllerTypes">The controller types that are allowed. </param>
        public static IMvcCoreBuilder UseSpecificControllers(this IMvcCoreBuilder mvcCoreBuilder, params Type[] controllerTypes) => mvcCoreBuilder.ConfigureApplicationPartManager(partManager => partManager.UseSpecificControllers(controllerTypes));
     
        /// <summary>
        /// Only instantiates selected controllers, not all of them. Prevents application scanning for controllers. 
        /// </summary>
        private class SelectedControllersApplicationParts : ApplicationPart, IApplicationPartTypeProvider
        {
           public SelectedControllersApplicationParts()
           {
              Name = "Only allow selected controllers";
           }
    
           public SelectedControllersApplicationParts(Type[] types)
           {
              Types = types.Select(x => x.GetTypeInfo()).ToArray();
           }
     
           public override string Name { get; }
     
           public IEnumerable<TypeInfo> Types { get; }
        }
     
        /// <summary>
        /// Ensure that internal controllers are also allowed. The default ControllerFeatureProvider hides internal controllers, but this one allows it. 
        /// </summary>
        private class InternalControllerFeatureProvider : ControllerFeatureProvider
        {
           private const string ControllerTypeNameSuffix = "Controller";
     
           /// <summary>
           /// Determines if a given <paramref name="typeInfo"/> is a controller. The default ControllerFeatureProvider hides internal controllers, but this one allows it. 
           /// </summary>
           /// <param name="typeInfo">The <see cref="TypeInfo"/> candidate.</param>
           /// <returns><code>true</code> if the type is a controller; otherwise <code>false</code>.</returns>
           protected override bool IsController(TypeInfo typeInfo)
           {
              if (!typeInfo.IsClass)
              {
                 return false;
              }
     
              if (typeInfo.IsAbstract)
              {
                 return false;
              }
     
              if (typeInfo.ContainsGenericParameters)
              {
                 return false;
              }
     
              if (typeInfo.IsDefined(typeof(Microsoft.AspNetCore.Mvc.NonControllerAttribute)))
              {
                 return false;
              }
     
              if (!typeInfo.Name.EndsWith(ControllerTypeNameSuffix, StringComparison.OrdinalIgnoreCase) &&
                         !typeInfo.IsDefined(typeof(Microsoft.AspNetCore.Mvc.ControllerAttribute)))
              {
                 return false;
              }
     
              return true;
           }
        }
     }
    

    Put the extensions class wherever in your project, and use like this

    public void ConfigureServices(IServiceCollection services)
    {
      // put this line before services.AddControllers()
      services.AddMvcCore().UseSpecificControllers(typeof(MyApiController), typeof(MyOtherController));
    }
    

    Source: https://gist.github.com/damianh/5d69be0e3004024f03b6cc876d7b0bd3

    Courtesy of Damian Hickey.