Унаследовать маршрутизацию от базы контроллера baseapi к производному контроллеру - PullRequest
0 голосов
/ 07 ноября 2019

Я использую .NET Core 3.0 Web API, у меня есть ситуация, когда я установил префикс в baseapicontroller, а также установил маршрут в производном контроллере, теоретически я думал, что он будет работать как " baseapicontroller route/ производный маршрут контроллера", но он так не работает, ядро ​​.net выбирает только маршрут производного класса и игнорирует маршрут baseapicontroller.

Я попытался использовать атрибут" Маршрут"и" RoutePrefix", он все тот же.

[ApiController]
[RoutePrefix("api/v1")]
//[Route("api/v1")]
public class BaseApiController : ControllerBase
{
}

производный контроллер

[Route("testing-route")]
public class TestRouteController : BaseApiController
{
    [HttpGet]
    public string test()
    {

        return "";
    }
}

Я хочу, чтобы URL был" api / v1 / testing-route / test", я не хочу повторять api / v1 в каждом контроллере. а также я знаю, что люди могут сказать, что я могу использовать « api / v1 / [controller] / [action] », но это не мое требование. Я хочу использовать пользовательский маршрут для контроллера.

С нетерпением жду любых хороших ответов.

Спасибо

1 Ответ

2 голосов
/ 08 ноября 2019

Встроенный RouteAttribute не будет работать с наследованием контроллера по умолчанию. Однако для достижения своей цели вы можете создать пользовательский IControllerModelConvention, чтобы применить правило, учитывающее наследование контроллера во время запуска.

How-to

Чтобы избежать путаницы со значением по умолчанию [RouteAttribute], я создаю пользовательский MyRoutePrefixAttribute вместо использования значения по умолчанию [RouteAttribute] (не стесняйтесь использовать [RouteAttribute], если считаете, что это поведение должно быть переопределено):

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

И для поиска этого [MyRoutePrefixAttribute] из цепочки наследования я создаю RoutePrefixConvention, который будет рекурсивно объединять префиксы:

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);
        }
    }
}

это соглашение будет НЕ влиятьпроизводительность: он ищет только все атрибуты [MyRoutePrefixAttribute] по цепочке наследования во время запуска .

Наконец, не забудьте добавить это соглашение при запуске:

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

Демонстрация

Давайте создадим три контроллера для тестирования вышеуказанной RoutePrefixConvention:

RootApiController -> BaseApiController -> TestRouteController

[ApiController]
[MyRoutePrefix("root/controllers")]
public class RootApiController : ControllerBase { }

[MyRoutePrefix("api/v1")]
public class BaseApiController : RootApiController { }

[Route("testing-route")]
public class TestRouteController : BaseApiController
{
    [HttpGet]
    public string test()
    {
        return "abc";
    }
}

Теперь при доступе к /root/controllers/api/v1/testing-route мы получим строку abc:

custom route prefix through inheritance

...