Partilhar via


Parte 6: Criação de Controladores de Produto e Encomenda

por Rick Anderson

Descarregar Projeto Concluído

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.

Captura de ecrã da caixa de diálogo para adicionar controlador.

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)".

Captura de ecrã da caixa de diálogo de adicionar controlador. OrdersController está escrito na caixa de texto.

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 de Order.
  • Quando consultamos a base de dados para a ordem, usamos o método DbQuery.Include para obter as entidades relacionadas OrderDetail e Product.
  • 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.