简体   繁体   中英

How to apply a common route for multiple ASP.NET MVC controllers

I have a common base controller and a couple of inherited controller classes with similar routes applied to them. I want to apply the common part of the different routes and apply it to the base class. Below is the bare minimum of my code:

using Microsoft.AspNetCore.Mvc;

public abstract class CommonController : Controller
{
    // Some common code here
}

[Route("api/v1/BookLookup")]
public class BooksController : CommonController
{
    public async Task<JsonResult> GetAsync([FromBody]Dto1 filterParameters)
    {
        return await GetApiData(filterParameters);
    }
}

[Route("api/v1/MovieLookup")]
public class MoviesController : CommonController
{
    public async Task<JsonResult> GetAsync([FromBody]Dto1 filterParameters)
    {
        return await GetApiData(filterParameters);
    }
}

I want to have something like the following:

using Microsoft.AspNetCore.Mvc;

[Route("api/v1/")] // or any other attribute?
public abstract class CommonController : Controller
{
    // Some common code here
}

[Route("BookLookup")]
public class BooksController : CommonController
{
    public async Task<JsonResult> GetAsync([FromBody]Dto1 filterParameters)
    {
        return await GetApiData(filterParameters);
    }
}

[Route("MovieLookup")]
public class MoviesController : CommonController
{
    public async Task<JsonResult> GetAsync([FromBody]Dto1 filterParameters)
    {
        return await GetApiData(filterParameters);
    }
}

is this possible and if it is, how?

The project targets .NET Core 2.0 and the Controller class in the above example is ASP.NET Core MVC controller class, not Web API controller.

The built-in RouteAttribute will not work with controller inheritance by default. However, to achieve your goal, you could create a custom IControllerModelConvention to apply a rule that takes consideration of controller inheritance at startup-time.

To avoid mixing up with the default [RouteAttribute] , I create a custom MyRoutePrefixAttribute instead of using the default [RouteAttribute] (Feel free to use the [RouteAttribute] if you think this behavior should be overrided ):

[AttributeUsage(AttributeTargets.Class)]
public class MyRoutePrefixAttribute : Attribute
{
    public MyRoutePrefixAttribute(string prefix) { Prefix = prefix; }
    public string Prefix { get; }
}

And to lookup this [MyRoutePrefixAttribute] from the inheritance chain, I create a RoutePrefixConvention that will combine the prefixes recursively:

public class RoutePrefixConvention : IControllerModelConvention
{
    public void Apply(ControllerModel controller)
    {
        foreach (var selector in controller.Selectors)
        {
            var prefixes = GetPrefixes(controller.ControllerType);  // [prefix, parentPrefix, grandpaPrefix,...]
            if(prefixes.Count ==  0) continue;
            // combine these prefixes one by one
            var prefixRouteModels = prefixes.Select(p => new AttributeRouteModel(new RouteAttribute(p.Prefix)))
                .Aggregate( (acc , prefix)=> AttributeRouteModel.CombineAttributeRouteModel(prefix, acc));
            selector.AttributeRouteModel =  selector.AttributeRouteModel != null ?
                AttributeRouteModel.CombineAttributeRouteModel(prefixRouteModels, selector.AttributeRouteModel):
                selector.AttributeRouteModel = prefixRouteModels;
        }
    }

    private IList<MyRoutePrefixAttribute> GetPrefixes(Type controlerType)
    {
        var list = new List<MyRoutePrefixAttribute>();
        FindPrefixesRec(controlerType, ref list);
        list = list.Where(r => r!=null).ToList();
        return list;

        // find [MyRoutePrefixAttribute('...')] recursively 
        void FindPrefixesRec(Type type, ref List<MyRoutePrefixAttribute> results)
        {
            var prefix = type.GetCustomAttributes(false).OfType<MyRoutePrefixAttribute>().FirstOrDefault();
            results.Add(prefix);   // null is valid because it will seek prefix from parent recursively
            var parentType = type.BaseType;
            if(parentType == null) return;
            FindPrefixesRec(parentType, ref results);
        }
    }
}

this convention will NOT influence the performance: it only searches all the [MyRoutePrefixAttribute] attributes through the inheritance chain at startup time.

Finally, don't forget to add this convention within your startup:

services.AddMvc(opts =>
{
     opts.Conventions.Add(new RoutePrefixConvention());
});

Controller:

[MyRoutePrefix("api/v1")]
public class CommonController : Controller
{ 
}

[Microsoft.AspNetCore.Mvc.Route("MovieLookup")]
public class MoviesController : CommonController
{
    public string GetAsync()
    {
        return "MovieLookup";
    }
}

Test Result:

在此处输入图像描述

Hope this can help you.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM