2012-09-10 30 views
5

tengo un controlador Groups con las siguientes acciones:métodos PUT múltiples en el API Web ASP.NET

public GroupModel Get(int ID) 

public GroupModel Post(CreateGroupModel model) 

public void Put(PublicUpdateGroupModel model) 

public void PutAddContacts(UpdateContactsModel model) 

public void PutRemoveContacts(UpdateContactsModel model) 

public void Delete(int ID) 

Y lo que me gustaría hacer es utilizar REST estándar de enrutamiento para llamar a la norma GET, POST, ponen , borra los mehods Pero llamar a la PutAddContacts y PutRemoveContacts si los nombres de acción se añade a la URL, por ejemplo:

grupos GET/- llamadas de método GET

grupos de POST/- insta método Post

grupos PUT/- llamadas método de poner

grupos borrar/- llamadas borrar método

grupos PUT/addcontacts - llama al método PutAddContacts

grupos PUT/removecontacts - llama al método PutRemoveContacts

¿Es posible configurar el enrutamiento de hacer esto o tengo que ir por el camino de RPC para el encaminamiento si quiero usar nombres de acción en mi URL?

Respuesta

11

lo que tienes ahora
Para utilizar sus métodos que arriba tendrá que ir RPC. Eso es porque su ejemplo ya está a medio camino impregnado del estilo RPC de hacer las cosas. Las rutas WebAPI predeterminadas alientan las configuraciones RESTful, pero si realiza una modificación menor a sus rutas, todo comenzaría a funcionar. Por ejemplo, podría cambiar su ruta por defecto a algo así como una ruta MVC típica:

routes.MapRoute(name : "Default",  
       url  : "{controller}/{action}/{id}", 
       defaults: new { controller = "Home", 
           action  = "Index", 
           id   = UrlParameter.Optional }); 

Después de la adición de la ruta, llamar a las cosas en la manera típica de MVC en los que utilice el nombre del controlador & acción. Desde su pregunta, sin embargo, sospecho que en realidad quiere ser REST, en lugar de conseguir que funcione así que sigue leyendo ...

Ser REST
RESTO doesn't require HTTP, aunque los dos son a menudo discutidas en conjunto. REST realmente se trata de que cada recurso tenga una representación semánticamente precisa. Cuando se usa HTTP, significa URI únicos que respetan la semántica de HTTP. Por ejemplo, una llamada que use HTTP GET nunca debe modificar los datos porque eso viola la definición de HTTP de GET y la infraestructura HTTP confusa como los cachés.

de POST/PUT vs MERGE/PATCH
Todos estamos familiarizados con GET, POST, PUT, CABEZA, etc .. como métodos HTTP. Generalmente, GET es para recuperar, POST es para agregar, y PUT es para modificar (aunque sujeto a mucho debate). Sin embargo, tiene dos tipos de modificaciones: agregar elementos y eliminar elementos de una colección. Entonces, ¿esos son PUT u otra cosa? La comunidad hasn't quite settled sobre cómo hacer esto.

  • Opción 1: Custom tipo de medio - La especificación HTTP realmente permite que todos los métodos de clases, que son los navegadores que realmente nos restringen al subconjunto familiar. De modo que puede crear métodos MERGE (a Roy Fielding work around) or PATCH (an oData work around) y definir el comportamiento para este nuevo tipo de medio, tal vez uno para agregar y otro para eliminar.

  • Opción 2: Use POST/PUT - Use PUT para agregar y eliminar contactos. Simplemente trate la lista de identificaciones como un alternar (si existe eliminar, si falta agregar) o alternativamente incluya suficiente información para saber qué hacer. A continuación, devuelva un HTTP 303 que indica al cliente que tiene un estado obsoleto y actualice.

  • Opción 3: Lista completa - Si su conjunto tiene un tamaño razonable, siempre puede pasar una lista completa de contactos cada vez que desee actualizar. De esta manera, la lógica es una limpieza súper simple y reemplazar.

Lo que realmente importa desde una perspectiva RESTful es que su aplicación se comporte de manera consistente en todos los métodos. Entonces, si MERGE significa agregar, siempre debería significar agregar. Si espera que un conjunto completo de ID pase a PUT, siempre pase un conjunto completo.

diseño del controlador
Si fuera yo, me romperé el controlador en varios controladores. Un controlador trata con Grupos, otro trata Contactos (como grupo) y un tercero trata con un contacto dentro de un grupo. Algo así como ...

//api/Group/ 
public List<GroupModel> Get() 
public GroupModel Get(int ID) 
public GroupModel Post(GroupModel model) //add a group 
public GroupModel Put(GroupModel model) //update a group (see comments above) 
public void Delete(int ID) 


//api/GroupContacts/ 
public ContactsModel Get()     //gets complete list 
public void PostContacts(ContactsModel model) //pushes a COMPLETE new state 
public void Delete()       //delete entire group of contacts 


//api/GroupContact/354/ 
public ContactModel Get(int id)    //get contact id #354 
public void PostContact(ContactModel model) //add contact (overwrite if exits) 
public void Delete(int id)     //delete contact if exists 

