Один контролер з декількома методами GET у веб-API ASP.NET


167

У Web API у мене був клас подібної структури:

public class SomeController : ApiController
{
    [WebGet(UriTemplate = "{itemSource}/Items")]
    public SomeValue GetItems(CustomParam parameter) { ... }

    [WebGet(UriTemplate = "{itemSource}/Items/{parent}")]
    public SomeValue GetChildItems(CustomParam parameter, SomeObject parent) { ... }
}

Оскільки ми могли зіставити окремі методи, отримати правильний запит у потрібному місці було дуже просто. Для аналогічного класу, який мав лише один GETметод, але також мав Objectпараметр, я успішно використовував IActionValueBinder. Однак у вищеописаному випадку я отримую таку помилку:

Multiple actions were found that match the request: 

SomeValue GetItems(CustomParam parameter) on type SomeType

SomeValue GetChildItems(CustomParam parameter, SomeObject parent) on type SomeType

Я намагаюся підійти до цієї проблеми, скасувавши ExecuteAsyncметод, ApiControllerале поки що не пощастило. Якісь поради з цього питання?

Редагувати: я забув зазначити, що зараз я намагаюся перемістити цей код на веб-API ASP.NET, який має інший підхід до маршрутизації. Питання полягає в тому, як змусити код працювати на веб-API ASP.NET?


1
Ви все ще отримали {батьків} як RouteParameter.Options?
Ентоні Скотт

Так. Можливо, я неправильно використовую IActionValueBinder, оскільки для таких типів, як int id (як у демонстраційній версії) він працює добре.
paulius_l

Вибачте, я мав би бути зрозумілішим. Я б подумав, що мати його як необов'язковий означатиме, що він відповідає маршруту пункту, а також маршруту підпунктів, що пояснить повідомлення про помилку, яке ви бачите.
Ентоні Скотт

Зараз ми обговорюємо, якщо підходи нижче (з кількома маршрутами) суперечать правилам REST? На мою думку, це добре. Мій колега вважає, що це не приємно. Будь-які коментарі з цього приводу?
Ремі

Я взагалі був проти цього, коли почав читати про REST. Я все ще не впевнений, що це правильний підхід, але іноді це зручніше чи зручніше у користуванні, тому злегка згинати правила може не так вже й погано. Поки це працює для вирішення конкретної проблеми. Минуло 6 місяців, як я опублікував це питання, і з тих пір ми не шкодували про використання цього підходу.
paulius_l

Відповіді:


249

Це найкращий спосіб, який я знайшов, щоб підтримувати додаткові методи GET та підтримувати звичайні методи REST. Додайте такі маршрути до свого WebApiConfig:

routes.MapHttpRoute("DefaultApiWithId", "Api/{controller}/{id}", new { id = RouteParameter.Optional }, new { id = @"\d+" });
routes.MapHttpRoute("DefaultApiWithAction", "Api/{controller}/{action}");
routes.MapHttpRoute("DefaultApiGet", "Api/{controller}", new { action = "Get" }, new { httpMethod = new HttpMethodConstraint(HttpMethod.Get) });
routes.MapHttpRoute("DefaultApiPost", "Api/{controller}", new {action = "Post"}, new {httpMethod = new HttpMethodConstraint(HttpMethod.Post)});

Я перевірив це рішення за допомогою тестового класу нижче. Я зміг вдало вдарити кожен метод у своєму контролері нижче:

public class TestController : ApiController
{
    public string Get()
    {
        return string.Empty;
    }

    public string Get(int id)
    {
        return string.Empty;
    }

    public string GetAll()
    {
        return string.Empty;
    }

    public void Post([FromBody]string value)
    {
    }

    public void Put(int id, [FromBody]string value)
    {
    }

    public void Delete(int id)
    {
    }
}

Я переконався, що він підтримує такі запити:

GET /Test
GET /Test/1
GET /Test/GetAll
POST /Test
PUT /Test/1
DELETE /Test/1

