Множественный метод HttpPost в контроллере веб-API

126

Я начинаю использовать проект MVC4 Web API, у меня есть контроллер с несколькими HttpPostметодами. Контроллер выглядит следующим образом:

контроллер

public class VTRoutingController : ApiController
{
    [HttpPost]
    public MyResult Route(MyRequestTemplate routingRequestTemplate)
    {
        return null;
    }

    [HttpPost]
    public MyResult TSPRoute(MyRequestTemplate routingRequestTemplate)
    {
        return null;
    }
}

Здесь MyRequestTemplateпредставлен шаблонный класс, отвечающий за обработку Json, приходящего через запрос.

Ошибка:

Когда я делаю запрос с помощью Fiddler for http://localhost:52370/api/VTRouting/TSPRouteили http://localhost:52370/api/VTRouting/Route получаю сообщение об ошибке:

Найдено несколько действий, соответствующих запросу

Если я удалю один из вышеуказанных методов, он будет работать нормально.

Global.asax

Я попытался изменить таблицу маршрутизации по умолчанию global.asax, но все еще получаю сообщение об ошибке. Думаю, у меня проблема с определением маршрутов в global.asax. Вот что я делаю в global.asax.

public static void RegisterRoutes(RouteCollection routes)
{
    routes.MapHttpRoute(
        name: "MyTSPRoute",
        routeTemplate: "api/VTRouting/TSPRoute",
        defaults: new { }
    );

    routes.MapHttpRoute(
        name: "MyRoute",
        routeTemplate: "api/VTRouting/Route",
        defaults: new { action="Route" }
    );
}

Я делаю запрос в Fiddler с помощью POST, передавая json в RequestBody для MyRequestTemplate.

Хабиб
источник

Ответы:

143

Вы можете иметь несколько действий в одном контроллере.

Для этого вам нужно сделать следующие две вещи.

  • Сначала украсьте действия ActionNameатрибутом вроде

     [ActionName("route")]
     public class VTRoutingController : ApiController
     {
       [ActionName("route")]
       public MyResult PostRoute(MyRequestTemplate routingRequestTemplate)
       {
         return null;
       }
    
      [ActionName("tspRoute")]
      public MyResult PostTSPRoute(MyRequestTemplate routingRequestTemplate)
      {
         return null;
      }
    }
  • Во-вторых, определите следующие маршруты в WebApiConfigфайле.

    // Controller Only
    // To handle routes like `/api/VTRouting`
    config.Routes.MapHttpRoute(
        name: "ControllerOnly",
        routeTemplate: "api/{controller}"               
    );
    
    
    // Controller with ID
    // To handle routes like `/api/VTRouting/1`
    config.Routes.MapHttpRoute(
        name: "ControllerAndId",
        routeTemplate: "api/{controller}/{id}",
        defaults: null,
        constraints: new { id = @"^\d+$" } // Only integers 
    );
    
    // Controllers with Actions
    // To handle routes like `/api/VTRouting/route`
    config.Routes.MapHttpRoute(
        name: "ControllerAndAction",
        routeTemplate: "api/{controller}/{action}"
    );
