Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
de Rick Anderson
Descargar el proyecto completado
Agregar un controlador de productos
El controlador de administración es para los usuarios que tienen privilegios de administrador. Por otro lado, los clientes pueden ver los productos, pero no pueden crearlos, actualizarlos ni eliminarlos.
Podemos restringir fácilmente el acceso a los métodos Post, Put y Delete, mientras se deja abiertos los métodos Get. Pero examine los datos que se devuelven para un producto:
{"Id":1,"Name":"Tomato Soup","Price":1.39,"ActualCost":0.99}
¡La ActualCost propiedad no debe ser visible para los clientes! La solución consiste en definir un objeto de transferencia de datos (DTO) que incluya un subconjunto de propiedades que deben ser visibles para los clientes. Usaremos LINQ para proyectar instancias de Product a instancias de ProductDTO.
Agregue una clase denominada ProductDTO a la carpeta Models.
namespace ProductStore.Models
{
public class ProductDTO
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
}
Ahora agregue el controlador. En el Explorador de soluciones, haga clic con el botón derecho en la carpeta Controladores. Seleccione Agregar y, a continuación, seleccione Controlador. En el cuadro de diálogo Agregar controlador , asigne al controlador el nombre "ProductsController". En Plantilla, seleccione Controlador de API vacío.
Reemplace todo en el archivo de código fuente por el código siguiente:
namespace ProductStore.Controllers
{
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using ProductStore.Models;
public class ProductsController : ApiController
{
private OrdersContext db = new OrdersContext();
// Project products to product DTOs.
private IQueryable<ProductDTO> MapProducts()
{
return from p in db.Products select new ProductDTO()
{ Id = p.Id, Name = p.Name, Price = p.Price };
}
public IEnumerable<ProductDTO> GetProducts()
{
return MapProducts().AsEnumerable();
}
public ProductDTO GetProduct(int id)
{
var product = (from p in MapProducts()
where p.Id == 1
select p).FirstOrDefault();
if (product == null)
{
throw new HttpResponseException(
Request.CreateResponse(HttpStatusCode.NotFound));
}
return product;
}
protected override void Dispose(bool disposing)
{
db.Dispose();
base.Dispose(disposing);
}
}
}
El controlador sigue usando OrdersContext para consultar la base de datos. Pero en lugar de devolver Product instancias directamente, llamamos MapProducts para proyectarlas en instancias ProductDTO.
return from p in db.Products select new ProductDTO()
{ Id = p.Id, Name = p.Name, Price = p.Price };
El MapProducts método devuelve un IQueryable, por lo que podemos componer el resultado con otros parámetros de consulta. Puede ver esto en el GetProduct método , que agrega una cláusula where a la consulta:
var product = (from p in MapProducts()
where p.Id == 1
select p).FirstOrDefault();
Agregar un controlador de pedidos
A continuación, agregue un controlador que permita a los usuarios crear y ver pedidos.
Comenzaremos con otro DTO. En el Explorador de soluciones, haga clic con el botón derecho en la carpeta Modelos y agregue una clase denominada OrderDTO Use la siguiente implementación:
namespace ProductStore.Models
{
using System.Collections.Generic;
public class OrderDTO
{
public class Detail
{
public int ProductID { get; set; }
public string Product { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
}
public IEnumerable<Detail> Details { get; set; }
}
}
Ahora agregue el controlador. En el Explorador de soluciones, haga clic con el botón derecho en la carpeta Controladores. Seleccione Agregar y, a continuación, seleccione Controlador. En el cuadro de diálogo Agregar controlador , establezca las siguientes opciones:
- En Nombre del controlador, escriba "OrdersController".
- En Plantilla, seleccione "Controlador de API con acciones de lectura y escritura mediante Entity Framework".
- En Clase modelo, seleccione "Order (ProductStore.Models)".
- En Clase de contexto de datos, seleccione "OrdersContext (ProductStore.Models)".
Haga clic en Agregar. Esto agrega un archivo denominado OrdersController.cs. A continuación, es necesario modificar la implementación predeterminada del controlador.
En primer lugar, elimine los PutOrder métodos y DeleteOrder . Para este ejemplo, los clientes no pueden modificar ni eliminar pedidos existentes. En una aplicación real, necesitaría una gran cantidad de lógica de back-end para controlar estos casos. (Por ejemplo, ¿el pedido ya se envió?)
Cambie el GetOrders método para devolver solo los pedidos que pertenecen al usuario:
public IEnumerable<Order> GetOrders()
{
return db.Orders.Where(o => o.Customer == User.Identity.Name);
}
Cambie el método de la siguiente manera: GetOrder
public OrderDTO GetOrder(int id)
{
Order order = db.Orders.Include("OrderDetails.Product")
.First(o => o.Id == id && o.Customer == User.Identity.Name);
if (order == null)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
return new OrderDTO()
{
Details = from d in order.OrderDetails
select new OrderDTO.Detail()
{
ProductID = d.Product.Id,
Product = d.Product.Name,
Price = d.Product.Price,
Quantity = d.Quantity
}
};
}
Estos son los cambios realizados en el método :
- El valor devuelto es una instancia de
OrderDTO, en lugar de una instancia deOrder. - Cuando consultamos la base de datos para el pedido, usamos el método DbQuery.Include para obtener las entidades relacionadas
OrderDetailyProduct. - Se aplana el resultado mediante una proyección.
La respuesta HTTP contendrá una matriz de productos con cantidades:
{"Details":[{"ProductID":1,"Product":"Tomato Soup","Price":1.39,"Quantity":2},
{"ProductID":3,"Product":"Yo yo","Price":6.99,"Quantity":1}]}
Este formato es más fácil para que los clientes consuman que el gráfico de objetos original, que contiene entidades anidadas (orden, detalles y productos).
El último método que se debe tener en cuenta es PostOrder. En este momento, este método toma una Order instancia. Pero tenga en cuenta lo que sucede si un cliente envía un cuerpo de solicitud de la siguiente manera:
{"Customer":"Alice","OrderDetails":[{"Quantity":1,"Product":{"Name":"Koala bears",
"Price":5,"ActualCost":1}}]}
Se trata de un orden bien estructurado y Entity Framework lo insertará felizmente en la base de datos. Pero contiene una entidad "Product" que no existía anteriormente. El cliente acaba de crear un nuevo producto en nuestra base de datos. Esta será una sorpresa para el departamento de cumplimiento de pedidos, cuando vean una orden para los osos de koala. La moral es, tenga cuidado sobre los datos que acepta en una solicitud POST o PUT.
Para evitar este problema, cambie el PostOrder método para tomar una OrderDTO instancia. Usa el OrderDTO para crear el Order.
var order = new Order()
{
Customer = User.Identity.Name,
OrderDetails = (from item in dto.Details select new OrderDetail()
{ ProductId = item.ProductID, Quantity = item.Quantity }).ToList()
};
Tenga en cuenta que usamos las ProductID propiedades y Quantity , y omitemos los valores que el cliente envió para el nombre del producto o el precio. Si el identificador del producto no es válido, infringirá la restricción de clave externa en la base de datos y se producirá un error en la inserción, como debería.
Este es el PostOrder método completo:
public HttpResponseMessage PostOrder(OrderDTO dto)
{
if (ModelState.IsValid)
{
var order = new Order()
{
Customer = User.Identity.Name,
OrderDetails = (from item in dto.Details select new OrderDetail()
{ ProductId = item.ProductID, Quantity = item.Quantity }).ToList()
};
db.Orders.Add(order);
db.SaveChanges();
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, order);
response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = order.Id }));
return response;
}
else
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
Por último, agregue el atributo Authorize al controlador:
[Authorize]
public class OrdersController : ApiController
{
// ...
Ahora solo los usuarios registrados pueden crear o ver pedidos.