Método múltiple HttpPost en el controlador de API web

126

Estoy empezando a usar el proyecto MVC4 Web API, tengo un controlador con múltiples HttpPost métodos. El controlador tiene el siguiente aspecto:

Controlador

public class VTRoutingController : ApiController
{
    [HttpPost]
    public MyResult Route(MyRequestTemplate routingRequestTemplate)
    {
        return null;
    }

    [HttpPost]
    public MyResult TSPRoute(MyRequestTemplate routingRequestTemplate)
    {
        return null;
    }
}

Aquí MyRequestTemplaterepresenta la clase de plantilla responsable de manejar el Json que llega a través de la solicitud.

Error:

Cuando hago una solicitud con Fiddler http://localhost:52370/api/VTRouting/TSPRouteo http://localhost:52370/api/VTRouting/Route aparece un error:

Se encontraron varias acciones que coinciden con la solicitud.

Si elimino uno de los métodos anteriores, funciona bien.

Global.asax

Intenté modificar la tabla de enrutamiento predeterminada global.asax, pero todavía recibo el error, creo que tengo problemas para definir rutas en global.asax. Esto es lo que estoy haciendo en global.asax.

public static void RegisterRoutes(RouteCollection routes)
{
    routes.MapHttpRoute(
        name: "MyTSPRoute",
        routeTemplate: "api/VTRouting/TSPRoute",
        defaults: new { }
    );

    routes.MapHttpRoute(
        name: "MyRoute",
        routeTemplate: "api/VTRouting/Route",
        defaults: new { action="Route" }
    );
}

Estoy haciendo la solicitud en Fiddler usando POST, pasando json en RequestBody para MyRequestTemplate.

Habib
fuente

Respuestas:

143

Puede tener múltiples acciones en un solo controlador.

Para eso tienes que hacer las siguientes dos cosas.

  • Primero decorar acciones con ActionNameatributos como

     [ActionName("route")]
     public class VTRoutingController : ApiController
     {
       [ActionName("route")]
       public MyResult PostRoute(MyRequestTemplate routingRequestTemplate)
       {
         return null;
       }
    
      [ActionName("tspRoute")]
      public MyResult PostTSPRoute(MyRequestTemplate routingRequestTemplate)
      {
         return null;
      }
    }
  • En segundo lugar, defina las siguientes rutas en el WebApiConfigarchivo.

    // Controller Only
    // To handle routes like `/api/VTRouting`
    config.Routes.MapHttpRoute(
        name: "ControllerOnly",
        routeTemplate: "api/{controller}"               
    );
    
    
    // Controller with ID
    // To handle routes like `/api/VTRouting/1`
    config.Routes.MapHttpRoute(
        name: "ControllerAndId",
        routeTemplate: "api/{controller}/{id}",
        defaults: null,
        constraints: new { id = @"^\d+$" } // Only integers 
    );
    
    // Controllers with Actions
    // To handle routes like `/api/VTRouting/route`
    config.Routes.MapHttpRoute(
        name: "ControllerAndAction",
        routeTemplate: "api/{controller}/{action}"
    );