Асиф Муштак
источник
Что делать, если я не хочу устанавливать какие-либо ограничения на тип идентификатора? Значение: как я могу также принимать строковые идентификаторы?
frapontillo
5
@frapontillo: идентификатор должен быть целочисленным, так что он отделен от имени маршрута, иначе механизм маршрутизации будет рассматривать его как имя действия, а не как идентификатор. Если вам нужно иметь идентификатор в виде строки, вы можете создать действие.
Асиф Муштак,
Вместо этого я бы использовал маршрутизацию атрибутов. Таким образом, вам не придется использовать несколько маршрутов в WebApiConfig. Ознакомьтесь с этой ссылкой: docs.microsoft.com/en-us/aspnet/web-api/overview/…
Rich
Если я добавлю что-то подобное, у меня появится ошибка ------------ пространство имен ImageDownloadApplication.Controllers {public class FrontModel {public string skus {get; устанавливать; }} [ActionName ("ProductController")] открытый класс ProductController: ApiController {// GET: api / NewCotroller public IEnumerable <string> Get () {return new string [] {"value1", "value2"}; }
Умашанкар
42

Гораздо лучшим решением вашей проблемы было бы использование, Routeкоторое позволяет вам указать маршрут в методе с помощью аннотации:

[RoutePrefix("api/VTRouting")]
public class VTRoutingController : ApiController
{
    [HttpPost]
    [Route("Route")]
    public MyResult Route(MyRequestTemplate routingRequestTemplate)
    {
        return null;
    }

    [HttpPost]
    [Route("TSPRoute")]
    public MyResult TSPRoute(MyRequestTemplate routingRequestTemplate)
    {
        return null;
    }
}
Wisienkas
источник
В каком пространстве имен находится маршрут? Я использую MVC4, а маршрут не распознается.
eaglei22
Да, так и должно быть. Спасибо.
Newman
1
по какой-то причине я не могу заставить это работать. это именно то, что я уже делал.
oligofren
2
Как бы выглядел URL-адрес, чем для вызова Routeи TSPRoute?
Si8
27

использовать:

routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{action}/{id}",
    defaults: new { id = RouteParameter.Optional }
);

это больше не подход RESTful, но теперь вы можете вызывать свои действия по имени (вместо того, чтобы позволить веб-API автоматически определять одно для вас на основе глагола), например:

[POST] /api/VTRouting/TSPRoute

[POST] /api/VTRouting/Route

Вопреки распространенному мнению, в этом подходе нет ничего плохого, и он не злоупотребляет веб-API. Вы по-прежнему можете использовать все замечательные функции веб-API (делегирование обработчиков, согласование содержимого, форматирование mediatype и т. Д.) - вы просто отказываетесь от подхода RESTful.

Филип В
источник
1
Спасибо за ответ, но он по-прежнему вызывает ту же ошибку.
Habib
Это невозможно, значит, в вашем приложении должно быть неправильно настроено что-то еще. Можете показать всю настройку Рут? Также как именно вы вызываете действия контроллеров?
Filip W
Вся настройка маршрута находится в global.asax, я разместил эту часть в своем вопросе. Для запроса я использую Fiddler-> Compose-> и выбираю Post в качестве операции
Хабиб
попробуйте удалить все остальные определения маршрутов и просто оставьте то, что я опубликовал. Затем вы можете легко вызвать оба действия POST, расположенные в одном контроллере (так же, как старый подход MVC)
Filip W
1
Филип, я использую .Net framework 4.5 с mvc4 или Visual studio 2012 RC, какой шаблон вы используете для создания своего проекта, ваш - работает отлично
Хабиб
13

Конечная точка веб-API (контроллер) - это единственный ресурс, который принимает команды get / post / put / delete. Это не обычный контроллер MVC.

Обязательно при /api/VTRoutingможет быть только один метод HttpPost, принимающий отправляемые вами параметры. Имя функции не имеет значения , если вы украшаете его содержимым [http]. Но я никогда не пробовал.

Изменить: это не работает. При разрешении это похоже на количество параметров, а не на попытку привязки модели к типу.

Вы можете перегрузить функции, чтобы они принимали другие параметры. Я почти уверен, что все будет в порядке, если вы объявите это так, как вы делаете, но использовали бы другие (несовместимые) параметры для методов. Если параметры совпадают, вам не повезло, поскольку привязка модели не будет знать, какой из них вы имели в виду.

[HttpPost]
public MyResult Route(MyRequestTemplate routingRequestTemplate) {...}

[HttpPost]
public MyResult TSPRoute(MyOtherTemplate routingRequestTemplate) {...}

Эта часть работает

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

public class ValuesController : ApiController
{
    // GET is overloaded here.  one method takes a param, the other not.
    // GET api/values  
    public IEnumerable<string> Get() { .. return new string[] ... }
    // GET api/values/5
    public string Get(int id) { return "hi there"; }

    // POST api/values (OVERLOADED)
    public void Post(string value) { ... }
    public void Post(string value, string anotherValue) { ... }
    // PUT api/values/5
    public void Put(int id, string value) {}
    // DELETE api/values/5
    public void Delete(int id) {}
}

Если вы хотите создать один класс, который выполняет множество функций, для использования ajax нет большой причины не использовать стандартный шаблон контроллер / действие. Единственная реальная разница в том, что сигнатуры ваших методов не такие красивые, и вам нужно обернуть все, Json( returnValue)прежде чем возвращать их.

Редактировать:

Перегрузка отлично работает при использовании стандартного шаблона (отредактированного для включения) при использовании простых типов. Я пошел и протестировал и другой способ, с двумя пользовательскими объектами с разными подписями. Никогда не мог заставить его работать.

  • Связывание со сложными объектами не выглядит «глубоким», так что это недопустимо.
  • Вы можете обойти это, передав дополнительный параметр в строке запроса
  • Лучшее описание, чем я могу дать доступных вариантов,

В данном случае это сработало для меня, посмотрим, к чему это приведет. Исключение только для тестирования.

public class NerdyController : ApiController
{
    public void Post(string type, Obj o) { 
        throw new Exception("Type=" + type + ", o.Name=" + o.Name ); 
    }
}

public class Obj {
    public string Name { get; set; }
    public string Age { get; set; }
}

И вызывается так из консоли:

$.post("/api/Nerdy?type=white", { 'Name':'Slim', 'Age':'21' } )
Эндрю Бэкер
источник
Я попытался изменить типы параметров, но, похоже, он позволяет использовать только один метод Post в контроллере. Спасибо за ответ
Хабиб
Я предположил, что он попробует привязку модели, чтобы найти его, поскольку вы можете перегрузить. Но он работает с разными # параметров. Возможно, не так уж и сложно переписать это, чтобы сделать это, но они еще не выпустили исходный код, поэтому я просто застрял, глядя на уродливую дизассемблировку,
Эндрю Бэкер
2
+1 для фактического объяснения причины, по которой он не работает, и философии веб-API.
MEMark
Оцените разбивку ... Я предполагал, что это должен быть один POST / PUT / GET для каждого контроллера, но я не был уверен ... поэтому я искал его. С тех пор, как я начал разрабатывать с помощью MVC веб-приложения, где несколько подобных действий на контроллер являются нормой ... это почти кажется пустой тратой, поэтому я могу понять, зачем разработчику это нужно. Существует ли слишком много контроллеров?
Энтони Григгс
6

В один контроллер веб-API можно добавить несколько методов Get и Post. Здесь маршрут по умолчанию вызывает проблему. Веб-API проверяет соответствие маршрута сверху вниз и, следовательно, соответствие вашего маршрута по умолчанию для всех запросов. В соответствии с маршрутом по умолчанию в одном контроллере возможен только один метод Get и Post. Либо поместите следующий код сверху, либо закомментируйте / удалите маршрут по умолчанию

    config.Routes.MapHttpRoute("API Default", 
                               "api/{controller}/{action}/{id}",
                               new { id = RouteParameter.Optional });
Шахид Уллах
источник
1

Поместите префикс маршрута [RoutePrefix ("api / Profiles")] на уровне контроллера и поместите маршрут в метод действия [Route ("LikeProfile")]. Не нужно ничего менять в файле global.asax

namespace KhandalVipra.Controllers
{
    [RoutePrefix("api/Profiles")]
    public class ProfilesController : ApiController
    {
        // POST: api/Profiles/LikeProfile
        [Authorize]
        [HttpPost]
        [Route("LikeProfile")]
        [ResponseType(typeof(List<Like>))]
        public async Task<IHttpActionResult> LikeProfile()
        {
        }
    }
}
Сушил Кумар
источник
0

Думаю, на вопрос уже дан ответ. Я также искал что-то контроллер webApi, который имеет такие же подписанные методы, но разные имена. Я пытался реализовать Калькулятор как WebApi. В калькуляторе есть 4 метода с одинаковой сигнатурой, но разными именами.

public class CalculatorController : ApiController
{
    [HttpGet]
    [ActionName("Add")]
    public string Add(int num1 = 1, int num2 = 1, int timeDelay = 1)
    {
        Thread.Sleep(1000 * timeDelay);
        return string.Format("Add = {0}", num1 + num2);
    }

    [HttpGet]
    [ActionName("Sub")]
    public string Sub(int num1 = 1, int num2 = 1, int timeDelay = 1)
    {
        Thread.Sleep(1000 * timeDelay);
        return string.Format("Subtract result = {0}", num1 - num2);
    }

    [HttpGet]
    [ActionName("Mul")]
    public string Mul(int num1 = 1, int num2 = 1, int timeDelay = 1)
    {
        Thread.Sleep(1000 * timeDelay);
        return string.Format("Multiplication result = {0}", num1 * num2);
    }

    [HttpGet]
    [ActionName("Div")]
    public string Div(int num1 = 1, int num2 = 1, int timeDelay = 1)
    {
        Thread.Sleep(1000 * timeDelay);
        return string.Format("Division result = {0}", num1 / num2);
    }
}

и в файле WebApiConfig у вас уже есть

 config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{action}/{id}",
            defaults: new { id = RouteParameter.Optional });

