2012-05-03 5 views
29

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

¿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?

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

Gracias!

Edit: 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<string> Get() 
    { 
     return new string[] { "value1", "value2" }; 
    } 

    // GET /api/values/5 
    public string Get(int id) 
    { 
     return value; 
    } 
+3

En WebAPI I _think_ que se supone que tienen un controlador de recursos por lo que no tendría dos métodos de borrado. –

+1

Gracias Shane, esa es una buena información, pero lo que quiero decir es específicamente como un método de registro único y un método de registro múltiple. GET es probablemente un ejemplo más apropiado, pero es posible que tenga un método que obtiene un registro por un ID, y otro que obtiene todos los registros con un cierto FK. Pero ambos usan el verbo GET; No entiendo dónde existe la inteligencia de enrutamiento que discierne entre estos. –

Respuesta

58

Pido disculpas de antemano, esta publicación se aparta un poco de lo que me preguntaste, pero todo esto se hizo borroso cuando leí tu pregunta.

WebAPI a juego Semántica
El juego utilizado semántica por (los predeterminados rutas en) WebAPI es bastante simple.

  1. coincida con el nombre de la acción con el verbo (verbo = LLEGAR? Buscarse un nombre de método a partir de "GET")
  2. si se pasa un parámetro, la API busca una acción con un parámetro

Por lo tanto, en su muestra de código una solicitud GET sin un parámetro coincide con la función Get*() sin parámetros. A Obtener contenido e ID busca un Get***(int id).

Ejemplos
Mientras que la semántica juego es simple, se crea una cierta confusión para los desarrolladores MVC (bueno, al menos este desarrollador). Veamos algunos ejemplos:

Nombres impares - Tu método get puede llamarse cualquier cosa, siempre que comience con "get". Por lo tanto, en el caso de un controlador de widgets, puede asignarle un nombre a sus funciones GetStrawberry() y seguirá coincidiendo. Piense en el juego como algo parecido a: methodname.StartsWith("Get")

métodos de emparejamiento múltiples - ¿Qué pasa si usted tiene dos consigue métodos 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: la beta comportado como anteriormente para 'emparejan múltiples métodos' - La versión de lanzamiento RC & 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 las interfaces de estilo REST y RPC en el mismo controlador, dependiendo de la orden de las rutas superpuestas &.

¿Qué hacer?
Bueno, WebAPI es nuevo y el consenso sigue vinculándose. 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 & lo que las personas llaman REST parece ser la fuente de quite a bitof confusion, wellat least toRoy 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 en realidad no tiene una forma integrada de crear una estructura anidada de rutas api (lo que significa: /api/controller/ es fácil, pero /api/CATEGORY/Sub-Category/Controller es factible, pero duele).

Desde mi perspectiva, me gustaría ver el control de la estructura de carpetas WebAPI los caminos API por defecto ... es decir, si se crea una carpeta de categoría en mi proyecto de interfaz de usuario a continuación, /api/Category sería la ruta por defecto (algo parallel to this MVC article).

¿Qué hice?
Entonces, tenía algunos requisitos: (1) poder utilizar una sintaxis tranquila en la mayoría de los casos, (2) tener una separación de "espacio de nombres" de controladores (pensar en subcarpetas), (3) poder llamar métodos adicionales similares a rpc 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 primeras dos rutas crean rutas "sub-carpeta". 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 Namespace, para restringir qué clases se buscan para 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 descendió a bajar a los controladores que separan un poco más de lo que /api/XXXX no esté demasiado concurrido.

  • creo una carpeta en mi proyecto de interfaz de usuario (digamos Category1), y poner los controladores de la API dentro de la carpeta.
  • Visual Studio naturalmente establece espacios de nombres de clase basados ​​en la carpeta. Por lo tanto, Widget1 en la carpeta Category1 obtiene un espacio de nombre predeterminado de UI.Category1.Widget1.
  • Naturalmente, quería que las URL de las API reflejaran la estructura de las carpetas (/api/Category1/Widget). La primera asignación que ve arriba logra que, mediante la codificación dura de /api/Category1 en la ruta, el token namespace restringe las clases en las que se buscará un controlador que coincida.

NOTA: a partir de la liberación DataTokens son nulos por defecto. No soy seguro si this is a bug, or a feature. Así que escribí un poco de método de ayuda y se añade 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: incluso pensamos que este es un post WebAPI 1, como se señala en el @Jamie_Ide comentarios la solución anterior no funciona en WebAPI 2 porque IHttpRoute.DataTokens no tiene setter. Para evitar esto se puede utilizar un método de extensión simple como esto:

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<string, object> tokens     = new Dictionary<string, object>(); 
           tokens.Add("Namespaces", namespaceTokens); 

    IHttpRoute route = routes.CreateRoute(routeTemplate, defaultsDictionary, constraintsDictionary, dataTokens: tokens, handler:null); 
    routes.Add(name, route); 

    return route; 
} 
+1

Gracias EBarr, eso es muy útil. Su solución al final está un poco sobre mi cabeza, pero la primera parte definitivamente respondió mi pregunta, y creo que la última parte será extremadamente útil para volver a visitarla después de haber aprendido un poco más.Sospeché que había algún tipo de comparación entre el nombre de un método en su clase de controlador y los verbos http, pero hasta que alguien lo confirmara y me dijera cómo funcionaba, realmente no creía en mi propia sospecha. ¡Gracias! –

+0

@AndrewBSchultz - me alegro de ayudar. ..posicionando una explicación de solución un poco mejor. – EBarr

+0

Gracias @EBarr! Muy útil. –

3

Esto aparece con bastante frecuencia. 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.

Así que básicamente se puede:

  1. Exponer a la acción en su ruta (API Web trata a la palabra action similar a MVC), pero esto generalmente no está destinado a ser utilizado.
  2. definir métodos con diferentes parámetros como por esta post
  3. Como dije, más recomendada está utilizando un controlador por recurso. Entonces, incluso en el ejemplo de API web, el controlador para la recopilación de una entidad es diferente para el controlador de la entidad misma. Lea esto post por Rob Conery y here es la respuesta de Glenn.
+0

Gracias buenos artículos y respuesta de Aliostad, pero un seguimiento: Estoy preguntando específicamente sobre esto (esto viene en el código de ejemplo en la clase ValuesController en un proyecto de plantilla WebApi). No creo que esto sea de lo que Glenn está hablando, porque esto es todo en un controlador: Olvidé que estos comentarios no tienen un buen formato. Agregué el código que quería pegar aquí en mi publicación original. –

Cuestiones relacionadas