¿Cómo se correlaciona un método en MVC WebApi con un verbo http?

En el video de 5 minutos en el siguiente enlace, en la marca de 1:10, Jon Galloway dice que al agregar un método llamado DeleteComment a su clase de controlador CommentsController se asignará automáticamente al verbo http de eliminación.

¿Cómo sabe MVC con WebApi cómo pasar los métodos a los verbos correctos? Sé que el enrutamiento en el archivo global.asax.cs enruta las solicitudes al controlador correcto, pero ¿cómo se “mapea por convención” una solicitud de eliminación al método de eliminación, o cualquier método? ¿Especialmente cuando puede haber más de 1 método para cada verbo? “Por convención” me hace pensar que es solo mirar la primera palabra en un nombre de método … pero si es así, debería leer la firma de los métodos para decir dos métodos de eliminación o dos métodos de obtención aparte … y dónde es todo esto definido?

Video: http://www.asp.net/web-api/videos/getting-started/delete-and-update

¡Gracias!

Editar: Aquí está el código en la clase ValuesController de ejemplo que viene en la plantilla de WebApi. Esta fue la fuente de mi pregunta original. ¿Cómo funciona la “convención” que diferencia entre estos (y cualquier otro método en el controlador)?

// GET /api/values public IEnumerable Get() { return new string[] { "value1", "value2" }; } // GET /api/values/5 public string Get(int id) { return value; } 

Me disculpo de antemano, este post se extravía un poco de lo que me pediste, pero todo esto burbujeó cuando leí tu pregunta.

Semántica coincidente con WebAPI
La semántica coincidente utilizada por (las rutas predeterminadas en) WebAPI es bastante simple.

  1. Coincide con el nombre de la acción con el verbo (verb = GET? Buscar el nombre del método que comienza con “get”)
  2. si se pasa un parámetro, la API busca una acción con un parámetro

Por lo tanto, en el ejemplo del código, una solicitud GET sin un parámetro coincide con la función Get*( ) sin parámetros. A Get containing e ID busca un Get***(int id) .

Ejemplos
Si bien la semántica coincidente es simple, crea cierta confusión para los desarrolladores de MVC (al menos este desarrollador). Veamos algunos ejemplos:

Nombres impares : su método get puede llamarse cualquier cosa, siempre que comience con “get”. Por lo tanto, en el caso de un controlador de widgets, puede GetStrawberry() nombre a sus funciones GetStrawberry() y aún así se emparejará. Piense en la coincidencia como algo así como: methodname.StartsWith("Get")

Múltiples métodos de coincidencia : ¿qué ocurre si tiene dos métodos de obtención sin parámetros? GetStrawberry() y GetOrange() . Lo mejor que puedo decir es que la función definida primero (la parte superior del archivo) en tu código gana … extraño. Esto tiene el efecto secundario de hacer que algunos métodos en su controlador sean inalcanzables (al menos con las rutas predeterminadas) … extraño.

NOTA : el beta se comportó como se indica arriba para ’emparejar múltiples métodos’: la versión de RC & Release es un poco más OCD. Lanza un error si hay múltiples coincidencias potenciales. Este cambio elimina la confusión de múltiples coincidencias ambiguas. Al mismo tiempo, reduce nuestra capacidad para mezclar interfaces de estilo REST y RPC en el mismo controlador, confiando en el orden y las rutas superpuestas.

¿Qué hacer?
Bueno, WebAPI es nuevo y el consenso aún se está fusionando. La comunidad parece estar buscando los principios de REST bastante. Sin embargo, no todas las API pueden o deben ser RESTful, algunas se expresan de forma más natural en un estilo RPC. REST y lo que las personas llaman REST parece ser la fuente de bastante confusión , al menos para Roy Fielding .

Como pragmático, sospecho que muchas API serán 70% RESTful, con algunos métodos de estilo RPC. En primer lugar, la proliferación del controlador solo (dado el método de enlace webapi) va a volver loco a los desarrolladores. En segundo lugar, WebAPI realmente no tiene una forma integrada de crear una estructura anidada de rutas api (es decir: /api/controller/ es fácil, pero /api/CATEGORY/Sub-Category/Controller es factible, pero un problema).

Desde mi punto de vista, me encantaría ver que la estructura de carpetas webAPI controle las rutas API predeterminadas … lo que significa que si creo una carpeta Category en mi proyecto de interfaz de usuario, /api/Category sería la ruta predeterminada (algo paralelo a este artículo de MVC ) .

