Подтвердить что ты не робот

Как вы наследуете префикс маршрута на уровне класса контроллера в WebApi?

Примечание. Я прочитал о новых функциях маршрутизации как части WebApi 2.2, чтобы разрешить наследование маршрутов. Однако это, похоже, не решает мою конкретную проблему. Кажется, он решает проблему наследования атрибутов маршрута уровня действия, но не префиксов маршрутов, определенных на уровне класса. http://www.asp.net/web-api/overview/releases/whats-new-in-aspnet-web-api-22#ARI

Я хотел бы сделать что-то вроде этого:

[RoutePrefix("account")]
public abstract class AccountControllerBase : ControllerBase { }

[RoutePrefix("facebook")]
public class FacebookController : AccountControllerBase
{
    [Route("foo")]
    public async Task<string> GetAsync() { ... }
}

[RoutePrefix("google")]
public class GoogleController : AccountControllerBase
{
    [Route("bar")]
    public async Task<string> GetAsync() { ... }
}

Я бы хотел, чтобы префикс маршрута account наследовался, поэтому при определении контроллеров Facebook и Google я получаю маршруты:

~/account/facebook/foo
~/account/google/bar

В настоящее время маршруты определяются без части account из базового класса.

4b9b3361

Ответ 1

У меня было аналогичное требование. Я сделал это:

public class CustomDirectRouteProvider : DefaultDirectRouteProvider
{
    protected override string GetRoutePrefix(HttpControllerDescriptor controllerDescriptor)
    {
        var routePrefix =  base.GetRoutePrefix(controllerDescriptor);
        var controllerBaseType = controllerDescriptor.ControllerType.BaseType;

        if (controllerBaseType == typeof(BaseController))
        {
            //TODO: Check for extra slashes
            routePrefix = "api/{tenantid}/" + routePrefix;
        }

        return routePrefix;
    }
}

Где BaseController - это тот, который определяет префикс. Теперь нормальные префиксы работают, и вы можете добавить свои собственные. При настройке маршрутов вызовите

config.MapHttpAttributeRoutes(new CustomDirectRouteProvider());

Ответ 2

Как @HazardouS идентифицирует, ответ @Grbinho жестко запрограммирован. Заимствование из этого ответа на наследование прямой маршрутизации и из @HazardouS, я написал этот объект

public class InheritableDirectRouteProvider : DefaultDirectRouteProvider {}

Затем переопределите следующие методы, надеясь, что RoutePrefixAttribute наследуется:

protected override IReadOnlyList<IDirectRouteFactory> GetControllerRouteFactories(HttpControllerDescriptor controllerDescriptor)
{
  // Inherit route attributes decorated on base class controller
  // GOTCHA: RoutePrefixAttribute doesn't show up here, even though we were expecting it to.
  //  Am keeping this here anyways, but am implementing an ugly fix by overriding GetRoutePrefix
  return controllerDescriptor.GetCustomAttributes<IDirectRouteFactory>(true);
}

protected override IReadOnlyList<IDirectRouteFactory> GetActionRouteFactories(HttpActionDescriptor actionDescriptor)
{
  // Inherit route attributes decorated on base class controller actions
  return actionDescriptor.GetCustomAttributes<IDirectRouteFactory>(true);
}

К сожалению, в соответствии с полученным комментарием RoutePrefixAttribute не отображается в списке factory. Я не вникал в то, почему, если кто-то хочет исследовать немного глубже в этом. Поэтому я сохранил эти методы для будущей совместимости и переопределил метод GetRoutePrefix следующим образом:

