Parte 6: Criando controladores de produto e pedido

por Rick Anderson

Baixar Projeto Concluído

Adicionar um controlador de produtos

O controlador Administração é para usuários que têm privilégios de administrador. Os clientes, por outro lado, podem exibir produtos, mas não podem criá-los, atualizá-los ou excluí-los.

Podemos restringir facilmente o acesso aos métodos Post, Put e Delete, deixando os métodos Get abertos. Mas examine os dados retornados 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 DTO (objeto de transferência de dados ) que inclui um subconjunto de propriedades que devem ser visíveis para os clientes. Usaremos o LINQ para projetar Product instâncias em ProductDTO instâncias.

Adicione uma classe chamada ProductDTO à pasta Modelos.

namespace ProductStore.Models
{
    public class ProductDTO
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
    }
}

Agora adicione o controlador. No Gerenciador de Soluções, clique com o botão direito do mouse na pasta Controladores. Selecione Adicionare, em seguida, controlador. Na caixa de diálogo Adicionar Controlador , nomeie o controlador como "ProductsController". Em Modelo, selecione Controlador de API Vazio.

Captura de tela da caixa de diálogo adicionar controlador.

Substitua tudo no arquivo de origem 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 ainda usa o OrdersContext para consultar o banco de dados. Mas, em vez de retornar Product instâncias diretamente, chamamos para projetá-las MapProducts em 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 retorna um IQueryable, para que possamos compor o resultado com outros parâmetros de consulta. Você pode ver isso 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 pedidos

Em seguida, adicione um controlador que permite que os usuários criem e exibam pedidos.

Começaremos com outro DTO. Em Gerenciador de Soluções, clique com o botão direito do mouse na pasta Modelos e adicione uma classe chamada OrderDTO Usar 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 adicione o controlador. No Gerenciador de Soluções, clique com o botão direito do mouse na pasta Controladores. Selecione Adicionare, em seguida, controlador. Na caixa de diálogo Adicionar Controlador , defina as seguintes opções:

  • Em Nome do Controlador, insira "OrdersController".
  • Em Modelo, selecione "Controlador de API com ações de leitura/gravação, usando o Entity Framework".
  • Em Classe de modelo, selecione "Pedido (ProductStore.Models)".
  • Em Classe de contexto de dados, selecione "OrdersContext (ProductStore.Models)".

Captura de tela da caixa de diálogo adicionar controlador. OrdersController é escrito na caixa de texto.

Clique em Adicionar. Isso adiciona um arquivo chamado OrdersController.cs. Em seguida, precisamos modificar a implementação padrão do controlador.

Primeiro, exclua os PutOrder métodos e DeleteOrder . Para este exemplo, os clientes não podem modificar ou excluir pedidos existentes. Em um aplicativo real, você precisaria de muita lógica de back-end para lidar com esses casos. (Por exemplo, o pedido já foi enviado?)

Altere o GetOrders método para retornar apenas os pedidos que pertencem ao usuário:

public IEnumerable<Order> GetOrders()
{
    return db.Orders.Where(o => o.Customer == User.Identity.Name);
}

Altere o método da GetOrder seguinte maneira:

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 feitas no método :

  • O valor retornado é uma OrderDTO instância, em vez de um Order.
  • Quando consultamos o banco de dados para a ordem, usamos o método DbQuery.Include para buscar as entidades e Product relacionadasOrderDetail.
  • Nivelamos o resultado usando uma projeção.

A resposta HTTP conterá uma matriz 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}]}

Esse formato é mais fácil para os clientes consumirem do que o grafo de objeto original, que contém entidades aninhadas (ordem, detalhes e produtos).

O último método a considerá-lo PostOrder. Neste momento, esse método usa uma Order instância. Mas considere o que acontece se um cliente enviar um corpo de solicitação como este:

{"Customer":"Alice","OrderDetails":[{"Quantity":1,"Product":{"Name":"Koala bears", 
"Price":5,"ActualCost":1}}]}

Essa é uma ordem bem estruturada e o Entity Framework a inserirá alegremente no banco de dados. Mas ele contém uma entidade Product que não existia anteriormente. O cliente acabou de criar um novo produto em nosso banco de dados! Isso será uma surpresa para o departamento de cumprimento da ordem, quando eles vêem uma ordem para ursos coalas. A moral é ter muito cuidado com os dados que você aceita em uma solicitação POST ou PUT.

Para evitar esse problema, altere o PostOrder método para usar uma 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()
};

Observe que usamos as ProductID propriedades e Quantity e ignoramos todos os valores que o cliente enviou para o nome ou o preço do produto. Se a ID do produto não for válida, ela violará a restrição de chave estrangeira no banco de dados e a inserção falhará, como deveria.

Este é 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);
    }
}

Por fim, adicione o atributo Authorize ao controlador:

[Authorize]
public class OrdersController : ApiController
{
    // ...

Agora, somente usuários registrados podem criar ou exibir pedidos.