route - Recursos anidados en ASP.net MVC 4 WebApi
web api return json example (4)
Desde la API web 2, puede usar los atributos de ruta para definir el enrutamiento personalizado por método, lo que permite el enrutamiento jerárquico.
public class CustomersController : ApiController
{
[Route("api/customers/{id:guid}/products")]
public IEnumerable<Product> GetCustomerProducts(Guid id) {
return new Product[0];
}
}
También necesita inicializar la Asignación de atributos en WebApiConfig.Register (),
config.MapHttpAttributeRoutes();
¿Hay alguna manera mejor en el nuevo ASP.net MVC 4 WebApi para manejar recursos anidados que configurar una ruta especial para cada uno? (similar a aquí: ¿ ASP.Net MVC soporta recursos anidados? - esto fue publicado en 2009).
Por ejemplo, quiero manejar:
/customers/1/products/10/
He visto algunos ejemplos de acciones de ApiController
nombres diferentes a Get()
, Post()
, etc., por ejemplo, here veo un ejemplo de una acción llamada GetOrder()
. No puedo encontrar ninguna documentación sobre esto sin embargo. ¿Es esta una manera de lograr esto?
Lo siento, lo he actualizado varias veces ya que estoy buscando una solución.
Parece que hay muchas maneras de abordar esta, pero la más eficiente que he encontrado hasta ahora es:
Agregue esto debajo de la ruta predeterminada:
routes.MapHttpRoute(
name: "OneLevelNested",
routeTemplate: "api/{controller}/{customerId}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Esta ruta coincidirá con cualquier acción del controlador y el nombre del segmento correspondiente en la URL. Por ejemplo:
/ api / customers / 1 / orders coincidirán:
public IEnumerable<Order> Orders(int customerId)
/ api / customers / 1 / orders / 123 coincidirá:
public Order Orders(int customerId, int id)
/ api / customers / 1 / products coincidirá:
public IEnumerable<Product> Products(int customerId)
/ api / customers / 1 / products / 123 coincidirá:
public Product Products(int customerId, int id)
El nombre del método debe coincidir con el segmento de {acción} especificado en la ruta.
Nota IMPORTANTE:
De los comentarios
Dado que el RC deberás decirle a cada acción qué tipo de verbos son aceptables, es decir, [HttpGet]
, etc.
No me gusta usar el concepto de "acciones" en la ruta de una API web ASP.NET. Se supone que la acción en REST es el verbo HTTP. Implementé mi solución de una manera algo genérica y algo elegante simplemente usando el concepto de un controlador principal.
https://.com/a/15341810/326110
A continuación se muestra la respuesta reproducida en su totalidad porque no estoy seguro de qué hacer cuando una publicación responde dos preguntas de SO :(
Quería manejar esto de una manera más general, en lugar de conectar un ChildController directamente con controller = "Child"
, como lo hizo Abhijit Kadam. Tengo varios controladores secundarios y no quería tener que mapear una ruta específica para cada uno, con controller = "ChildX"
y controller = "ChildY"
una y otra vez.
Mi WebApiConfig
ve así:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "ChildApi",
routeTemplate: "api/{parentController}/{parentId}/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Mis controladores principales son muy estándar y coinciden con la ruta predeterminada anterior. Un ejemplo de controlador hijo tiene este aspecto:
public class CommentController : ApiController
{
// GET api/product/5/comment
public string Get(ParentController parentController, string parentId)
{
return "This is the comment controller with parent of "
+ parentId + ", which is a " + parentController.ToString();
}
// GET api/product/5/comment/122
public string Get(ParentController parentController, string parentId,
string id)
{
return "You are looking for comment " + id + " under parent "
+ parentId + ", which is a "
+ parentController.ToString();
}
}
public enum ParentController
{
Product
}
Algunos inconvenientes de mi implementación
- Como puede ver, utilicé una
enum
, por lo que todavía tengo que administrar los controladores principales en dos lugares separados. Podría haber sido fácilmente un parámetro de cadena, pero quería evitar queapi/crazy-non-existent-parent/5/comment/122
funcione. - Probablemente haya una forma de usar la reflexión o algo así para hacer esto sobre la marcha sin administrarlo por separado, pero esto funciona para mí por ahora.
- No es compatible con niños de niños.
Probablemente haya una mejor solución que es aún más general, pero como dije, esto funciona para mí.
EDITAR: Aunque esta respuesta todavía se aplica a la API web 1, para la API web 2, recomiendo utilizar la respuesta de Daniel Halan, ya que es el estado del arte para mapear recursos secundarios (entre otras cosas).
A algunas personas no les gusta usar {acción} en la API web porque creen que al hacerlo romperán la "ideología" REST ... Afirmo eso. {acción} es simplemente una construcción que ayuda a enrutar. Es interno para su implementación y no tiene nada que ver con el verbo HTTP utilizado para acceder a un recurso .
Si pone restricciones de verbos HTTP en las acciones y las nombra en consecuencia, no está rompiendo ninguna de las pautas RESTful y terminará con controladores más simples y concisos en lugar de toneladas de controladores individuales para cada sub-recurso. Recuerde: la acción es solo un mecanismo de enrutamiento y es interno para su implementación. Si lucha contra el marco, entonces algo anda mal con el marco o su implementación. Simplemente trace la ruta con una restricción HTTPMETHOD y estará listo:
routes.MapHttpRoute(
name: "OneLevelNested",
routeTemplate: "api/customers/{customerId}/orders/{orderId}",
constraints: new { httpMethod = new HttpMethodConstraint(new string[] { "GET" }) },
defaults: new { controller = "Customers", action = "GetOrders", orderId = RouteParameter.Optional, }
);
Puede manejar estos en CustomersController de esta manera:
public class CustomersController
{
// ...
public IEnumerable<Order> GetOrders(long customerId)
{
// returns all orders for customerId!
}
public Order GetOrders(long customerId, long orderId)
{
// return the single order identified by orderId for the customerId supplied
}
// ...
}
También puede enrutar una acción Crear en el mismo "recurso" (pedidos):
routes.MapHttpRoute(
name: "OneLevelNested",
routeTemplate: "api/customers/{customerId}/orders",
constraints: new { httpMethod = new HttpMethodConstraint(new string[] { "POST" }) },
defaults: new { controller = "Customers", action = "CreateOrder", }
);
Y maneje en consecuencia en el controlador del cliente:
public class CustomersController
{
// ...
public Order CreateOrder(long customerId)
{
// create and return the order just created (with the new order id)
}
// ...
}
Sí, todavía tiene que crear muchas rutas solo porque la API web todavía no puede enrutar a diferentes métodos según la ruta ... Pero creo que es más limpio definir las rutas de manera declarativa que crear mecanismos de despacho personalizados. basado en enumeraciones u otros trucos.
Para el consumidor de su API se verá perfectamente RESTful:
GET http://your.api/customers/1/orders
(maps to GetOrders (long) devolviendo todos los pedidos para el cliente 1)
GET http://your.api/customers/1/orders/22
(asigna a GetOrders (long, long) devolviendo el pedido 22 para el cliente 1
POST http://your.api/customers/1/orders
(asigna a CreateOrder (long) que creará un pedido y lo devolverá a la persona que llama (con el nuevo ID recién creado)
Pero no tome mi palabra como una verdad absoluta. Todavía estoy experimentando con él y creo que MS no pudo abordar correctamente el acceso a los recursos secundarios.
Te recomiendo que pruebes http://www.servicestack.net/ para una experiencia menos dolorosa al escribir REST apis ... Pero no me malinterpretes, adoro Web API y la utilizo para la mayoría de mis proyectos profesionales, principalmente porque es más fácil encontrar programadores que ya lo "sepan" ... Para mis proyectos personales prefiero ServiceStack.