Имена пользовательских методов в веб-API ASP.NET

110

Я перехожу с веб-API WCF на новый веб-API ASP.NET MVC 4. У меня есть UsersController, и я хочу иметь метод с именем Authenticate. Я вижу примеры того, как выполнять GetAll, GetOne, Post и Delete, но что, если я хочу добавить дополнительные методы в эти службы? Например, моя служба UsersService должна иметь метод под названием Authenticate, в который они передают имя пользователя и пароль, однако он не работает.

public class UsersController : BaseApiController
{
    public string GetAll()
    {
        return "getall!";
    }

    public string Get(int id)
    {
        return "get 1! " + id;
    }

    public User GetAuthenticate(string userName, string password, string applicationName)
    {
        LogWriter.Write(String.Format("Received authenticate request for username {0} and password {1} and application {2}",
            userName, password, applicationName));

        //check if valid leapfrog login.
        var decodedUsername = userName.Replace("%40", "@");
        var encodedPassword = password.Length > 0 ? Utility.HashString(password) : String.Empty;
        var leapFrogUsers = LeapFrogUserData.FindAll(decodedUsername, encodedPassword);

        if (leapFrogUsers.Count > 0)
        {
            return new User
            {
                Id = (uint)leapFrogUsers[0].Id,
                Guid = leapFrogUsers[0].Guid
            };
        }
        else
            throw new HttpResponseException("Invalid login credentials");
    }
}

Я могу перейти к myapi / api / users /, и он вызовет GetAll, а я могу перейти к myapi / api / users / 1, и он вызовет Get, однако, если я вызову myapi / api / users / authenticate? Username = {0} & password = {1}, тогда он вызовет Get (НЕ аутентифицирует) и выдаст ошибку:

Словарь параметров содержит пустую запись для параметра id типа System.Int32, не допускающего значения NULL, для метода System.String Get (Int32) в Navtrak.Services.WCF.NavtrakAPI.Controllers.UsersController. Необязательный параметр должен быть ссылочным типом, типом, допускающим значение NULL, или быть объявленным как необязательный параметр.

Как я могу вызвать пользовательские имена методов, такие как Authenticate?

Джастин
источник
Пожалуйста, обратитесь по этой ссылке: 5-й ответ stackoverflow.com/questions/12775590/…
Вишва Г.

Ответы:

137

По умолчанию конфигурация маршрута следует соглашениям RESTFul, что означает, что он будет принимать только имена действий Get, Post, Put и Delete (посмотрите на маршрут в global.asax => по умолчанию он не позволяет вам указать любое имя действия => он использует HTTP-глагол для отправки). Итак, когда вы отправляете запрос GET, /api/users/authenticateвы в основном вызываете Get(int id)действие и передаете, id=authenticateчто, очевидно, приводит к сбою, потому что ваше действие Get ожидает целое число.

Если вы хотите, чтобы имена действий отличались от стандартных, вы можете изменить определение маршрута в global.asax:

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

Теперь вы можете перейти к /api/values/getauthenticateдля аутентификации пользователя.

Дарин Димитров
источник
20
Есть ли способ заставить его по-прежнему использовать Get (id), Get () Put, Delete, Post, при этом разрешая другие действия?
Shawn Mclean
@ShawnMclean Я предполагаю, что вы могли бы указать другой маршрут без {action}ограничения, {id}чтобы ничего, кроме intили Guid(или чего-то еще), не совпадало. Тогда он сможет пройти до предложенного Дарином
Стив Грейтрекс
2
Еще одна важная вещь здесь заключается в том, что при таком стиле маршрутизации вы должны использовать атрибуты для указания разрешенных методов HTTP (например, [HttpGet]).
Гималаи Гарг
1
вы уверены, что вам нужно использовать другие действия? Вы действительно пытались вписать то, что вы делаете, в соглашения REST? Нет необходимости использовать другие действия.
niico
1
@niico: представьте, что вы хотите иметь метод Count (), который возвращает количество элементов, возвращаемых Get (). Я не понимаю, как это уместить в Get (), Get (id), Post (...), Put (...) или Delete (id). И, конечно же, я могу вообразить гораздо больше возможных методов.
Jens Mander
88

Это лучший метод, который я придумал до сих пор, чтобы включить дополнительные методы 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.

