4

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

}

1 Answer 1

7

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.

Sign up to request clarification or add additional context in comments.

12 Comments

@MarekSebera I found something that might be what you're looking for if you're willing to implement some custom methods: gist.github.com/damianh/5d69be0e3004024f03b6cc876d7b0bd3
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!
Also it might be necessary to use UseSpecificControllers before using AddControllers, i'm not really sure.
Thank you, I've added the usage example and code formatting to your answer
@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.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.