Si desea que aparezca anidado de su URL (por ejemplo: /api/Group/Contacts, /api/Group/Contact), se puede ver en this other post I wrote. En mi humilde opinión, el enrutamiento de asp.net necesita un ajuste para facilitar el anidamiento un poco más fácil ... pero ese es un problema diferente ;-)

+0

¡Gracias por la increíble respuesta! Estoy de acuerdo con su afirmación de que REST es más acerca de la semántica, y es por eso que creo que mi ejemplo sigue siendo REST. Y curiosamente, en realidad encontré tu otra publicación antes e implementé algo similar usando subcarpetas y teniendo una ruta para cada subcarpeta, iba a responder esta publicación yo mismo si nadie respondía marcando mi solución, pero supongo No necesito molestar ahora: D – jcvandan

+0

Me alegro de estar de servicio. Acabo de estar trabajando en un gran proyecto API que comenzó durante la versión beta de webapi. Me tomó un tiempo entender el sutil cambio de diseño entre MVC, donde podría tener un montón de métodos en un controlador y el estilo de API donde tengo proliferación de controladores. – EBarr

+0

@EBarr Me gustaría recibir sus comentarios sobre el enrutamiento jerárquico que demostré en mi respuesta a esta pregunta. –

3

Para repetir lo que dice EBarr, hacer enrutamiento jerárquico en API web puede ser un poco doloroso . Lo uso mucho en mis servicios, así que construí un servicio de enrutamiento de reemplazo para la API web. El Nuget es here y la fuente está en GitHub

Este enfoque de enrutamiento requiere que construya su espacio de nombre URI como una jerarquía de segmentos de ruta. En lugar de emparejar los patrones URI completos con los controladores, se conectan los controladores a puntos arbitrarios en el árbol de segmentos de ruta.

Sólo para dar una idea de lo que se vería así que creé una muestra pequeña de auto-host con URI similar a lo que está tratando de hacer:

internal class Program 
{ 
    private static void Main(string[] args) 
    { 
    var baseAddress = new Uri("http://oak:8700/"); 

    var configuration = new HttpSelfHostConfiguration(baseAddress); 
    var router = new ApiRouter("api", baseAddress); 

    // /api/Contacts 
    router.Add("Contacts", rcs => rcs.To<ContactsController>()); 

    // /api/Contact/{contactid} 
    router.Add("Contact", rc => 
          rc.Add("{contactid}", rci => rci.To<ContactController>())); 

    // /api/Group/{groupid} 
    // /api/Group/{groupid}/Contacts 
    router.Add("Group", rg => 
         rg.Add("{groupid}", rgi => rgi.To<GroupController>() 
                 .Add("Contacts", rgc => rgc.To<GroupContactsController>()))); 


    configuration.MessageHandlers.Add(router); 

    var host = new HttpSelfHostServer(configuration); 
    host.OpenAsync().Wait(); 

    Console.WriteLine("Host open. Hit enter to exit..."); 

    Console.Read(); 

    host.CloseAsync().Wait(); 
    } 
} 

public class GroupController : TestController { } 
public class ContactsController : TestController { } 
public class ContactController : TestController { } 
public class GroupContactsController : TestController { } 


public class TestController : ApiController 
{ 
    public HttpResponseMessage Get() 
    { 
     var pathRouteData = (PathRouteData) Request.GetRouteData(); 

     var paramvalues = new StringBuilder(); 

     foreach (KeyValuePair<string, object> keyValuePair in pathRouteData.Values) 
     { 
      paramvalues.Append(keyValuePair.Key); 
      paramvalues.Append(" = "); 
      paramvalues.Append(keyValuePair.Value); 
      paramvalues.Append(Environment.NewLine); 
     } 

     var url = pathRouteData.RootRouter.GetUrlForController(this.GetType()); 

     return new HttpResponseMessage() 
        { 
         Content = new StringContent("Response from " + this.GetType().Name + Environment.NewLine 
                + "Url: " + url.AbsoluteUri 
                + "Parameters: " + Environment.NewLine 
                + paramvalues.ToString()) 
        }; 
    } 
} 

Usted debe ser capaz de simplemente pega este codifique en una aplicación de consola y agregue una referencia a Microsoft.AspNet.WebApi.SelfHost y Tavis.WebApiRouter nukes y pruébelo. Si tiene curiosidad sobre qué tan lejos puede llegar con este tipo de enrutamiento, hay una muestra más compleja here.

+0

interesante. En este momento tengo un plazo muy ajustado, pero vuelvo a las respuestas largas que escribo regularmente.Estaba planeando escribir mi propio controlador de ruta. Todo lo que realmente quiero es uno que respete mis espacios de nombres (por lo que las clases en 'UI.Parent.Child.GrandChild' se correlacionarían con'/api/Parent/Child/Grandchild'. Así que tal vez me salvaste el tiempo :-). Daré la vuelta y evaluaré, pero puede llevarme algunas semanas. – EBarr

+0

@Ebarr Debería ser bastante sencillo escribir algún código que refleje contra el espacio de nombres y busque ApiControllers y construya automáticamente una jerarquía. Solo parámetros que necesitarían algún tipo de convención. También tengo mis espacios de nombres que coinciden con mis URI así que esto sería útil para mí. –