Nomes de métodos personalizados na API Web ASP.NET


110

Estou convertendo da API Web do WCF para a nova API Web ASP.NET MVC 4. Eu tenho um UsersController e quero um método chamado Authenticate. Vejo exemplos de como fazer GetAll, GetOne, Post e Delete, mas e se eu quiser adicionar métodos extras a esses serviços? Por exemplo, meu UsersService deve ter um método chamado Authenticate onde eles passam um nome de usuário e senha, mas não funciona.

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");
    }
}

Posso navegar para myapi / api / users / e ele chamará GetAll e posso navegar para myapi / api / users / 1 e chamará Get, no entanto, se eu chamar myapi / api / users / authenticate? Username = {0} & senha = {1} então chamará Get (NOT Authenticate) e erro:

O dicionário de parâmetros contém uma entrada nula para o parâmetro 'id' do tipo não anulável 'System.Int32' para o método 'System.String Get (Int32)' em 'Navtrak.Services.WCF.NavtrakAPI.Controllers.UsersController'. Um parâmetro opcional deve ser um tipo de referência, um tipo anulável ou ser declarado como um parâmetro opcional.

Como posso chamar nomes de métodos personalizados, como Authenticate?


Consulte este link: 5ª resposta stackoverflow.com/questions/12775590/…
Vishwa G

Respostas:


137

Por padrão, a configuração da rota segue as convenções RESTFul, o que significa que ela aceitará apenas os nomes de ação Get, Post, Put e Delete (veja a rota em global.asax => por padrão, ela não permite que você especifique nenhum nome de ação => usa o verbo HTTP para despachar). Então, quando você envia uma solicitação GET, /api/users/authenticatebasicamente chama a Get(int id)ação e passa, o id=authenticateque obviamente trava porque sua ação Get espera um número inteiro.

Se quiser ter nomes de ação diferentes dos padrões, você pode modificar sua definição de rota em global.asax:

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

Agora você pode navegar /api/values/getauthenticatepara autenticar o usuário.


20
Existe uma maneira de fazer com que ele ainda use Get (id), Get () Put, Delete, Post enquanto permite outras ações?
Shawn Mclean

@ShawnMclean Eu acho que você poderia especificar outra rota sem {action}que tenha uma restrição em {id}para que qualquer coisa diferente de intou Guid(ou qualquer outra) não coincidisse. Em seguida, deve ser capaz de cair para o sugerido por Darin
Steve Greatrex

2
Uma coisa mais importante aqui é que, com este estilo de roteamento, você deve usar atributos para especificar os métodos HTTP permitidos (como [HttpGet]).
Himalaya Garg

1
tem certeza de que precisa usar outras ações? Você realmente tentou encaixar o que está fazendo nas convenções REST? Não deve ser necessário usar outras ações.
niico

1
@niico: Imagine que você quisesse um método Count (), que retornasse o número de elementos que Get () retornaria. Não vejo como encaixar isso em Get (), Get (id), Post (...), Put (...) ou Delete (id). E, claro, existem métodos indefinidamente mais possíveis que eu poderia imaginar.
Jens Mander de

88

Este é o melhor método que desenvolvi até agora para incorporar métodos GET extras ao mesmo tempo que oferece suporte aos métodos REST normais. Adicione as seguintes rotas ao seu 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)});

Eu verifiquei essa solução com a classe de teste abaixo. Consegui acertar com sucesso cada método em meu controlador abaixo:

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)
    {
    }
}

Verifiquei que ele suporta as seguintes solicitações:

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

Observe que se suas ações GET extras não começarem com 'Get', você pode adicionar um atributo HttpGet ao método.


1
boa solução, você poderia me dizer se eu configurar os verbos pute deletecomo você fez em gete post, vai funcionar bem também?
Felipe Oriani

1
Na minha opinião, isso deve ser incluído nos padrões para projetos WebAPI (talvez comentado). Dá-lhe rotas de estilo WebAPI E MVC ao mesmo tempo ...
John Culviner

1
@FelipeOriani, não acho que você queira ou precise configurar os verbos putou deletejá que essas solicitações normalmente acompanhariam um parâmetro id para identificar o recurso ao qual deseja aplicar essa operação. Uma deletechamada para /api/foodeve gerar um erro, pois qual foo você está tentando excluir? Portanto, a rota DefaultApiWithId deve lidar bem com esses casos.
nwayve

4
isso não funcionou para mim de forma alguma. recebi mensagens de erro quando tentei fazer um GET básico.
Matt de

Para o primeiro, DefaultApiWithId, os padrões não deveriam ser nulos em vez de new {id = RouteParameter.Optional}? O 'id' não é obrigatório?
Johnny Oshika

22

Estou dias no mundo MVC4.

Pelo que vale a pena, eu tenho um SitesAPIController e precisava de um método personalizado, que poderia ser chamado assim:

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

Com valores diferentes para o último parâmetro obter registro com disposições diferentes.

O que finalmente funcionou para mim foi:

O método no 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;
}

E isso no 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}"
 );