protected override string GetRoutePrefix(HttpControllerDescriptor controllerDescriptor)
{
  // Get the calling controller route prefix
  var routePrefix = base.GetRoutePrefix(controllerDescriptor);

  // Iterate through each of the calling controller base classes that inherit from HttpController
  var baseControllerType = controllerDescriptor.ControllerType.BaseType;
  while(typeof(IHttpController).IsAssignableFrom(baseControllerType))
  {
    // Get the base controller route prefix, if it exists
    // GOTCHA: There are two RoutePrefixAttributes... System.Web.Http.RoutePrefixAttribute and System.Web.Mvc.RoutePrefixAttribute!
    //  Depending on your controller implementation, either one or the other might be used... checking against typeof(RoutePrefixAttribute) 
    //  without identifying which one will sometimes succeed, sometimes fail.
    //  Since this implementation is generic, I'm handling both cases.  Preference would be to extend System.Web.Mvc and System.Web.Http
    var baseRoutePrefix = Attribute.GetCustomAttribute(baseControllerType, typeof(System.Web.Http.RoutePrefixAttribute)) 
      ?? Attribute.GetCustomAttribute(baseControllerType, typeof(System.Web.Mvc.RoutePrefixAttribute));
    if (baseRoutePrefix != null)
    {
      // A trailing slash is added by the system. Only add it if we're prefixing an existing string
      var trailingSlash = string.IsNullOrEmpty(routePrefix) ? "" : "/";
      // Prepend the base controller prefix
      routePrefix = ((RoutePrefixAttribute)baseRoutePrefix).Prefix + trailingSlash + routePrefix;
    }

    // Traverse up the base hierarchy to check for all inherited prefixes
    baseControllerType = baseControllerType.BaseType;
  }

  return routePrefix;
}

Примечания:

  • Метод Attribute.GetCustomAttributes(сборка, тип, bool) включает в себя "наследовать" логическое... но он игнорируется для этого метода подпись. ARG! Потому что, если бы это сработало, мы могли бы отказаться от цикл отражения... который переносит нас в следующую точку:
  • Это пересекает иерархию наследования с отражением. Не идеально из-за вызовов O (n) через отражение, но для моего необходимо. Вы можете избавиться от цикла, если у вас есть только 1 или 2 уровня наследования.
  • В GOTCHA в коде, RoutePrefixAttribute объявляется в System.Web.Http и в System.Web.Mvc. Они оба наследуют непосредственно от Атрибута, и они оба реализуют свои собственные Интерфейс IRoutePrefix (т. System.Web.Http.RoutePrefixAttribute < - System.Web.Http.IRoutePrefix а также System.Web.Mvc.RoutePrefixAttribute < - System.Web.Mvc.IRoutePrefix). Конечным результатом является то, что библиотека, используемая для объявления вашего контроллера (web.mvc или web.http) - это библиотека, атрибут RoutePrefixAttribute назначены. Это, конечно, имеет смысл, но я потерял 2 часа код рефакторинга, который был фактически законным, потому что мой тестовый пример неявно проверяется на System.Web.Http.RoutePrefixAttribute, но контроллер был объявлен с помощью System.Web.Mvc... Следовательно, явное пространство имен в коде.

Ответ 3

Пробовал это в ASP.NET Web Api 2.2 (должен/мог также работать в MVC):

public class InheritedRoutePrefixDirectRouteProvider : DefaultDirectRouteProvider
{
    protected override string GetRoutePrefix(HttpControllerDescriptor controllerDescriptor)
    {
        var sb = new StringBuilder(base.GetRoutePrefix(controllerDescriptor));
        var baseType = controllerDescriptor.ControllerType.BaseType;

        for (var t = baseType; typeof(ApiController).IsAssignableFrom(t); t = t.BaseType)
        {
            var a = (t as MemberInfo).GetCustomAttribute<RoutePrefixAttribute>(false);
            if (a != null)
            {
                sb.Insert(0, $"{a.Prefix}{(sb.Length > 0 ? "/": "")}");
            }
        }

        return sb.ToString();
    }
}

Он связывает префиксы маршрута вместе в цепочке наследования контроллера.

Ответ 4

Можно подтвердить, что ответ Marlon работает в MVC, переключив типы веб-API на эквивалентные типы MVC:

public class InheritedRoutePrefixDirectRouteProvider : DefaultDirectRouteProvider
{
    protected override string GetRoutePrefix(ControllerDescriptor controllerDescriptor)
    {
        var sb = new StringBuilder(base.GetRoutePrefix(controllerDescriptor));
        var baseType = controllerDescriptor.ControllerType.BaseType;

        for (var t = baseType; typeof(Controller).IsAssignableFrom(t); t = t.BaseType)
        {
            var a = (t as MemberInfo).GetCustomAttribute<RoutePrefixAttribute>(false);
            if (a != null)
            {
                sb.Insert(0, $"{a.Prefix}{(sb.Length > 0 ? "/" : "")}");
            }
        }

        return sb.ToString();
    }
}

Ответ 5

Может быть, уже поздно, но я думаю, что этот атрибут базового контроллера заставит его работать:

[Route("account/[Controller]")]