Část 6: Vytvoření kontrolerů produktů a objednávek
Rick Anderson
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.
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).
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ístoOrder
. - Při dotazování databáze na pořadí použijeme k načtení souvisejících
OrderDetail
aProduct
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é.