Enrutamiento de API web: api / {controller} / {action} / {id} api de "disfunciones" / {controller} / {id}

94

Tengo la ruta predeterminada en Global.asax:

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

Quería poder apuntar a una función específica, así que creé otra ruta:

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

Entonces, en mi controlador, tengo:

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

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

Llamar .../api/records/bycategoryid/5me dará lo que quiero. Sin embargo, llamar .../api/records/1me dará el error.

Se encontraron varias acciones que coinciden con la solicitud: ...

Entiendo por qué es así: las rutas solo definen qué URL son válidas, pero cuando se trata de coincidencia de funciones, ambas Get(int id)y ByCategoryId(int id)coincidencia api/{controller}/{id}, que es lo que confunde el marco.

¿Qué debo hacer para que la ruta API predeterminada vuelva a funcionar y conservar la que tiene {action}? Pensé en crear un controlador diferente llamado RecordByCategoryIdControllerpara que coincida con la ruta API predeterminada, por lo que solicitaría .../api/recordbycategoryid/5. Sin embargo, encuentro que es una solución "sucia" (por lo tanto, insatisfactoria). He buscado respuestas sobre esto y no hay ningún tutorial sobre el uso de una ruta que {action}incluso mencione este problema.

Mickael Caruso
fuente

Respuestas:

104

El motor de ruta usa la misma secuencia a la que agrega reglas. Una vez que obtenga la primera regla coincidente, dejará de verificar otras reglas y tomará esto para buscar controlador y acción.

Entonces, deberías:

  1. Ponga sus reglas específicas por delante de sus reglas generales (como las predeterminadas), lo que significa usar RouteTable.Routes.MapHttpRoutepara asignar "WithActionApi" primero, luego "DefaultApi".

  2. Elimina el defaults: new { id = System.Web.Http.RouteParameter.Optional }parámetro de tu regla "WithActionApi" porque una vez que la identificación es opcional, una URL como "/ api / {part1} / {part2}" nunca entrará en "DefaultApi".

  3. Agregue una acción con nombre a su "DefaultApi" para indicarle al motor de ruta qué acción debe ingresar. De lo contrario, una vez que tenga más de una acción en su controlador, el motor no sabrá cuál usar y arrojará "Se encontraron varias acciones que coinciden con la solicitud: ...". Luego, para que coincida con su método Get, use un ActionNameAttribute .

Entonces tu ruta debería tener este aspecto:

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

Y tu 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
fuente
1
Probé los consejos anteriores y todo funciona como se esperaba. Muchas gracias por ese "secreto".
Mickael Caruso
En el punto 2 de su respuesta, si ides opcional, entonces la URL como /api/{part1}/{part2}aún puede entrar en la DefaultApiruta si no se encuentra una acción coincidente para la WithActionApiruta. Por favor corrígeme si estoy equivocado.
orad
qué sucede si quiero tener api / {controller} / {action} solamente. sin id. Si alguien más se enfrenta al mismo problema. olvídese de WebApiConfig. lea sobre el enrutamiento de atributos.
ahsant
40

Puede resolver su problema con la ayuda del enrutamiento de atributos

Controlador

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

URI en jquery

api/category/1

Configuración de ruta

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

y su enrutamiento predeterminado funciona como enrutamiento predeterminado basado en convenciones

Controlador

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

URI en Jquery

/api/records/1 

Configuración de ruta

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

Consulte el artículo para obtener más información. Enrutamiento de atributos y enrutamiento basado en convenciones aquí y esto

MSTdev
fuente
Dios responde. pero ¿no podemos agregar para todos, api / {controller} / {action} / {id} junto con api / {controller} / {id}?
karim
El enrutamiento de atributos definitivamente resuelve el problema. Un punto importante: antes de Web API 2, las plantillas de proyecto de Web API generaban código como este: protected void Application_Start () {WebApiConfig.Register (GlobalConfiguration.Configuration); } Si el enrutamiento de atributos está habilitado, este código generará una excepción. Si actualiza un proyecto de API web existente para usar el enrutamiento de atributos, asegúrese de actualizar este código de configuración a lo siguiente: protected void Application_Start () {GlobalConfiguration.Configure (WebApiConfig.Register); }
Deeb
0

Prueba esto.

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
fuente
0

La posible razón también puede ser que no haya heredado Controller de ApiController. Me pasó un tiempo para entender lo mismo.

Paritosh Tiwari
fuente
0

Para diferenciar las rutas, intente agregar una restricción de que id debe 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
fuente