Asif Mushtaq
fuente
¿Qué sucede si no quiero establecer ninguna restricción en el tipo de ID? Significado: ¿cómo puedo aceptar ID de cadena también?
frapontillo
55
@frapontillo: El Id debe ser un integeter, de modo que esté diferenciado del nombre de la ruta; de lo contrario, el motor de enrutamiento lo tratará como un nombre de acción en lugar de un id. Si necesita tener la identificación como cadena, puede crear una acción.
Asif Mushtaq
En su lugar, usaría el enrutamiento de atributos. No tendrá que usar múltiples rutas en WebApiConfig de esa manera. Echa un vistazo a este enlace: docs.microsoft.com/en-us/aspnet/web-api/overview/…
Rich
Si agrego así, me da un error ------------ namespace ImageDownloadApplication.Controllers {public class FrontModel {public string skus {get; conjunto; }} [ActionName ("ProductController")] public class ProductController: ApiController {// GET: api / NewCotroller public IEnumerable <string> Get () {return new string [] {"value1", "value2"}; }
Umashankar
41

Una solución mucho mejor para su problema sería usar una Routeque le permita especificar la ruta en el método mediante anotaciones:

[RoutePrefix("api/VTRouting")]
public class VTRoutingController : ApiController
{
    [HttpPost]
    [Route("Route")]
    public MyResult Route(MyRequestTemplate routingRequestTemplate)
    {
        return null;
    }

    [HttpPost]
    [Route("TSPRoute")]
    public MyResult TSPRoute(MyRequestTemplate routingRequestTemplate)
    {
        return null;
    }
}
Wisienkas
fuente
¿En qué espacio de nombres está la ruta? Estoy usando MVC4 y no se reconoce la ruta.
eaglei22
Sí, así es como debe ser. Gracias.
newman
1
por alguna razón no puedo hacer que esto funcione. Esto es exactamente lo que ya estaba haciendo.
oligofren
2
¿Cómo se vería la url que llamar Routey TSPRoute?
Si8
27

utilizar:

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

ya no es un enfoque RESTful, pero ahora puede llamar a sus acciones por su nombre (en lugar de dejar que la API web determine automáticamente una por usted según el verbo) de esta manera:

[POST] /api/VTRouting/TSPRoute

[POST] /api/VTRouting/Route

Contrariamente a la creencia popular, este enfoque no tiene nada de malo y no está abusando de la API web. Todavía puede aprovechar todas las increíbles funciones de la API web (delegar controladores, negociación de contenido, formateadores de medios, etc.): simplemente abandona el enfoque RESTful.

Filip W
fuente
1
Gracias por la respuesta, pero todavía me da el mismo error.
Habib
Eso no es posible, entonces algo más debe estar mal configurado en su aplicación. ¿Puedes mostrar la configuración completa de la ruta? Además, ¿cómo exactamente estás llamando a las acciones de los controladores?
Filip W
Toda la configuración de la ruta está en global.asax, publiqué esa parte en mi pregunta. Para realizar una solicitud, estoy usando Fiddler-> Compose-> y selecciono Publicar como operación
Habib
intente eliminar todas las demás definiciones de rutas y simplemente deje la que publiqué. Entonces puede llamar fácilmente a las dos acciones POST ubicadas dentro de un controlador (igual que el antiguo enfoque MVC)
Filip W
1
Filip, estoy usando .Net framework 4.5, con mvc4 o Visual studio 2012 RC, ¿Qué plantilla estás usando para crear tu proyecto? El tuyo está funcionando perfectamente
Habib
13

Un punto final de API web (controlador) es un recurso único que acepta verbos get / post / put / delete. Es no un controlador normal de MVC.

Necesariamente, /api/VTRoutingsolo puede haber un método HttpPost que acepte los parámetros que está enviando. El nombre de la función no importa , siempre y cuando esté decorando con el material [http]. Sin embargo, nunca lo he intentado.

Editar: esto no funciona. Al resolver, parece ir por el número de parámetros, no tratando de vincular el modelo al tipo.

Puede sobrecargar las funciones para aceptar diferentes parámetros. Estoy bastante seguro de que estaría bien si lo declarara de la manera en que lo hace, pero usa parámetros diferentes (incompatibles) para los métodos. Si los parámetros son los mismos, no tiene suerte, ya que el enlace del modelo no sabrá a qué se refiere.

[HttpPost]
public MyResult Route(MyRequestTemplate routingRequestTemplate) {...}

[HttpPost]
public MyResult TSPRoute(MyOtherTemplate routingRequestTemplate) {...}

Esta parte funciona

La plantilla predeterminada que dan cuando creas una nueva lo hace bastante explícito, y yo diría que deberías seguir con esta convención:

public class ValuesController : ApiController
{
    // GET is overloaded here.  one method takes a param, the other not.
    // GET api/values  
    public IEnumerable<string> Get() { .. return new string[] ... }
    // GET api/values/5
    public string Get(int id) { return "hi there"; }

    // POST api/values (OVERLOADED)
    public void Post(string value) { ... }
    public void Post(string value, string anotherValue) { ... }
    // PUT api/values/5
    public void Put(int id, string value) {}
    // DELETE api/values/5
    public void Delete(int id) {}
}

Si desea crear una clase que haga muchas cosas, para usar ajax, no hay una gran razón para no usar un controlador / patrón de acción estándar. La única diferencia real es que las firmas de tu método no son tan bonitas y debes envolver las cosas Json( returnValue)antes de devolverlas.

Editar:

La sobrecarga funciona bien cuando se usa la plantilla estándar (editada para incluir) cuando se usan tipos simples. También fui y probé a la inversa, con 2 objetos personalizados con diferentes firmas. Nunca pude hacerlo funcionar.

  • La vinculación con objetos complejos no parece "profunda", por lo que no se puede
  • Puede evitar esto pasando un parámetro adicional, en la cadena de consulta
  • Una mejor redacción de la que puedo dar sobre las opciones disponibles

Esto funcionó para mí en este caso, mira a dónde te lleva. Excepción solo para pruebas.

public class NerdyController : ApiController
{
    public void Post(string type, Obj o) { 
        throw new Exception("Type=" + type + ", o.Name=" + o.Name ); 
    }
}

public class Obj {
    public string Name { get; set; }
    public string Age { get; set; }
}

Y llamado así desde la consola:

$.post("/api/Nerdy?type=white", { 'Name':'Slim', 'Age':'21' } )
Andrew Backer
fuente
He intentado cambiar los tipos de parámetros, pero parece que solo permite un único método Post en el controlador. Gracias por su respuesta
Habib
Supuse que intentaría vincular el modelo para encontrarlo, ya que puede sobrecargarlo. Sin embargo, funciona con diferentes números de parámetros. Sin embargo, puede que no sea tan difícil reescribirlo para hacer esto, pero aún no han publicado el código fuente, por lo que estoy atascado mirando el desagradable desmontaje
Andrew Backer
2
+1 por explicar realmente la razón por la que no funciona y la filosofía detrás de la API web.
MEMark
Aprecio el desglose ... Supuse que se suponía que era un solo POST / PUT / GET por controlador, pero no estaba seguro ... por lo tanto, la razón por la que lo busqué. Desde que comencé a desarrollar con MVC para aplicaciones web donde múltiples acciones similares por controlador son la norma ... casi parece un desperdicio, así que puedo entender por qué un desarrollador querría hacerlo. ¿Existe tal cosa como demasiados controladores?
Anthony Griggs el
6

Es posible agregar múltiples métodos Get y Post en el mismo controlador de API web. Aquí la ruta predeterminada está causando el problema. La API web verifica la coincidencia de ruta de arriba a abajo y, por lo tanto, sus rutas de ruta predeterminadas para todas las solicitudes. Según la ruta predeterminada, solo es posible un Método Get and Post en un controlador. Coloque el siguiente código en la parte superior o comente / elimine la ruta predeterminada

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

Coloque el Prefijo de ruta [RoutePrefix ("api / Profiles")] en el nivel del controlador y coloque una ruta en el método de acción [Route ("LikeProfile")]. No es necesario cambiar nada en el archivo global.asax

namespace KhandalVipra.Controllers
{
    [RoutePrefix("api/Profiles")]
    public class ProfilesController : ApiController
    {
        // POST: api/Profiles/LikeProfile
        [Authorize]
        [HttpPost]
        [Route("LikeProfile")]
        [ResponseType(typeof(List<Like>))]
        public async Task<IHttpActionResult> LikeProfile()
        {
        }
    }
}
Sushil Kumar
fuente
0

Creo que la pregunta ya ha sido respondida. También estaba buscando algo un controlador webApi que tenga los mismos métodos firmados pero nombres diferentes. Estaba tratando de implementar la Calculadora como WebApi. La calculadora tiene 4 métodos con la misma firma pero con nombres diferentes.

public class CalculatorController : ApiController
{
    [HttpGet]
    [ActionName("Add")]
    public string Add(int num1 = 1, int num2 = 1, int timeDelay = 1)
    {
        Thread.Sleep(1000 * timeDelay);
        return string.Format("Add = {0}", num1 + num2);
    }

    [HttpGet]
    [ActionName("Sub")]
    public string Sub(int num1 = 1, int num2 = 1, int timeDelay = 1)
    {
        Thread.Sleep(1000 * timeDelay);
        return string.Format("Subtract result = {0}", num1 - num2);
    }

    [HttpGet]
    [ActionName("Mul")]
    public string Mul(int num1 = 1, int num2 = 1, int timeDelay = 1)
    {
        Thread.Sleep(1000 * timeDelay);
        return string.Format("Multiplication result = {0}", num1 * num2);
    }

    [HttpGet]
    [ActionName("Div")]
    public string Div(int num1 = 1, int num2 = 1, int timeDelay = 1)
    {
        Thread.Sleep(1000 * timeDelay);
        return string.Format("Division result = {0}", num1 / num2);
    }
}

y en el archivo WebApiConfig ya tienes

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

¡Simplemente configure la autenticación / autorización en IIS y listo!

¡Espero que esto ayude!

Yawar Murtaza
fuente
0

Puedes usar este enfoque:

public class VTRoutingController : ApiController
{
    [HttpPost("Route")]
    public MyResult Route(MyRequestTemplate routingRequestTemplate)
    {
        return null;
    }

    [HttpPost("TSPRoute")]
    public MyResult TSPRoute(MyRequestTemplate routingRequestTemplate)
    {
        return null;
    }
}
Amirhossein Yari
fuente
-1
public class Journal : ApiController
{
    public MyResult Get(journal id)
    {
        return null;
    }
}

public class Journal : ApiController
{

    public MyResult Get(journal id, publication id)
    {
        return null;
    }
}

No estoy seguro de si la sobrecarga del método get / post viola el concepto de API restfull, pero funciona. Si alguien pudiera haberse iluminado sobre este asunto. ¿Qué pasa si tengo una uri como

uri:/api/journal/journalid
uri:/api/journal/journalid/publicationid

así que, como puede ver, mi diario es una especie de agregado global, aunque puedo definir otro controlador para publicación únicamente y pasar el número de identificación de publicación en mi URL, sin embargo, esto tiene mucho más sentido. ya que mi publicación no existiría sin la propia revista.

mobygeek
fuente
-1

Acabo de agregar "action = action_name" a la url y de esta manera el motor de enrutamiento sabe qué acción quiero. También agregué el atributo ActionName a las acciones, pero no estoy seguro de que sea necesario.

Rony Tesler
fuente
-1

La mejor y más simple explicación que he visto sobre este tema: http://www.binaryintellect.net/articles/9db02aa1-c193-421e-94d0-926e440ed297.aspx

  • Editado

Lo conseguí trabajando solo con Route, y no necesitaba RoutePrefix.

Por ejemplo, en el controlador

[HttpPost]
[Route("[action]")]
public IActionResult PostCustomer
([FromBody]CustomerOrder obj)
{
}

y

[HttpPost]
[Route("[action]")]
public IActionResult PostCustomerAndOrder
([FromBody]CustomerOrder obj)
{
}

Luego, el nombre de la función va en jquery como:

options.url = "/api/customer/PostCustomer";

o

options.url = "/api/customer/PostCustomerAndOrder";
Howard Shlom
fuente