Část 6: Vytvoření kontrolerů produktů a objednávek

Rick Anderson

Stáhnout dokončený projekt

Přidání kontroleru produktů

Kontroler Správa je určený pro uživatele, kteří mají oprávnění správce. Zákazníci naopak můžou produkty zobrazit, ale nemůžou je vytvářet, aktualizovat ani odstraňovat.

Můžeme snadno omezit přístup k metodám Post, Put a Delete a ponechat metody Get otevřené. Podívejte se ale na data vrácená pro produkt:

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

Vlastnost ActualCost by neměla být viditelná pro zákazníky! Řešením je definovat objekt přenosu dat (DTO), který obsahuje podmnožinu vlastností, které by měly být viditelné pro zákazníky. K promítání Product instancí do ProductDTO instancí použijeme LINQ.

Přidejte třídu s názvem ProductDTO do složky Modely.

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

Teď přidejte kontroler. V Průzkumník řešení klikněte pravým tlačítkem na složku Kontrolery. Vyberte Přidat a pak vyberte Kontroler. V dialogovém okně Přidat kontroler pojmenujte kontroler ProductsController. V části Šablona vyberte Prázdný kontroler rozhraní API.

Snímek obrazovky s dialogem přidat ovladač

Nahraďte vše ve zdrojovém souboru následujícím kódem:

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

Kontroler stále používá OrdersContext k dotazování databáze . Ale místo přímého vrácení Product instancí voláme MapProducts , abychom je promítali na ProductDTO instance:

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

Metoda MapProducts vrátí IQueryable, takže můžeme vytvořit výsledek s dalšími parametry dotazu. Můžete to vidět v GetProduct metodě, která do dotazu přidá klauzuli where :

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

Přidání kontroleru objednávek

Dále přidejte kontroler, který uživatelům umožní vytvářet a zobrazovat objednávky.

Začneme s dalším DTO. V Průzkumník řešení klikněte pravým tlačítkem na složku Models a přidejte třídu s názvem OrderDTO Použít následující implementaci:

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

Teď přidejte kontroler. V Průzkumník řešení klikněte pravým tlačítkem na složku Kontrolery. Vyberte Přidat a pak vyberte Kontroler. V dialogovém okně Přidat kontroler nastavte následující možnosti:

  • V části Název kontroleru zadejte "OrdersController".
  • V části Šablona vyberte Kontroler rozhraní API s akcemi čtení a zápisu pomocí Entity Frameworku.
  • V části Třída modelu vyberte Order (ProductStore.Models).
  • V části Třída kontextu dat vyberte OrdersContext (ProductStore.Models).

Snímek obrazovky s dialogem přidat ovladač OrdersController je zapsán v textovém poli.

Klikněte na Přidat. Tím se přidá soubor s názvem OrdersController.cs. Dále musíme upravit výchozí implementaci kontroleru.

Nejprve odstraňte PutOrder metody a DeleteOrder . V této ukázce zákazníci nemůžou upravovat nebo odstraňovat existující objednávky. V reálné aplikaci byste k těmto případům potřebovali spoustu back-endové logiky. (Byla například objednávka již odeslána?)

Změňte metodu GetOrders tak, aby vracela jenom objednávky, které patří uživateli:

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

Změňte metodu GetOrder následujícím způsobem:

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

Tady jsou změny, které jsme provedli v metodě:

  • Vrácená hodnota je OrderDTO instance místo Order.
  • Při dotazování databáze na pořadí použijeme k načtení souvisejících OrderDetail a Product entit metodu DbQuery.Include.
  • Výsledek zploštěme pomocí projekce.

Odpověď HTTP bude obsahovat pole produktů s množstvím:

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

Tento formát je pro klienty jednodušší než původní graf objektů, který obsahuje vnořené entity (pořadí, podrobnosti a produkty).

Poslední metoda, která je třeba zvážit .PostOrder Právě teď tato metoda přebírá Order instanci. Zvažte ale, co se stane, když klient odešle text požadavku takto:

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

Jedná se o dobře strukturované pořadí a Entity Framework ho s radostí vloží do databáze. Obsahuje ale entitu Product, která dříve neexistovala. Klient právě vytvořil nový produkt v naší databázi! To bude překvapením pro oddělení plnění objednávek, když uvidí objednávku pro medvědy koala. Morální je, že buďte opravdu opatrní ohledně dat, která přijímáte v požadavku POST nebo PUT.

Chcete-li se tomuto problému vyhnout, změňte metodu PostOrder tak, aby převzala OrderDTO instanci. Pomocí příkazu OrderDTO vytvořte 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()
};

Všimněte si, že používáme ProductID vlastnosti a Quantity a ignorujeme všechny hodnoty, které klient odeslal pro název produktu nebo cenu. Pokud id produktu není platné, dojde k porušení omezení cizího klíče v databázi a vložení selže, jak by mělo.

Tady je úplná PostOrder metoda:

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

Nakonec přidejte atribut Authorize do kontroleru:

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

Objednávky teď můžou vytvářet nebo zobrazovat jenom registrovaní uživatelé.