3

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()
  {
    // ...
  }

}
silkfire
  • 24,585
  • 15
  • 82
  • 105
Marek Sebera
  • 39,650
  • 37
  • 158
  • 244

1 Answers1

5

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.

Marek Sebera
  • 39,650
  • 37
  • 158
  • 244
silkfire
  • 24,585
  • 15
  • 82
  • 105
  • Thank you, I use specific pattern, but not always, is it possible to extract the attribute routes by interop/reflection/... and pass them to `MapControllerRoute` ? – Marek Sebera Jul 27 '21 at 21:00
  • 1
    @MarekSebera I found something that might be what you're looking for if you're willing to implement some custom methods: https://gist.github.com/damianh/5d69be0e3004024f03b6cc876d7b0bd3 – silkfire Jul 27 '21 at 21:50
  • 1
    well damn, it works, could you please post the code from gist as an answer along with the usage (i went with `services.AddMvcCore().UseSpecificControllers(typeof(MyApiController));`). Thank you! – Marek Sebera Jul 28 '21 at 06:47
  • 1
    Also it might be necessary to use `UseSpecificControllers` before using `AddControllers`, i'm not really sure. – Marek Sebera Jul 28 '21 at 06:50
  • @MarekSebera I've updated my answer. Glad the suggested solution worked out for you :) – silkfire Jul 28 '21 at 13:35
  • 1
    Thank you, I've added the usage example and code formatting to your answer – Marek Sebera Jul 28 '21 at 18:11
  • Is the class name/attribute check in `IsController` still necessary if we're providing a list of classes anyway? – Medinoc Sep 25 '21 at 10:18
  • 1
    @Medinoc In practice probably not, but you it could be good to have a guard there in case you provide an interface or a struct by accident. – silkfire Sep 25 '21 at 15:11