Просто установите аутентификацию / авторизацию в IIS, и все готово!

Надеюсь это поможет!

Явар Муртаза
источник
0

Вы можете использовать такой подход:

public class VTRoutingController : ApiController
{
    [HttpPost("Route")]
    public MyResult Route(MyRequestTemplate routingRequestTemplate)
    {
        return null;
    }

    [HttpPost("TSPRoute")]
    public MyResult TSPRoute(MyRequestTemplate routingRequestTemplate)
    {
        return null;
    }
}
Амирхоссейн Яри
источник
-1
public class Journal : ApiController
{
    public MyResult Get(journal id)
    {
        return null;
    }
}

public class Journal : ApiController
{

    public MyResult Get(journal id, publication id)
    {
        return null;
    }
}

Я не уверен, нарушает ли перегрузка метода get / post концепцию restfull api, но он работает. Если бы кто-нибудь мог просветить по этому поводу. Что делать, если у меня есть uri как

uri:/api/journal/journalid
uri:/api/journal/journalid/publicationid

так что, как вы могли видеть, мой журнал является своего рода aggregateroot, хотя я могу определить другой контроллер для публикации исключительно и передать идентификатор публикации в моем URL-адресе, однако это дает гораздо больше смысла. поскольку моей публикации не было бы без самого журнала.

mobygeek
источник
-1

Я просто добавил «action = action_name» к URL-адресу, и таким образом механизм маршрутизации знает, какое действие я хочу. Я также добавил к действиям атрибут ActionName, но не уверен, что он нужен.

Рони Теслер
источник
-1

Лучшее и простейшее объяснение, которое я видел по этой теме - http://www.binaryintellect.net/articles/9db02aa1-c193-421e-94d0-926e440ed297.aspx

  • Отредактировано -

Я получил его, работающий только с Route, и мне не нужен RoutePrefix.

Например, в контроллере

[HttpPost]
[Route("[action]")]
public IActionResult PostCustomer
([FromBody]CustomerOrder obj)
{
}

и

[HttpPost]
[Route("[action]")]
public IActionResult PostCustomerAndOrder
([FromBody]CustomerOrder obj)
{
}

Затем имя функции входит в jquery как -

options.url = "/api/customer/PostCustomer";

или

options.url = "/api/customer/PostCustomerAndOrder";
Говард Шлом
источник