Home > Blockchain >  .NET 6 - Concatenate routes of base classes
.NET 6 - Concatenate routes of base classes

Time:07-12

In my code, I have below base class:

[ApiController]
[Produces("application/json")]
[Route("modules/example/v{apiVersion:apiVersion}")]
public abstract class ModuleControllerBase : ControllerBase
{ ... }

The data which my API handles sometimes has composite primary keys, for which I have defined another base class inheriting the above:

[ApiController]
[Route("subsidiary/{subsidiaryId:int:required}/branch/{branchId:int:required}")]
public abstract class CollectionControllerBase : ModuleControllerBase
{ ... }

Then, finally, an implementation of the above classes would look as such:

[ApiController]
[Route("[controller]")]
public class WorkOrderLineController : CollectionControllerBase
{ ... }

My idea was that this would produce a URL like {api-url}/modules/example/v1/subsidiary/1/branch/3/WorkOrderLine/{some-endpoint-in-WorkOrderLineController} at runtime.

However, when Swagger opens up in the browser it only shows the route from the implemented controller.The generated endpoint doc by Swagger

Is there some default configuration that I need to change, or am I misunderstanding something?

I can only find results about handling polymorphic response models.

CodePudding user response:

In ASP.NET Core, the Route attribute is "static" in the sense that it simply pulls the route from the string value specified, substituting the [controller] token for the controller name.

If you want the behavior you specified, you can either create a new attribute that implements IRouteTemplateProvider or create a custom application model.

A custom attribute, e.g. NestedRouteAttribute, would probably be easier. I don't have a code example, but here's a description:

You can have the IRouteTemplateProvider.Template implementation walk the inheritance tree (using controller.GetType().BaseType) until it hits ControllerBase. Once you have the list of types, you can iterate through the list from parent(s) to child types, concatenating the values of the Route attribute.

Edit: Here's a quick-and-dirty example:

using System.Reflection;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Routing;

[AttributeUsage(AttributeTargets.Class)]
public class NestedRouteAttribute : Attribute, IRouteTemplateProvider
{
    Type _controllerType;
    string _template;

    public NestedRouteAttribute(string template, Type t)
    {
        _controllerType = t;
        _template = template;
    }

    public string? Template
    {
        get
        {
            // Look up the route from the parent type. This only goes up one level, but if the parent class also has a `NestedRouteAttribute`, then it should work recursively.
            Type? bt  = _controllerType.BaseType;

            IRouteTemplateProvider? routeAttr = bt?.GetCustomAttributes().Where(a => a is IRouteTemplateProvider).FirstOrDefault() as IRouteTemplateProvider;
            return Path.Join(routeAttr?.Template, _template);
        }
    }

    public int? Order => null;

    public string? Name => _template;
}
[Route("api/v1")]
public class MyApiControllerBase : ControllerBase { }
[NestedRoute("api/v1", typeof(MyBranchControllerBase))]
public class MyBranchControllerBase : ControllerBase { }
using Microsoft.AspNetCore.Mvc;

[NestedRoute("[controller]", typeof(FooController))]
public class FooController : MyApiControllerBase
{
    [HttpGet]
    public string HelloWorld() => "hello, world!";

}

Requesting /api/v1/branch/division/foo should return hello, world!.

Note 1: Since attributes don't have information about the types they're attached to, if you want to get rid of having to use typeof(FooController) in the attribute, I think you'll need to go with the custom application model instead. You could also change the API to declare the parent class in the NestedRouteAttribute instead.

Note 2: This only retrieves the first route attribute from the parent class, so this doesn't support multiple route attributes on the same class. That's considered bad practice by Microsoft, but if you need it, I think that's a separate question.

  • Related