Por enquanto eu estava nomeando a {disposition} como {id} eu estava encontrando:

{
"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."
}

Quando mudei o nome para {disposition}, ele começou a funcionar. Portanto, aparentemente, o nome do parâmetro corresponde ao valor no marcador.

Sinta-se à vontade para editar esta resposta para torná-la mais precisa / explicativa.


Obrigado pela dica. Eu estava cometendo o mesmo erro que você.
abhi

16

A API da Web por padrão espera URL na forma de api / {controller} / {id}, para substituir este roteamento padrão. você pode definir o roteamento com qualquer uma das duas maneiras abaixo.

Primeira opção:

Adicione o registro de rota abaixo em WebApiConfig.cs

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

Decore seu método de ação com HttpGet e parâmetros como abaixo

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

 {

// your code here

}

para chamar o método acima, o url será como abaixo

http: // localhost: [yourport] / api / MyData / ReadMyData? param1 = valor1 & param2 = valor2 & param3 = valor3

Segunda opção Adicionar prefixo de rota à classe Controller e decorar seu método de ação com HttpGet como abaixo. Nesse caso, não é necessário alterar nenhum WebApiConfig.cs. Ele pode ter roteamento padrão.

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

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

{

// your code here

}

}

para chamar o método acima, o url será como abaixo

http: // localhost: [yourport] / api / MyData / ReadMyData? param1 = valor1 & param2 = valor2 & param3 = valor3


Eu gosto muito da segunda opção. Você também poderia me mostrar como usá-lo no VB.net? Muito obrigado.
user1617676

12

Caso você esteja usando ASP.NET 5 com ASP.NET MVC 6 , a maioria dessas respostas simplesmente não funcionará porque você normalmente deixará o MVC criar a coleção de rotas apropriada para você (usando as convenções RESTful padrão), o que significa que você não encontrará nenhuma Routes.MapRoute()chamada para editar à vontade.

O ConfigureServices()método invocado pelo Startup.csarquivo registrará MVC com o framework Dependency Injection embutido na ASP.NET 5: dessa forma, quando você chamar ApplicationBuilder.UseMvc()posteriormente nessa classe, o framework MVC adicionará automaticamente essas rotas padrão ao seu aplicativo. Podemos dar uma olhada no que acontece nos bastidores, observando a UseMvc()implementação do método dentro do código-fonte do framework:

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());
}

O bom disso é que o framework agora lida com todo o trabalho pesado, iterando por meio de todas as ações do controlador e configurando suas rotas padrão, poupando assim a você algum trabalho redundante.

O ruim é que há pouca ou nenhuma documentação sobre como você pode adicionar suas próprias rotas. Felizmente, você pode fazer isso facilmente usando uma abordagem baseada em convenção e / ou baseada em atributos (também conhecida como roteamento de atributos ).

Baseado em convenção

Em sua classe Startup.cs, substitua este:

app.UseMvc();

com isso:

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" }
                );
            });

Baseado em Atributos

Uma grande coisa sobre MVC6 é que você também pode definir rotas em uma base por controlador, decorando a Controllerclasse e / ou os Actionmétodos com os parâmetros apropriados RouteAttributee / ou HttpGet/ HttpPostmodelo, como o seguinte:

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!");
        }
    }
}

Este controlador irá lidar com as seguintes solicitações:

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

Observe também que se você usar as duas abordagens juntas, as rotas baseadas em atributos (quando definidas) substituirão as baseadas em convenção, e ambas substituirão as rotas padrão definidas por UseMvc().

Para mais informações, você também pode ler a seguinte postagem no meu blog.


1
Isto é perfeito! Nenhuma das outras respostas realmente fez o que eu precisava. Mas você me salvou :)
Rei Arthur III

Existe uma maneira de usar um modelo predefinido como um segundo parâmetro? Por exemplo, quando estou remendar um determinado usuário assim: public IActionResult Patch(int id, [FromQuery] Person person), todas as propriedades recebidas são nulos!
Rei Arthur III,


0

Apenas modifique seu WebAPIConfig.cs conforme abaixo

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

Em seguida, implemente sua API conforme abaixo

    // GET: api/Controller_Name/Show/1
    [ActionName("Show")]
    [HttpGet]
    public EventPlanner Id(int id){}

0

Web APi 2 e versões posteriores suportam um novo tipo de roteamento, chamado roteamento de atributo. Como o nome indica, o roteamento de atributo usa atributos para definir rotas. O roteamento de atributos oferece mais controle sobre os URIs em sua API da web. Por exemplo, você pode criar facilmente URIs que descrevem hierarquias de recursos.

Por exemplo:

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

Será perfeito e você não precisa de nenhum código extra, por exemplo, em WebApiConfig.cs. Você só precisa ter certeza de que o roteamento da API da web está habilitado ou não no WebApiConfig.cs, se não você pode ativar como abaixo:

        // Web API routes
        config.MapHttpAttributeRoutes();

Você não precisa fazer nada mais ou alterar algo em WebApiConfig.cs. Para mais detalhes você pode dar uma olhada neste artigo .

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.