Condividi tramite


Parte 6: Creazione di controller di prodotti e ordini

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, d'altra parte, possono visualizzare i prodotti, ma non possono crearli, aggiornarli o eliminarli.

È possibile limitare facilmente l'accesso ai metodi Post, Put e Delete, lasciando aperti i metodi Get. Esaminare tuttavia 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. Si userà LINQ per proiettare istanze di Product su istanze di ProductDTO.

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 con il pulsante destro del mouse sulla cartella Controllers. Selezionare Aggiungi e quindi Controller. Nella finestra di dialogo Aggiungi controller assegnare al controller il nome "ProductsController". Sotto Modello, seleziona Controller API vuoto.

Screenshot della finestra di dialogo Aggiungi controller.

Sostituire tutti gli elementi 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 ancora OrdersContext per eseguire query sul database. Invece di restituire direttamente le istanze Product, viene chiamato MapProducts per proiettarli in istanze ProductDTO.

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. È possibile visualizzarlo 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 consenta agli utenti di creare e visualizzare gli ordini.

Si inizierà 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 con il pulsante destro del mouse sulla cartella Controllers. 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 modello 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.

Fare clic su Aggiungi. Verrà aggiunto un file denominato OrdersController.cs. Successivamente, è necessario modificare l'implementazione predefinita del controller.

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

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

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

Modificare il GetOrder metodo come segue:

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 oggetto Order.
  • Quando si esegue una query sul database per l'ordine, si usa il metodo DbQuery.Include per recuperare le entità correlate OrderDetail e Product.
  • Il risultato viene appiattito tramite 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 clienti da utilizzare rispetto al grafico dell'oggetto originale, che contiene entità nidificate (ordine, dettagli e prodotti).

L'ultimo metodo da considerare come opzione PostOrder. Al 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. Ma contiene un'entità Product che non esisteva in precedenza. Il client ha appena creato un nuovo prodotto nel database. Sarà una sorpresa per il reparto di evasione degli ordini, quando vedranno un ordine per gli orsi koala. La morale è, prestare molta attenzione ai dati accettati in una richiesta POST o PUT.

Per evitare questo problema, modifica il metodo PostOrder per accettare un'istanza di OrderDTO. Usare il OrderDTO per creare il 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 ProductID proprietà e Quantity e vengono ignorati tutti i valori inviati dal client per il nome o il prezzo del prodotto. 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);
    }
}

Aggiungere infine l'attributo Authorize al controller:

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

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