Roteamento de API da Web - api / {controlador} / {ação} / {id} “disfunções” api / {controlador} / {id}

94

Eu tenho a rota padrão em Global.asax:

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

Eu queria ser capaz de direcionar uma função específica, então criei outra rota:

RouteTable.Routes.MapHttpRoute(
         name: "WithActionApi",
         routeTemplate: "api/{controller}/{action}/{id}",
         defaults: new { id = System.Web.Http.RouteParameter.Optional }
         );

Então, no meu controlador, eu tenho:

    public string Get(int id)
    {
        return "object of id id";
    }        

    [HttpGet]
    public IEnumerable<string> ByCategoryId(int id)
    {
        return new string[] { "byCategory1", "byCategory2" };
    }

Ligar .../api/records/bycategoryid/5vai me dar o que eu quero. No entanto, ligar .../api/records/1me dará o erro

Foram encontradas várias ações que correspondem à solicitação: ...

Eu entendo o porquê - as rotas apenas definem quais URLs são válidos, mas quando se trata de correspondência de função, tanto Get(int id)e ByCategoryId(int id)correspondência api/{controller}/{id}, que é o que confunde a estrutura.

O que preciso fazer para que a rota padrão da API funcione novamente e manter aquela com {action}? Pensei em criar um controlador diferente nomeado RecordByCategoryIdControllerpara corresponder à rota de API padrão, para o qual eu solicitaria .../api/recordbycategoryid/5. No entanto, considero que é uma solução "suja" (portanto insatisfatória). Procurei respostas sobre isso e nenhum tutorial por aí sobre como usar uma rota com {action}sequer menciona esse problema.

Mickael Caruso
fonte

Respostas:

104

O mecanismo de rota usa a mesma sequência em que você adiciona regras a ele. Depois de obter a primeira regra correspondente, ele irá parar de verificar outras regras e fará isso para pesquisar o controlador e a ação.

Então você deveria:

  1. Coloque suas regras específicas à frente de suas regras gerais (como padrão), o que significa usar RouteTable.Routes.MapHttpRoutepara mapear "WithActionApi" primeiro, depois "DefaultApi".

  2. Remova o defaults: new { id = System.Web.Http.RouteParameter.Optional }parâmetro da regra "WithActionApi" porque, uma vez que o id é opcional, o url como "/ api / {part1} / {part2}" nunca vai para "DefaultApi".

  3. Adicione uma ação nomeada ao seu "DefaultApi" para informar ao mecanismo de rota qual ação inserir. Caso contrário, quando você tiver mais de uma ação em seu controlador, o mecanismo não saberá qual usar e exibirá "Foram encontradas várias ações que correspondem à solicitação: ...". Em seguida, para fazer com que corresponda ao seu método Get, use um ActionNameAttribute .

Portanto, sua rota deve ser assim:

// Map this rule first
RouteTable.Routes.MapRoute(
     "WithActionApi",
     "api/{controller}/{action}/{id}"
 );

RouteTable.Routes.MapRoute(
    "DefaultApi",
    "api/{controller}/{id}",
    new { action="DefaultAction", id = System.Web.Http.RouteParameter.Optional }
);

E seu controlador:

[ActionName("DefaultAction")] //Map Action and you can name your method with any text
public string Get(int id)
{
    return "object of id id";
}        

[HttpGet]
public IEnumerable<string> ByCategoryId(int id)
{
    return new string[] { "byCategory1", "byCategory2" };
}
Chris Li
fonte
1
Tentei o conselho acima e tudo funciona conforme o esperado. Muito obrigado por esse "segredo".
Mickael Caruso
No ponto 2 da sua resposta, se idfor opcional, o URL like /api/{part1}/{part2}ainda pode entrar na DefaultApirota se nenhuma ação correspondente for encontrada para a WithActionApirota. Por favor me corrija se eu estiver errado.
orad
o que acontece se eu quiser apenas api / {controlador} / {ação}. sem id. Se outra pessoa está enfrentando o mesmo problema. esqueça o WebApiConfig. leia sobre o Roteamento de Atributos.
ahsant
40

Você pode resolver seu problema com a ajuda de roteamento de atributo

Controlador

[Route("api/category/{categoryId}")]
public IEnumerable<Order> GetCategoryId(int categoryId) { ... }

URI em jquery

api/category/1

Configuração de rota

using System.Web.Http;

namespace WebApplication
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API routes
            config.MapHttpAttributeRoutes();

            // Other Web API configuration not shown.
        }
    }
}

e seu roteamento padrão está funcionando como roteamento baseado em convenção padrão

Controlador

public string Get(int id)
    {
        return "object of id id";
    }   

URI em Jquery

/api/records/1 

Configuração de rota

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Attribute routing.
        config.MapHttpAttributeRoutes();

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

Revise o artigo para obter mais informações Roteamento de atributo e roteamento baseado em convenção aqui e isto

MSTdev
fonte
Deus responda. mas não podemos adicionar para todos, api / {controlador} / {ação} / {id} junto com api / {controlador} / {id}?
karim
O roteamento de atributos resolve definitivamente o problema. Um ponto importante: antes da API Web 2, os modelos de projeto da API Web geravam código como este: protected void Application_Start () {WebApiConfig.Register (GlobalConfiguration.Configuration); } Se o roteamento de atributo estiver habilitado, este código lançará uma exceção. Se você atualizar um projeto de API da Web existente para usar o roteamento de atributo, certifique-se de atualizar este código de configuração para o seguinte: protected void Application_Start () {GlobalConfiguration.Configure (WebApiConfig.Register); }
Deeb
0

Experimente isso.

public class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services

        var json = config.Formatters.JsonFormatter;
        json.SupportedMediaTypes.Add(new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
        config.Formatters.Remove(config.Formatters.XmlFormatter);

        // Web API routes
        config.MapHttpAttributeRoutes();

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

        );
    }
}
Amit
fonte
0

A possível razão também pode ser que você não herdou o Controller de ApiController. Aconteceu comigo demorou um pouco para entender o mesmo.

Paritosh Tiwari
fonte
0

Para diferenciar as rotas, tente adicionar uma restrição de que o id deve ser numérico:

RouteTable.Routes.MapHttpRoute(
         name: "DefaultApi",
         routeTemplate: "api/{controller}/{id}",
         constraints: new { id = @"\d+" }, // Only matches if "id" is one or more digits.
         defaults: new { id = System.Web.Http.RouteParameter.Optional }
         );  
Derek Wade
fonte