¿Qué hice?
Por lo tanto, tenía algunos requisitos: (1) poder utilizar una syntax tranquila en la mayoría de los casos, (2) tener una separación de controladores de “espacio de nombres” (pensar en subcarpetas), (3) poder invocar rpc- adicionales como métodos cuando sea necesario. La implementación de estos requisitos se redujo a un enrutamiento inteligente.

 // SEE NOTE AT END ABOUT DataToken change from RC to RTM Route r; r = routes.MapHttpRoute( name : "Category1", routeTemplate : "api/Category1/{controller}/{id}", defaults : new { id = RouteParameter.Optional } ); r.DataTokens["Namespaces"] = new string[] {" UI.Controllers.Category1"}; r = routes.MapHttpRoute( name : "Category2", routeTemplate : "api/Category2/{controller}/{id}", defaults : new { id = RouteParameter.Optional } ); r.DataTokens["Namespaces"] = new string[] {" UI.Controllers.Category2"}; routes.MapHttpRoute( name : "ApiAllowingBL", routeTemplate : "api/{controller}/{action}/{id}", defaults : new { id = RouteParameter.Optional } ); routes.MapHttpRoute( name : "DefaultApi", routeTemplate : "api/{controller}/{id}", defaults : new { id = RouteParameter.Optional } ); 
  • Las dos primeras rutas crean rutas de “subcarpeta”. Necesito crear una ruta para cada subcarpeta, pero me limité a categorías principales, así que solo termino con 3-10 de estos. Observe cómo estas rutas agregan el token de datos del Namespace , para restringir las clases en las que se busca una ruta en particular. Esto corresponde muy bien a la configuración de espacio de nombres típico cuando agrega carpetas a un proyecto de IU.
  • La tercera ruta permite llamar a nombres de métodos específicos (como el mvc tradicional). Dado que la API web elimina el nombre de la acción en la URL, es relativamente fácil saber qué llamadas quieren esta ruta.
  • La última entrada de ruta es la ruta de API web predeterminada. Esto capta cualquier clase, particularmente las que están fuera de mis ‘subcarpetas’.

Dicho de otra manera
Mi solución se redujo a separar los controladores un poco más, así que /api/XXXX no se llenó demasiado.

  • Creo una carpeta en mi proyecto de interfaz de usuario (digamos Category1 ) y coloco controladores de API dentro de la carpeta.
  • Visual Studio naturalmente establece espacios de nombres de clase basados ​​en la carpeta. Entonces Widget1 en la carpeta Category1 obtiene un espacio de nombres predeterminado de UI.Category1.Widget1 .
  • Naturalmente, quería que las URL de las API reflejaran la estructura de la carpeta ( /api/Category1/Widget ). La primera asignación que ve arriba logra que, al codificar /api/Category1 en la ruta, el token de namespace restringe las clases que se buscarán para un controlador que coincida.

NOTA : a partir de la versión DataTokens son nulos por defecto. No estoy seguro de si esto es un error o una característica . Así que escribí un pequeño método de ayuda y lo agregué a mi archivo RouteConfig.cs ….

 r.AddRouteToken("Namespaces", new string[] {"UI.Controllers.Category1"}); private static Route AddRouteToken(this Route r, string key, string[] values) { //change from RC to RTM ...datatokens is null if (r.DataTokens == null) { r.DataTokens = new RouteValueDictionary(); } r.DataTokens[key] = values; return r; } 

NOTA 2 : aunque esta es una publicación de WebAPI 1, como @Jamie_Ide señala en los comentarios, la solución anterior no funciona en WebAPI 2 porque IHttpRoute.DataTokens no tiene setter. Para evitar esto, puede usar un método de extensión simple como este:

 private static IHttpRoute MapHttpRoute(this HttpRouteCollection routes, string name, string routeTemplate, object defaults, object constraints, string[] namespaceTokens) { HttpRouteValueDictionary defaultsDictionary = new HttpRouteValueDictionary(defaults); HttpRouteValueDictionary constraintsDictionary = new HttpRouteValueDictionary(constraints); IDictionary tokens = new Dictionary(); tokens.Add("Namespaces", namespaceTokens); IHttpRoute route = routes.CreateRoute(routeTemplate, defaultsDictionary, constraintsDictionary, dataTokens: tokens, handler:null); routes.Add(name, route); return route; } 

Esto aparece bastante a menudo. Y hay diferentes puntos de vista sobre eso. Personalmente, no me suscribí a ninguna idea en particular por el momento, pero parece que la que tiene un controlador por recurso es la más popular entre la comunidad REST.

Entonces, básicamente, puedes:

  1. Exponga la acción en su ruta (Web API trata la palabra action similar a MVC) pero, en general, no está destinada a ser utilizada.
  2. Definir métodos con diferentes parámetros según esta publicación
  3. Como dije, lo más recomendado es usar un controlador por recurso . Entonces, incluso en el ejemplo de API web, el controlador para la recostackción de una entidad es diferente para el controlador de la entidad misma. Lea esta publicación de Rob Conery y aquí está la respuesta de Glenn.