Зауважте, що якщо ваші додаткові дії GET не починаються з "Get", ви можете додати до методу атрибут HttpGet.


4
Це чудова відповідь, і це мені дуже допомогло в іншому пов'язаному питанні. Дякую!!
Альферо Чингоно

4
Спробував це - не здається, працює. Усі маршрути відображаються випадковим чином до методу GetBlah (long id). :(
BrainSlugs83

1
@ BrainSlugs83: Це залежить від порядку. І ви хочете додати (до методів "withId"), аconstraints: new{id=@"\d+"}
Ерік Фальскен

4
як щодо додавання ще одного методу - Get (int id, ім'я рядка)? ... не вдається
Аніл Першані

1
Мені довелося додати такий додатковий маршрут routes.MapHttpRoute("DefaultApiPut", "Api/{controller}", new {action = "Put"}, new {httpMethod = new HttpMethodConstraint(HttpMethod.Put)});для мого Putметоду, інакше він дав мені 404.
Syed Ali Taqi

57

Перейдіть від цього:

config.Routes.MapHttpRoute("API Default", "api/{controller}/{id}",
            new { id = RouteParameter.Optional });

До цього:

config.Routes.MapHttpRoute("API Default", "api/{controller}/{action}/{id}",
            new { id = RouteParameter.Optional });

Отже, тепер ви можете вказати, до якої дії (способу) потрібно надіслати HTTP-запит.

публікація в "http: // localhost: 8383 / api / Command / PostCreateUser" викликає:

public bool PostCreateUser(CreateUserCommand command)
{
    //* ... *//
    return true;
}

та публікація в "http: // localhost: 8383 / api / Command / PostMakeBooking" викликає:

public bool PostMakeBooking(MakeBookingCommand command)
{
    //* ... *//
    return true;
}

Я спробував це у власному сервісному додатку WEB API, і це працює як шарм :)


8
Дякуємо за корисну відповідь. Я хотів би додати, що якщо ви запускаєте імена методів за допомогою Get, Post тощо, ваші запити будуть відповідати цим методам на основі використовуваного дієслова HTTP. Але ви також можете назвати ваші методи нічого, а потім прикрасити їх з [HttpGet], [HttpPost]і так далі атрибути для відображення дієслова методу.
indot_brad

ласкаво дивіться моє запитання
Moeez

@DikaArtaKarunia немає проблем, радий, що моя відповідь все-таки застосовна через 6 років: D
uggeh

31

Я вважаю, що атрибути є більш чистими у використанні, ніж вручну додавати їх за допомогою коду. Ось простий приклад.

[RoutePrefix("api/example")]
public class ExampleController : ApiController
{
    [HttpGet]
    [Route("get1/{param1}")] //   /api/example/get1/1?param2=4
    public IHttpActionResult Get(int param1, int param2)
    {
        Object example = null;
        return Ok(example);
    }

}

Це вам також потрібно у вашій веб-сторінці

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

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

Деякі хороші посилання http://www.asp.net/web-api/overview/getting-started-with-aspnet-web-api/tutorial-your-first-web-api Це пояснює маршрутизацію краще. http://www.asp.net/web-api/overview/web-api-routing-and-action/routing-in-aspnet-web-api


3
Мені потрібно було також додати config.MapHttpAttributeRoutes();до свого WebApiConfig.csі GlobalConfiguration.Configuration.EnsureInitialized();в кінці свого WebApiApplication.Application_Start()методу отримати атрибути маршруту для роботи.
Ergwun

@Ergwun Цей коментар мені дуже допоміг. Тільки щоб додати його, config.MapHttpAttributeRoutes();потрібно з’явитися перед картографуванням маршруту (наприклад, раніше config.Routes.MappHttpRoute(....
Philip Stratford

11

Потрібно визначити подальші маршрути на global.asax.cs таким чином:

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

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

5
Так, це правда, але було б непогано насправді побачити приклад цих маршрутів. Це зробило б цю відповідь більш цінною для громади. (і ви отримаєте +1 від мене :)
Аран Малхолланд

Приклад ви можете прочитати тут - stackoverflow.com/questions/11407267/…
Том Керхове,

2
Дійсне рішення було б приємніше.
Так багато гоблінів

6

З новим Web Api 2 стало легше мати кілька методів отримання.

Якщо параметр, переданий GETметодам, досить відрізняється, щоб система маршрутизації атрибутів розрізняла їх типи, як це стосується ints і Guids, ви можете вказати очікуваний тип в [Route...]атрибуті

Наприклад -

[RoutePrefix("api/values")]
public class ValuesController : ApiController
{

    // GET api/values/7
    [Route("{id:int}")]
    public string Get(int id)
    {
       return $"You entered an int - {id}";
    }

    // GET api/values/AAC1FB7B-978B-4C39-A90D-271A031BFE5D
    [Route("{id:Guid}")]
    public string Get(Guid id)
    {
       return $"You entered a GUID - {id}";
    }
} 

Більш детально про цей підхід дивіться тут http://nodogmablog.bryanhogan.net/2017/02/web-api-2-controller-with-multiple-get-methods-part-2/

Інший варіант - надати GETметодам різні маршрути.

    [RoutePrefix("api/values")]
    public class ValuesController : ApiController
    {
        public string Get()
        {
            return "simple get";
        }

        [Route("geta")]
        public string GetA()
        {
            return "A";
        }

        [Route("getb")]
        public string GetB()
        {
            return "B";
        }
   }

Детальніше дивіться тут - http://nodogmablog.bryanhogan.net/2016/10/web-api-2-controller-with-multiple-get-methods/


5

У ASP.NET Core 2.0 ви можете додати атрибут Route до контролера:

[Route("api/[controller]/[action]")]
public class SomeController : Controller
{
    public SomeValue GetItems(CustomParam parameter) { ... }

    public SomeValue GetChildItems(CustomParam parameter, SomeObject parent) { ... }
}

4

Я намагався використовувати маршрутизацію атрибутів Web Api 2, щоб дозволити кілька методів Get, і я включив корисні пропозиції з попередніх відповідей, але в Controller я лише прикрасив "спеціальний" метод (приклад):

[Route( "special/{id}" )]
public IHttpActionResult GetSomethingSpecial( string id ) {

... без також розміщення [RoutePrefix] у верхній частині контролера:

[RoutePrefix("api/values")]
public class ValuesController : ApiController

Я отримував помилки, вказуючи, що жодного маршруту не було знайдено відповідно до поданого URI. Після того, як у мене було як [Route] прикрасити метод, так і [RoutePrefix] прикрасити контролер в цілому, воно спрацювало.


3

Я не впевнений, чи знайшов ти відповідь, але я це зробив, і це працює

public IEnumerable<string> Get()
{
    return new string[] { "value1", "value2" };
}

// GET /api/values/5
public string Get(int id)
{
    return "value";
}

// GET /api/values/5
[HttpGet]
public string GetByFamily()
{
    return "Family value";
}

Зараз у global.asx

routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

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

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

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

3

Ви спробували перейти на WebInvokeAttribute та встановити метод "GET"?

Я вважаю, що у мене була подібна проблема, і я перейшов до того, щоб чітко сказати, який метод (GET / PUT / POST / DELETE) очікується для більшості, якщо не всіх моїх методів.

public class SomeController : ApiController
{
    [WebInvoke(UriTemplate = "{itemSource}/Items"), Method="GET"]
    public SomeValue GetItems(CustomParam parameter) { ... }

    [WebInvoke(UriTemplate = "{itemSource}/Items/{parent}", Method = "GET")]
    public SomeValue GetChildItems(CustomParam parameter, SomeObject parent) { ... }
}

WebGet повинен це впоратися, але я бачив, що у нього є деякі проблеми з кількома Отримати набагато менше множинних Отримати той же тип повернення.

[Редагувати: нічого з цього не вірно із заходом WCF WebAPI та міграцією до ASP.Net WebAPI на стеку MVC]


1
Вибачте, я забув зазначити, що я переміщую код у веб-API ASP.NET з моменту припинення роботи веб-API WCF. Я редагував публікацію. Дякую.
paulius_l

2
**Add Route function to direct the routine what you want**
    public class SomeController : ApiController
    {
        [HttpGet()]
        [Route("GetItems")]
        public SomeValue GetItems(CustomParam parameter) { ... }

        [HttpGet()]
        [Route("GetChildItems")]
        public SomeValue GetChildItems(CustomParam parameter, SomeObject parent) { ... }
    }

Ласкаво просимо до переповнення стека! Будь ласка , змініть свій відповідь , щоб включити пояснення вашого коду, а також опис того , як вона відрізняється від чотирнадцяти інших відповідей тут. Це питання майже вісім років , і вже має прийняті та кілька добре пояснених відповідей. Без ваших пояснень це, швидше за все, буде знято або видалено. Наявність цього пояснення допоможе виправдати місце вашої відповіді на це питання.
Das_Geek

1
Особисто (я знаю, що таке рекомендації щодо ПС) на питання, це чітке / базове, я особисто міг би мати чисту відповідь на код . Я не хочу читати багато пояснень. Хочу зробити корисне функціональне програмне забезпечення швидко . +1
MemeDeveloper

2

Лінива / поспішаюча альтернатива (Dotnet Core 2.2):

[HttpGet("method1-{item}")]
public string Method1(var item) { 
return "hello" + item;}

[HttpGet("method2-{item}")]
public string Method2(var item) { 
return "world" + item;}

Виклик їх:

localhost: 5000 / api / ім'я контролера / метод1-42

"привіт42"

localhost: 5000 / api / ім'я контролера / метод2-99

"world99"


0

Жоден із наведених прикладів не працював для моїх особистих потреб. Нижче - це те, що я закінчив робити.

 public class ContainsConstraint : IHttpRouteConstraint
{       
    public string[] array { get; set; }
    public bool match { get; set; }

    /// <summary>
    /// Check if param contains any of values listed in array.
    /// </summary>
    /// <param name="param">The param to test.</param>
    /// <param name="array">The items to compare against.</param>
    /// <param name="match">Whether we are matching or NOT matching.</param>
    public ContainsConstraint(string[] array, bool match)
    {

        this.array = array;
        this.match = match;
    }

    public bool Match(System.Net.Http.HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection)
    {
        if (values == null) // shouldn't ever hit this.                   
            return true;

        if (!values.ContainsKey(parameterName)) // make sure the parameter is there.
            return true;

        if (string.IsNullOrEmpty(values[parameterName].ToString())) // if the param key is empty in this case "action" add the method so it doesn't hit other methods like "GetStatus"
            values[parameterName] = request.Method.ToString();

        bool contains = array.Contains(values[parameterName]); // this is an extension but all we are doing here is check if string array contains value you can create exten like this or use LINQ or whatever u like.

        if (contains == match) // checking if we want it to match or we don't want it to match
            return true;
        return false;             

    }

Щоб використовувати вищезазначене у своєму маршруті, використовуйте:

config.Routes.MapHttpRoute("Default", "{controller}/{action}/{id}", new { action = RouteParameter.Optional, id = RouteParameter.Optional}, new { action = new ContainsConstraint( new string[] { "GET", "PUT", "DELETE", "POST" }, true) });

Те, що відбувається, полягає в обмеженні роду підробок у методі, так що цей маршрут буде відповідати лише методам GET, POST, PUT та DELETE. Там "справжній" говорить, що ми хочемо перевірити відповідність елементів у масиві. Якщо це неправда, ви скажете виключити ті, що в strY, то ви можете використовувати маршрути вище цього методу за замовчуванням, наприклад:

config.Routes.MapHttpRoute("GetStatus", "{controller}/status/{status}", new { action = "GetStatus" });

У вищесказаному він по суті шукає таку URL-адресу => http://www.domain.com/Account/Status/Activeабо щось подібне.

Крім вищесказаного, я не впевнений, що я занадто божевільний. Наприкінці дня це має бути за один ресурс. Але я бачу необхідність відображати дружні URL-адреси з різних причин. Я відчуваю себе досить впевнено, оскільки розвивається Web Api, буде якесь положення. Якщо час, я буду будувати більш постійне рішення та розміщувати повідомлення.


Ви можете використовувати new System.Web.Http.Routing.HttpMethodConstraint(HttpMethod.Get, HttpMethod.Post, HttpMethod.Put, HttpMethod.Delete) замість цього.
абатищев

0

Не вдалося зробити жодне з вищезазначених рішень маршрутизації - деякі синтаксиси, здається, змінилися, і я все ще новачок у MVC - за дрібницю, хоча я зібрав цей справді жахливий (і простий) хак, який отримає мене на даний момент - зауважте, це замінює метод "public MyObject GetMyObjects (long id)" - ми змінюємо тип "id" на рядок і змінюємо тип повернення на об'єкт.

// GET api/MyObjects/5
// GET api/MyObjects/function
public object GetMyObjects(string id)
{
    id = (id ?? "").Trim();

    // Check to see if "id" is equal to a "command" we support
    // and return alternate data.

    if (string.Equals(id, "count", StringComparison.OrdinalIgnoreCase))
    {
        return db.MyObjects.LongCount();
    }

    // We now return you back to your regularly scheduled
    // web service handler (more or less)

    var myObject = db.MyObjects.Find(long.Parse(id));
    if (myObject == null)
    {
        throw new HttpResponseException
        (
            Request.CreateResponse(HttpStatusCode.NotFound)
        );
    }

    return myObject;
}

0

Якщо у вас є кілька дій в одному файлі, тоді передайте один і той же аргумент, наприклад, Id всій дії. Це тому, що дія може ідентифікувати лише Id, тож замість того, щоб давати будь-яке ім'я аргументу, лише оголошуйте Id таким.


[httpget]
[ActionName("firstAction")] firstAction(string Id)
{.....
.....
}
[httpget]
[ActionName("secondAction")] secondAction(Int Id)
{.....
.....
}
//Now go to webroute.config file under App-start folder and add following
routes.MapHttpRoute(
name: "firstAction",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);

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

Як виглядатиме Url для перегляду кожної функції у браузері?
Si8

0

Проста альтернатива

Просто використовуйте рядок запиту.

Маршрутизація

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

Контролер

public class TestController : ApiController
{
    public IEnumerable<SomeViewModel> Get()
    {
    }

    public SomeViewModel GetById(int objectId)
    {
    }
}

Запити

GET /Test
GET /Test?objectId=1

Примітка

Майте на увазі, що парам рядка запиту не повинен бути "id" або будь-яким параметром у налаштованому маршруті.


-1

Змініть WebApiConfig та додайте наприкінці ще один Routes.MapHttpRoute, як це:

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

Потім створіть такий контролер:

public class ServiceController : ApiController
{
        [HttpGet]
        public string Get(int id)
        {
            return "object of id id";
        }
        [HttpGet]
        public IQueryable<DropDownModel> DropDowEmpresa()
        {
            return db.Empresa.Where(x => x.Activo == true).Select(y =>
                  new DropDownModel
                  {
                      Id = y.Id,
                      Value = y.Nombre,
                  });
        }

        [HttpGet]
        public IQueryable<DropDownModel> DropDowTipoContacto()
        {
            return db.TipoContacto.Select(y =>
                  new DropDownModel
                  {
                      Id = y.Id,
                      Value = y.Nombre,
                  });
        }

        [HttpGet]
        public string FindProductsByName()
        {
            return "FindProductsByName";
        }
}

Ось як я це вирішив. Сподіваюся, це комусь допоможе.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.