Sky-Dev
источник
1
хорошее решение, не могли бы вы сказать мне , если я настроить putи deleteглаголы , такие , как вы делали на getи postбудет работать тоже хорошо?
Фелипе Ориани
1
На мой взгляд, это должно быть включено в значения по умолчанию для проектов WebAPI (возможно, закомментировано). Он дает вам маршруты в стиле WebAPI и MVC одновременно ...
Джон Калвинер,
1
@FelipeOriani, я не думаю, что вам нужно или нужно настраивать putили deleteглаголы, поскольку эти запросы обычно сопровождают параметр id для идентификации ресурса, к которому вы хотите применить эту операцию. deleteВызов /api/fooдолжен выдавать ошибку , потому который Foo вы пытаетесь удалить? Поэтому маршрут DefaultApiWithId должен нормально обрабатывать эти случаи.
nwayve
4
у меня это совсем не сработало. появлялись сообщения об ошибках, когда я пытался выполнить базовый GET.
Мэтт
Для первого, DefaultApiWithId, не должны ли значения по умолчанию быть нулевыми вместо нового {id = RouteParameter.Optional}? Разве не требуется "идентификатор"?
Джонни Ошика
22

Я уже несколько дней в мире MVC4.

Как бы то ни было, у меня есть SitesAPIController, и мне нужен был специальный метод, который можно было бы назвать так:

http://localhost:9000/api/SitesAPI/Disposition/0

С разными значениями последнего параметра для получения записи с разными диспозициями.

Что в итоге сработало для меня:

Метод в SitesAPIController:

// GET api/SitesAPI/Disposition/1
[ActionName("Disposition")]
[HttpGet]
public Site Disposition(int disposition)
{
    Site site = db.Sites.Where(s => s.Disposition == disposition).First();
    return site;
}

И это в файле WebApiConfig.cs

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

// this i added
config.Routes.MapHttpRoute(
    name: "Action",
    routeTemplate: "api/{controller}/{action}/{disposition}"
 );

Пока я называл {disposition} как {id}, я встречал:

{
"Message": "No HTTP resource was found that matches the request URI 'http://localhost:9000/api/SitesAPI/Disposition/0'.",
"MessageDetail": "No action was found on the controller 'SitesAPI' that matches the request."
}

Когда я переименовал его в {disposition}, он заработал. Таким образом, очевидно, что имя параметра совпадает со значением в заполнителе.

Не стесняйтесь редактировать этот ответ, чтобы сделать его более точным / пояснительным.

Кинджал Диксит
источник
Спасибо за чаевые. Я совершал ту же ошибку, что и ты.
abhi
16

Web Api по умолчанию ожидает URL в форме api / {controller} / {id}, чтобы переопределить эту маршрутизацию по умолчанию. вы можете настроить маршрутизацию одним из двух способов.

Первый вариант:

Добавьте ниже регистрацию маршрута в WebApiConfig.cs

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

Украсьте свой метод действия HttpGet и параметрами, как показано ниже.

[HttpGet]
public HttpResponseMessage ReadMyData(string param1,
                        string param2, string param3)

 {

// your code here

}

для вызова вышеуказанного метода URL-адрес будет таким, как показано ниже

http: // localhost: [ваш порт] / api / MyData / ReadMyData? param1 = value1 & param2 = value2 & param3 = value3

Второй вариант Добавьте префикс маршрута к классу контроллера и украсьте свой метод действия с помощью HttpGet, как показано ниже. В этом случае не нужно изменять WebApiConfig.cs. Может иметь маршрутизацию по умолчанию.

[RoutePrefix("api/{controller}/{action}")]
public class MyDataController : ApiController
{

[HttpGet]
public HttpResponseMessage ReadMyData(string param1,
                        string param2, string param3)

{

// your code here

}

}

для вызова вышеуказанного метода URL-адрес будет таким, как показано ниже

http: // localhost: [ваш порт] / api / MyData / ReadMyData? param1 = value1 & param2 = value2 & param3 = value3

Нагараджу Менгани
источник
Второй вариант мне очень нравится. Не могли бы вы также показать мне, как использовать его в VB.net? Большое спасибо.
user1617676
12

Если вы используете ASP.NET 5 с ASP.NET MVC 6 , большинство из этих ответов просто не будут работать, потому что вы обычно позволяете MVC создавать для вас соответствующую коллекцию маршрутов (с использованием соглашений RESTful по умолчанию), что означает, что вы не найдете Routes.MapRoute()призывов к редактированию по желанию.

ConfigureServices()Метод вызывается Startup.csфайл зарегистрирует MVC с рамками внедрения зависимостей , встроенной в ASP.NET 5: Таким образом, когда вы звоните ApplicationBuilder.UseMvc()позже в этом классе, структура MVC автоматически добавит эти маршруты по умолчанию для вашего приложения. Мы можем взглянуть на то, что происходит за кулисами, посмотрев на UseMvc()реализацию метода в исходном коде фреймворка:

public static IApplicationBuilder UseMvc(
    [NotNull] this IApplicationBuilder app,
    [NotNull] Action<IRouteBuilder> configureRoutes)
{
    // Verify if AddMvc was done before calling UseMvc
    // We use the MvcMarkerService to make sure if all the services were added.
    MvcServicesHelper.ThrowIfMvcNotRegistered(app.ApplicationServices);

    var routes = new RouteBuilder
    {
        DefaultHandler = new MvcRouteHandler(),
        ServiceProvider = app.ApplicationServices
    };

    configureRoutes(routes);

    // Adding the attribute route comes after running the user-code because
    // we want to respect any changes to the DefaultHandler.
    routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(
        routes.DefaultHandler,
        app.ApplicationServices));

    return app.UseRouter(routes.Build());
}

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

Плохо то, что документации о том, как можно добавлять собственные маршруты, мало или совсем нет. К счастью, вы можете легко сделать это, используя подход на основе соглашений и / или на основе атрибутов (также известный как маршрутизация атрибутов ).

На основе конвенций

В своем классе Startup.cs замените это:

app.UseMvc();

с этим:

app.UseMvc(routes =>
            {
                // Route Sample A
                routes.MapRoute(
                    name: "RouteSampleA",
                    template: "MyOwnGet",
                    defaults: new { controller = "Items", action = "Get" }
                );
                // Route Sample B
                routes.MapRoute(
                    name: "RouteSampleB",
                    template: "MyOwnPost",
                    defaults: new { controller = "Items", action = "Post" }
                );
            });

На основе атрибутов

В MVC6 замечательно то, что вы также можете определять маршруты для каждого контроллера, украсив Controllerкласс и / или Actionметоды соответствующими параметрами RouteAttributeи / или HttpGet/ HttpPostшаблоном, например следующими:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc;

namespace MyNamespace.Controllers
{
    [Route("api/[controller]")]
    public class ItemsController : Controller
    {
        // GET: api/items
        [HttpGet()]
        public IEnumerable<string> Get()
        {
            return GetLatestItems();
        }

        // GET: api/items/5
        [HttpGet("{num}")]
        public IEnumerable<string> Get(int num)
        {
            return GetLatestItems(5);
        }       

        // GET: api/items/GetLatestItems
        [HttpGet("GetLatestItems")]
        public IEnumerable<string> GetLatestItems()
        {
            return GetLatestItems(5);
        }

        // GET api/items/GetLatestItems/5
        [HttpGet("GetLatestItems/{num}")]
        public IEnumerable<string> GetLatestItems(int num)
        {
            return new string[] { "test", "test2" };
        }

        // POST: /api/items/PostSomething
        [HttpPost("PostSomething")]
        public IActionResult Post([FromBody]string someData)
        {
            return Content("OK, got it!");
        }
    }
}

Этот контроллер будет обрабатывать следующие запросы:

 [GET] api/items
 [GET] api/items/5
 [GET] api/items/GetLatestItems
 [GET] api/items/GetLatestItems/5
 [POST] api/items/PostSomething

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

Для получения дополнительной информации вы также можете прочитать следующий пост в моем блоге.

Darkseal
источник
1
Это потрясающе! Ни один из других ответов на самом деле не сделал то, что мне нужно. Но вы меня спасли :)
Король Артур Третий
Есть ли способ использовать предопределенную модель в качестве второго параметра? Например, когда я заплаты определенного пользователя , как это: public IActionResult Patch(int id, [FromQuery] Person person), все входящие свойства равно нуль!
Король Артур Третий,
0

Просто измените свой WebAPIConfig.cs, как показано ниже

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

Затем реализуйте свой API, как показано ниже

    // GET: api/Controller_Name/Show/1
    [ActionName("Show")]
    [HttpGet]
    public EventPlanner Id(int id){}
Динуван Калубовила
источник
0

Web APi 2 и более поздние версии поддерживают новый тип маршрутизации, называемый маршрутизацией по атрибутам. Как следует из названия, атрибутная маршрутизация использует атрибуты для определения маршрутов. Маршрутизация атрибутов дает вам больше контроля над URI в вашем веб-API. Например, вы можете легко создавать URI, описывающие иерархии ресурсов.

Например:

[Route("customers/{customerId}/orders")]
public IEnumerable<Order> GetOrdersByCustomer(int customerId) { ... }

Будет идеально, и вам не понадобится дополнительный код, например, в WebApiConfig.cs. Просто убедитесь, что маршрутизация веб-API включена или не включена в WebApiConfig.cs, если нет, вы можете активировать, как показано ниже:

        // Web API routes
        config.MapHttpAttributeRoutes();

Вам не нужно ничего делать или что-то менять в WebApiConfig.cs. Более подробно вы можете прочитать в этой статье .

nzrytmn
источник