Как бороться с конфликтами маршрутизации в WebAPI - идентификаторы строк против действия + идентификатор
Я размещаю REST API с помощью Web API в.NET 4 (версия WebAPI 4.0.30506 для nuget). Чтобы учесть более сложную маршрутизацию атрибутов, я также включил в свое решение attterouting.net.
У меня есть 2 атрибутных маршрута, которые конфликтуют. Причиной конфликта является то, что мы запрашиваем строковый идентификатор в одном вызове, а строковое действие + числовой идентификатор - в другом. Сообщение, добавленное в ответ HTTP, гласит: Multiple actions were found that match the request
, Вот примеры запросов, чтобы продемонстрировать оба из них:
1) http://example/api/libraries/?libraryId=some_library (some_library is a string identifier)
2) http://example/api/libraries/bookStatus/1 (1 is the library database ID)
Я боролся с различными способами сделать эту работу. Мои текущие подписи контроллера выглядят так:
[GET("api/libraries/?libraryId={libraryId}", Precedence = 2)]
[System.Web.Http.HttpGet]
public Library QueryLibraryByLibraryId(string libraryId){}
[GET("api/libraries/bookStatus/{libraryId:long}", Precedence = 1)]
[System.Web.Http.HttpGet]
public Dictionary<string, Dictionary<string, string>> QueryBookStatus(long libraryId){}
Я могу понять, почему маршрутизация может запутаться: как узнать, что строковый идентификатор "bookStatus/1"
является недействительным, это хороший вопрос. Тем не менее, я решил, что это должно быть проще для определения некоторых ограничений вокруг.
Как мне разрешить эти конфликты при сохранении структуры подписей такого типа?
2 ответа
Проблема заключается в том, что соглашение о маршрутизации WebAPI по умолчанию сопоставляется до атрибутной маршрутизации.
Вы должны установить атрибут маршрутизации как маршрутизацию по умолчанию, что означает, что в WebApiConfig.cs вам нужно вызвать
config.Routes.MapHttpAttributeRoutes
во-первых, прежде чем позвонить другим
config.Routes.MapHttpRoute
,
Еще одна необязательная вещь, которую стоит упомянуть, вам не нужно указывать libraryId в части запроса для первого метода.
Это может быть сделано автоматически.
[GET("api/libraries", Precedence = 2)]
[System.Web.Http.HttpGet]
public Library QueryLibraryByLibraryId(string libraryId) { }
[GET("api/libraries/bookStatus/{libraryId:long}", Precedence = 1)]
[System.Web.Http.HttpGet]
public Dictionary<string, Dictionary<string, string>> QueryBookStatus(long libraryId) { }
Я проверил вашу конфигурацию с примером проекта. И, кажется, вы сделали ошибку в одной из следующих областей. Я даю краткое изложение того, что я сделал, пожалуйста, убедитесь, что вы сделали то же самое, чтобы заставить его работать -
One Remove Default Routing.
По умолчанию маршруты ASP.Net MVC настроены на обслуживание со значениями по умолчанию. Это первое, что я удалил, чтобы убедиться, что моя атрибутная маршрутизация работает. Мы можем привести маршрут по умолчанию позже, но пока удалите его, чтобы он не мешал. Поэтому удалите следующее или комментарий от WebApiConfig
учебный класс -
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Так что мой конфиг маршрута выглядит так -
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
//config.Routes.MapHttpRoute(
// name: "DefaultApi",
// routeTemplate: "api/{controller}/{id}",
// defaults: new { id = RouteParameter.Optional }
//);
// Uncomment the following line of code to enable query support for actions with an IQueryable or IQueryable<T> return type.
// To avoid processing unexpected or malicious queries, use the validation settings on QueryableAttribute to validate incoming queries.
// For more information, visit http://go.microsoft.com/fwlink/?LinkId=279712.
//config.EnableQuerySupport();
// To disable tracing in your application, please comment out or remove the following line of code
// For more information, refer to: http://www.asp.net/web-api
config.EnableSystemDiagnosticsTracing();
}
}
Это важно, потому что это главная причина, по которой маршрут портится. Я объясню позже, почему.
Два регистра атрибута маршрутов.
Я использовал следующий код для регистрации моих атрибутов маршрутов, вот так выглядит запуск моего приложения в Global.asax -
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
//code to register attribtue routes
GlobalConfiguration.Configuration.Routes.MapHttpAttributeRoutes(config =>
{
config.AddRoutesFromAssembly(Assembly.GetExecutingAssembly());
});
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
Теперь, когда у нас есть маршруты в местах, давайте заставим наши методы контроллера выполнять тесты -
namespace MvcApplication1.Controllers
{
public class ValuesController : ApiController
{
......
[GET("api/libraries", Precedence = 2)]
public string Get_1(string id)
{
return "value :" + id;
}
[GET("api/libraries/bookStatus/{id:long}", Precedence = 1)]
public string Get_2(int id)
{
return "value :" + id;
}
......
}
}
и угадай что? оба URL работает -
/api/libraries/?id=2
/api/libraries/bookStatus/5
На данный момент у вас есть решение. Но давайте посмотрим, почему это произошло? Описано ниже -
Причина
Сначала обратите внимание, что маршрут по умолчанию должен был обслуживаться именем контроллера, а также имя моего контроллера не соответствует используемому пути, это потому, что я хотел показать вам, почему это происходит. Поскольку наша маршрутизация атрибутов работает, мы обслуживаем URL, даже если имя нашего контроллера API отличается от маршрутов. Но маршрут по умолчанию не может этого сделать и, следовательно, ошибка.
Сначала давайте проверим, что наш маршрут по умолчанию не работает, перейдя в /api/{controller}/{id}
и поэтому мы просматриваем - /api/values/5
и это говорит 404. Его очевидная причина, мы удалили это.
Давайте вернем наш старый маршрут по умолчанию MVC, который мы удалили в One -
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Теперь давайте попробуем еще раз и VOILA!! Мы получили нашу ошибку:) -
Мне нужно объяснить больше?:) ... думаю, у тебя есть причина
Но подождите... нам нужны наши маршруты по умолчанию, верно? Хорошо, тогда просто зарегистрируйте его после наших атрибутов маршрутов, и это решение... все работает на этом этапе -
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
//code to register attribtue routes
GlobalConfiguration.Configuration.Routes.MapHttpAttributeRoutes(config =>
{
config.AddRoutesFromAssembly(Assembly.GetExecutingAssembly());
});
//after attribute routes, now we can safely register our default routes
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}