Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
por Rick Anderson
Adicionar um controlador de produtos
O controlador de administrador é para utilizadores que têm privilégios de administrador. Os clientes, por outro lado, podem ver produtos, mas não podem criá-los, atualizá-los ou eliminá-los.
Podemos facilmente restringir o acesso aos métodos Publicar, Colocar e Eliminar, mantendo os métodos Obter abertos. Mas veja os dados que são devolvidos para um produto:
{"Id":1,"Name":"Tomato Soup","Price":1.39,"ActualCost":0.99}
A ActualCost propriedade não deve ser visível para os clientes! A solução é definir um objeto de transferência de dados (DTO) que inclua um subconjunto de propriedades que devem ser visíveis para os clientes. Vamos usar o LINQ para mapear instâncias de Product para ProductDTO.
Adicione uma classe nomeada ProductDTO à pasta Models.
namespace ProductStore.Models
{
public class ProductDTO
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
}
Agora adiciona o controlador. No Explorador de Soluções, clique com o botão direito na pasta Controladores. Seleciona Adicionar, depois seleciona Controlador. No diálogo Adicionar Controlador , nomeie o controlador como "ProductsController". Em Template, selecione Controlador de API vazio.
Substitua tudo o que está no ficheiro fonte pelo seguinte código:
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);
}
}
}
O controlador continua a usar o OrdersContext para consultar a base de dados. Mas, em vez de devolver Product instâncias diretamente, chamamos MapProducts para projetá-las sobre ProductDTO instâncias:
return from p in db.Products select new ProductDTO()
{ Id = p.Id, Name = p.Name, Price = p.Price };
O MapProducts método devolve um IQueryable, por isso podemos compor o resultado com outros parâmetros de consulta. Pode ver isto no GetProduct método, que adiciona uma cláusula where à consulta:
var product = (from p in MapProducts()
where p.Id == 1
select p).FirstOrDefault();
Adicionar um Controlador de Ordens
De seguida, adicione um controlador que permita aos utilizadores criar e visualizar ordens.
Vamos começar com outro DTO. No Explorador de Soluções, clique com o botão direito na pasta Modelos e adicione uma classe chamada OrderDTO Use a seguinte implementação:
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; }
}
}
Agora adiciona o controlador. No Explorador de Soluções, clique com o botão direito na pasta Controladores. Seleciona Adicionar, depois seleciona Controlador. No diálogo Adicionar Controlador , defina as seguintes opções:
- Em Nome do Controlador, introduza "OrdersController".
- Em Template, selecione "controlador API com ações de leitura/escrita, usando Entity Framework".
- Na classe de modelo, selecione "Order (ProductStore.Models)".
- Na classe de contexto de dados, selecione "OrdersContext (ProductStore.Models)".
Clique em Adicionar. Isto adiciona um ficheiro chamado OrdersController.cs. De seguida, precisamos de modificar a implementação padrão do controlador.
Primeiro, apague os métodos PutOrder e DeleteOrder. Para este exemplo, os clientes não podem modificar ou eliminar encomendas existentes. Numa aplicação real, seria necessário muita lógica de back-end para lidar com estes casos. (Por exemplo, a encomenda já estava enviada?)
Altere o GetOrders método para devolver apenas as ordens que pertencem ao utilizador:
public IEnumerable<Order> GetOrders()
{
return db.Orders.Where(o => o.Customer == User.Identity.Name);
}
Mude o GetOrder método da seguinte forma:
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
}
};
}
Aqui estão as alterações que fizemos ao método:
- O valor de retorno é uma instância de
OrderDTO, em vez deOrder. - Quando consultamos a base de dados para a ordem, usamos o método DbQuery.Include para obter as entidades relacionadas
OrderDetaileProduct. - Achatamos o resultado usando uma projeção.
A resposta HTTP conterá um conjunto de produtos com quantidades:
{"Details":[{"ProductID":1,"Product":"Tomato Soup","Price":1.39,"Quantity":2},
{"ProductID":3,"Product":"Yo yo","Price":6.99,"Quantity":1}]}
Este formato é mais fácil de consumir para os clientes do que o gráfico de objetos original, que contém entidades aninhadas (encomenda, detalhes e produtos).
O último método a considerar é PostOrder. Neste momento, este método leva uma Order instância. Mas considere o que acontece se um cliente enviar um corpo de pedido assim:
{"Customer":"Alice","OrderDetails":[{"Quantity":1,"Product":{"Name":"Koala bears",
"Price":5,"ActualCost":1}}]}
Esta é uma ordem bem estruturada, e o Entity Framework irá inseri-la com prazer na base de dados. Mas contém uma entidade Produto que não existia anteriormente. O cliente acabou de criar um novo produto na nossa base de dados! Isto será uma surpresa para o departamento de atendimento de encomendas, quando virem uma encomenda de coalas. A moral é: tem muito cuidado com os dados que aceitas num pedido POST ou PUT.
Para evitar este problema, altere o PostOrder método para pegar numa OrderDTO instância. Use o OrderDTO para criar o 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()
};
Repare que usamos o ProductID e Quantity propriedades e ignoramos quaisquer valores que o cliente tenha enviado para nomes ou preços do produto. Se o ID do produto não for válido, irá violar a restrição da chave estrangeira na base de dados, e o inserto falhará, como deveria.
Aqui está o método completo PostOrder :
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);
}
}
Finalmente, adicione o atributo Authorize ao controlador:
[Authorize]
public class OrdersController : ApiController
{
// ...
Agora só os utilizadores registados podem criar ou visualizar encomendas.