Несколько действий для одного и того же HttpVerb

У меня есть контроллер веб-API со следующими действиями:

    [HttpPut]
    public string Put(int id, JObject data)

    [HttpPut, ActionName("Lock")]
    public bool Lock(int id)

    [HttpPut, ActionName("Unlock")]
    public bool Unlock(int id)

И нанесены следующие маршруты:

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

Когда я делаю следующие запросы, все работает так, как ожидалось:

PUT /api/items/Lock/5
PUT /api/items/Unlock/5

Но когда я пытаюсь сделать запрос на:

PUT /api/items/5

Я получаю следующее исключение:

Multiple actions were found that match the request:
    Put(int id, JObject data)
    Lock(int id)
    Unlock(int id)

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

[HttpPut, ActionName("")]
public string Put(int id, JObject data)

Любые идеи, как я могу объединить маршрутизацию RESTful по умолчанию с именами настраиваемых действий?

EDIT: Механизм маршрутизации не путается с выбором контроллера. Его смущает выбор action на одном контроллере. Что мне нужно, так это сопоставить действие по умолчанию, когда действие не указано. Надеюсь, что это проясняет ситуацию.


person Alfero Chingono    schedule 29.10.2012    source источник
comment
Похоже, это может ответить на ваш вопрос: stackoverflow.com/questions/9499794/   -  person Giscard Biamby    schedule 30.10.2012
comment
@GiscardBiamby Спасибо за ссылку, я просмотрел ее, чтобы попробовать.   -  person Alfero Chingono    schedule 30.10.2012
comment
@GiscardBiamby Вы определенно указали мне правильное направление. Этот ответ: stackoverflow.com/a/12185249/99373 помог мне понять, что мне нужно было сделать, и это сработало! Если вам нужен кредит, опубликуйте ответ, и я приму его.   -  person Alfero Chingono    schedule 30.10.2012


Ответы (3)


Это ожидаемая ошибка от селектора действий по умолчанию, которым является ApiControllerActionSelector. В основном у вас есть три метода действия, которые соответствуют глаголу HTTP Put. Также имейте в виду, что селектор действий по умолчанию рассматривает простые типы параметров действий, которые являются примитивными типами .NET, хорошо известными простыми типами (System.String, System.DateTime, System.Decimal, System.Guid, System.DateTimeOffset, System.TimeSpan) и базовыми простыми типами (например: Nullable<System.Int32>).

В качестве решения вашей проблемы я бы создал два контроллера для таких, как показано ниже:

public class FooController : ApiController { 

    public string Put(int id, JObject data)
}

public class FooRPCController : ApiController { 

    [HttpPut]
    public bool Lock(int id)

    [HttpPut]
    public bool Unlock(int id)
}

маршруты будут выглядеть следующим образом:

routes.MapHttpRoute(
    name: "ApiAction",
    routeTemplate: "api/Foo/{action}/{id}",
    defaults: new { controller = "FooRPC" }
);

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

С другой стороны (не совсем относящейся к вашей теме), у меня есть три сообщения в блоге о выборе действий, особенно с параметрами сложного типа. Я рекомендую вам ознакомиться с ними, поскольку они могут дать вам еще несколько перспектив:

person tugberk    schedule 29.10.2012
comment
Из любопытства, это случай, когда это невозможно сделать таким образом? Я действительно предпочел бы хранить все в одном контроллере, потому что это упрощает обслуживание моего приложения. В приложении есть много других контроллеров, которые необходимо изменить для поддержки этой предлагаемой реализации. - person Alfero Chingono; 30.10.2012
comment
@adaptive Смотрите мой обновленный ответ. Если вам удастся сохранить селектор действий счастливым, убедитесь, что вы можете обрабатывать их в одном контроллере. - person tugberk; 30.10.2012
comment
Спасибо за содержательный ответ. Так что, может быть, я могу написать собственный ActionSelector для решения проблемы? - person Alfero Chingono; 30.10.2012
comment
@adaptive снова увидеть обновление. Я добавил несколько ссылок на посты в блоге. Они также могут помочь. ИМХО, вы должны делать любой RPC, но это только мое мнение. Написание селектора действий с нуля — это боль (я был там), а селектор действий по умолчанию не так уж расширяем. - person tugberk; 30.10.2012
comment
Спасибо за отличную информацию. Пройдет некоторое время, прежде чем я смогу отчитаться. Очень признателен!! - person Alfero Chingono; 30.10.2012
comment
@adaptive о, выдумка! Это должно было быть ИМХО, вы не должны выполнять RPC, но это только мое мнение. - person tugberk; 30.10.2012

С помощью Giscard Biamby я нашел этот ответ, который указал мне правильное направление. В конце концов, чтобы решить эту конкретную проблему, я сделал это следующим образом:

routes.MapHttpRoute(
    name: "ApiPut", 
    routeTemplate: "api/{controller}/{id}",
    defaults: new { action = "Put" }, 
    constraints: new { httpMethod = new HttpMethodConstraint("Put") }
);

Спасибо @GiscardBiamby

person Alfero Chingono    schedule 29.11.2012

Во-первых, удалите [HttpPut, ActionName("")], а затем измените свой маршрут на этот

config.Routes.MapHttpRoute(
    name: "Api",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional },
    constraints: new { id = @"^[0-9]+$" }
    );  
person Yasser Shaikh    schedule 29.10.2012
comment
К сожалению, я не могу этого сделать, потому что у меня есть другие контроллеры со строковыми/гуид-идентификаторами. Числовое ограничение нарушит их. - person Alfero Chingono; 29.10.2012
comment
Вы можете определить несколько маршрутов. Просто измените приведенный выше код, чтобы он нацеливался на ваш конкретный контроллер, т.е. routesTemplate: "api/items/{id}", defaults: new { controller = "items"... - person Ben Foster; 29.10.2012
comment
Пожалуйста, смотрите мои правки выше. Механизм маршрутизации находит контроллер без проблем. Проблема в том, что он считает, что все три действия соответствуют запросу: PUT /api/items/5. Что мне нужно, так это сопоставить действие по умолчанию, когда действие не указано. Надеюсь, что это проясняет ситуацию. - person Alfero Chingono; 29.10.2012