Condividi tramite


Parte 6: Creazione di controller di prodotto e ordine

di Rick Anderson

Scaricare il progetto completato

Aggiungere un controller di prodotti

Il controller di Amministrazione è destinato agli utenti con privilegi di amministratore. I clienti, invece, possono visualizzare i prodotti, ma non possono creare, aggiornare o eliminarli.

È possibile limitare facilmente l'accesso ai metodi Post, Put ed Delete, lasciando aperti i metodi Get. Tuttavia, esaminare i dati restituiti per un prodotto:

{"Id":1,"Name":"Tomato Soup","Price":1.39,"ActualCost":0.99}

La ActualCost proprietà non deve essere visibile ai clienti! La soluzione consiste nel definire un oggetto DTO ( Data Transfer Object ) che include un subset di proprietà che devono essere visibili ai clienti. Verrà usato LINQ per proiettare Product le istanze alle ProductDTO istanze.

Aggiungere una classe denominata ProductDTO alla cartella Models.

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

Aggiungere ora il controller. In Esplora soluzioni fare clic sulla cartella Controller. Selezionare Aggiungi e quindi Controller. Nella finestra di dialogo Aggiungi controller assegnare al controller il nome "ProductsController". In Modello selezionare Controller API vuoto.

Screenshot della finestra di dialogo Aggiungi controller.

Sostituire tutto nel file di origine con il codice seguente:

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

Il controller usa comunque l'oggetto OrdersContext per eseguire query sul database. Invece di restituire Product direttamente le istanze, viene chiamato MapProducts a proiettarli in ProductDTO istanze:

return from p in db.Products select new ProductDTO() 
    { Id = p.Id, Name = p.Name, Price = p.Price };

Il MapProducts metodo restituisce un oggetto IQueryable, in modo da poter comporre il risultato con altri parametri di query. Questo è possibile vedere nel GetProduct metodo, che aggiunge una clausola where alla query:

var product = (from p in MapProducts() 
    where p.Id == 1
    select p).FirstOrDefault();

Aggiungere un controller ordini

Aggiungere quindi un controller che consente agli utenti di creare e visualizzare gli ordini.

Inizieremo con un altro DTO. In Esplora soluzioni fare clic con il pulsante destro del mouse sulla cartella Modelli e aggiungere una classe denominata OrderDTO Usa l'implementazione seguente:

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; }
    }
}

Aggiungere ora il controller. In Esplora soluzioni fare clic sulla cartella Controller. Selezionare Aggiungi e quindi Controller. Nella finestra di dialogo Aggiungi controller impostare le opzioni seguenti:

  • In Nome controller immettere "OrdersController".
  • In Modello selezionare "Controller API con azioni di lettura/scrittura, usando Entity Framework".
  • In Classe Model selezionare "Order (ProductStore.Models)".
  • In Classe contesto dati selezionare "OrdersContext (ProductStore.Models)".

Screenshot della finestra di dialogo Aggiungi controller. OrdersController viene scritto nella casella di testo.

Scegliere Aggiungi. In questo modo viene aggiunto un file denominato OrdersController.cs. È quindi necessario modificare l'implementazione predefinita del controller.

Prima di tutto, eliminare i PutOrder metodi e DeleteOrder . Per questo esempio, i clienti non possono modificare o eliminare ordini esistenti. In un'applicazione reale è necessario un sacco di logica back-end per gestire questi casi. (Ad esempio, è stato già spedito l'ordine?)

Modificare il GetOrders metodo per restituire solo gli ordini appartenenti all'utente:

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

Modificare il GetOrder metodo come indicato di seguito:

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
                      }
    };
}

Ecco le modifiche apportate al metodo:

  • Il valore restituito è un'istanza OrderDTO , anziché un Orderoggetto .
  • Quando si esegue una query sul database per l'ordine, viene usato il metodo DbQuery.Include per recuperare le entità e Product correlateOrderDetail.
  • Il risultato viene appiattito usando una proiezione.

La risposta HTTP conterrà una matrice di prodotti con quantità:

{"Details":[{"ProductID":1,"Product":"Tomato Soup","Price":1.39,"Quantity":2},
{"ProductID":3,"Product":"Yo yo","Price":6.99,"Quantity":1}]}

Questo formato è più semplice per i client da usare rispetto al grafico degli oggetti originale, che contiene entità annidate (ordine, dettagli e prodotti).

Ultimo metodo da considerare PostOrder. A questo momento, questo metodo accetta un'istanza Order . Si consideri tuttavia cosa accade se un client invia un corpo della richiesta simile al seguente:

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

Si tratta di un ordine ben strutturato e Entity Framework lo inserisce felicemente nel database. Contiene tuttavia un'entità Product che non esiste in precedenza. Il client ha appena creato un nuovo prodotto nel database! Questa sarà una sorpresa per il reparto di evasione degli ordini, quando vedono un ordine per gli orsi koala. La morale è, prestare molta attenzione ai dati accettati in una richiesta POST o PUT.

Per evitare questo problema, modificare il metodo per accettare un'istanza PostOrderOrderDTO . Usare l'oggetto OrderDTO per creare l'oggetto 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()
};

Si noti che vengono usate le proprietà e Quantity e vengono ignorati tutti i ProductID valori inviati dal client per il nome del prodotto o il prezzo. Se l'ID prodotto non è valido, viola il vincolo di chiave esterna nel database e l'inserimento avrà esito negativo, come dovrebbe.

Ecco il metodo 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);
    }
}

Infine, aggiungere l'attributo Authorize al controller:

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

Ora solo gli utenti registrati possono creare o visualizzare gli ordini.