Несколько типов контроллеров с одинаковым префиксом маршрута ASP.NET Web Api

Можно ли разделить GET и POST на отдельные типы контроллеров API и получить к ним доступ с использованием одного и того же префикса маршрута?

Вот мои контроллеры:

[RoutePrefix("api/Books")]
public class BooksWriteController : EventStoreApiController
{
    [Route("")]
    public void Post([FromBody] CommandWrapper commandWrapper){...}
}

[RoutePrefix("api/Books")]
public class BooksReadController : MongoDbApiController
{
    [Route("")]
    public Book[] Get() {...}

    [Route("{id:int}")]
    public Book Get(int id) {...}
}

person user1145404    schedule 15.04.2014    source источник
comment
Хотя это все равно будет один и тот же контроллер, вы можете сделать свой контроллер частичным классом с вашими GET в одном файле и вашими POSTS в другом.   -  person user875234    schedule 25.04.2019


Ответы (3)


Веб-API (1.x-2.x) не поддерживает несколько маршрутов атрибутов с одним и тем же путем на разных контроллерах. Результат — 404, поскольку все маршруты соответствуют более чем одному контроллеру, и в этот момент веб-API будет считать результат неоднозначным.

Обратите внимание, что MVC Core поддерживает это примечание к сценарию: MVC Core служит как MVC и веб-API.

Если вы решите использовать веб-API 2.11 (или новее), вы можете создать ограничение маршрута для метода http для каждого контроллера и использовать его вместо встроенного атрибута маршрута. В приведенном ниже примере показано, что вы можете использовать RoutePrefix или напрямую Routes (например, ответ kmacdonald).

using System.Collections.Generic;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Routing;

public class BooksWriteController : ApiController
{
    [PostRoute("api/Books")]
    public void Post() { }
}

[RoutePrefix("api/books")]
public class BooksReadController : ApiController
{
    [GetRoute]
    public void Get() { }

    [GetRoute("{id:int}")]
    public void Get(int id) { }
}

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

class GetRouteAttribute : MethodConstraintedRouteAttribute
{
    public GetRouteAttribute(string template) : base(template ?? "", HttpMethod.Get) { }
}

class PostRouteAttribute : MethodConstraintedRouteAttribute
{
    public PostRouteAttribute(string template) : base(template ?? "", HttpMethod.Post) { }
}

Этот класс позволяет добавлять ограничения к сгенерированному маршруту.

class MethodConstraintedRouteAttribute : RouteFactoryAttribute
{
    public MethodConstraintedRouteAttribute(string template, HttpMethod method)
        : base(template)
    {
        Method = method;
    }

    public HttpMethod Method
    {
        get;
        private set;
    }

    public override IDictionary<string, object> Constraints
    {
        get
        {
            var constraints = new HttpRouteValueDictionary();
            constraints.Add("method", new MethodConstraint(Method));
            return constraints;
        }
    }
}

Это просто стандартное ограничение маршрута, nit: вы можете кэшировать объект ограничений, чтобы уменьшить выделение.

class MethodConstraint : IHttpRouteConstraint
{
    public HttpMethod Method { get; private set; }

    public MethodConstraint(HttpMethod method)
    {
        Method = method;
    }

    public bool Match(HttpRequestMessage request,
                      IHttpRoute route,
                      string parameterName,
                      IDictionary<string, object> values,
                      HttpRouteDirection routeDirection)
    {
        return request.Method == Method;
    }
}
person Yishai Galatzer    schedule 16.04.2014
comment
@YishaiGalatzer: любая конкретная причина, по которой вам нужен пользовательский MethodConstraint здесь вместо повторного использования System.Web.Http.Routing.HttpMethodConstraint? - person Serguei; 07.11.2015
comment
Дело в ограничении самого маршрута. Встроенное свойство действует только при выборе действия, которое запускается слишком поздно. - person Yishai Galatzer; 08.11.2015
comment
В случае, если кто-то просматривает этот ответ и у него возникают проблемы с названием маршрута, а затем с построением маршрута с использованием Url.Link(...). Здесь есть аналогичный ответ: stackoverflow.com/questions/40892637/ Основными отличиями, которые я видел, были дополнительные интерфейсы и возможное использование HttpMethodConstraint с массивом HttpMethods. В любом случае, другой ответ позволяет именованию маршрутов по-прежнему работать, к вашему сведению. - person wigs; 03.08.2017

Вам не всегда нужно указывать RoutePrefix на вашем контроллере. вы можете просто указать маршрут непосредственно в веб-методах:

public class BooksWriteController : EventStoreApiController
{
    [Route("api/Books")]
    public void Post([FromBody] CommandWrapper commandWrapper){...}
}

public class BooksReadController : MongoDbApiController
{
    [Route("api/Books")]
    public TaskTypeInfo[] Get() {...}


    [Route("api/Books/{id:int}")]
    public TaskTypeInfo Get(int id) {...}
}

Однако я полагаю, что ваш RoutePrefix будет нормально работать на обоих контроллерах. Я думаю, что атрибут RoutePrefix используется в сочетании с атрибутом Route, который фактически определяет маршрут. Это означает, что пока у вас нет конфликтующих маршрутов (это важно), все будет в порядке.

person kmacdonald    schedule 15.04.2014
comment
Спасибо, это не проблема. Проблема заключается в выборе правильного типа контроллера на основе GET или POST. - person user1145404; 16.04.2014

Воспользуйтесь частичными классами. Частичные классы и методы - C# MSDN

Создайте два файла: BooksController.Write.cs и BooksController.Read.cs. Только RoutePrefix для одного файла, так как они относятся к одному классу, вы получите сообщение об ошибке, говорящее о том, что вы добавляете префикс. один и тот же класс два раза.

Оба файла будут скомпилированы как один класс (поскольку это один класс, но разделенный на разные файлы).

// File BooksController.Write.cs
[RoutePrefix("api/Books")]
public partial class BooksController : EventStoreApiController
{
    [Route("")]
    public void Post([FromBody] CommandWrapper commandWrapper){...}
}

// File BooksController.Read.cs
public partial class BooksController : MongoDbApiController
{
    [Route("")]
    public Book[] Get() {...}


    [Route("{id:int}")]
    public Book Get(int id) {...}
}
person Nuno C    schedule 17.04.2020
comment
получение ошибки -- Частичные объявления не должны указывать разные базовые классы - person ManirajSS; 11.02.2021