Część 6. Tworzenie kontrolerów produktów i zamówień
Autor: Rick Anderson
Pobieranie ukończonego projektu
Dodawanie kontrolera produktów
Kontroler Administracja jest przeznaczony dla użytkowników, którzy mają uprawnienia administratora. Z drugiej strony klienci mogą wyświetlać produkty, ale nie mogą ich tworzyć, aktualizować ani usuwać.
Można łatwo ograniczyć dostęp do metod Post, Put i Delete, pozostawiając otwarte metody Get. Spójrz jednak na dane, które są zwracane dla produktu:
{"Id":1,"Name":"Tomato Soup","Price":1.39,"ActualCost":0.99}
Właściwość nie powinna być widoczna ActualCost
dla klientów! Rozwiązaniem jest zdefiniowanie obiektu transferu danych (DTO), który zawiera podzestaw właściwości, które powinny być widoczne dla klientów. Użyjemy LINQ do tworzenia Product
wystąpień ProductDTO
w wystąpieniach.
Dodaj klasę o nazwie ProductDTO
do folderu Models.
namespace ProductStore.Models
{
public class ProductDTO
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
}
Teraz dodaj kontroler. W Eksplorator rozwiązań kliknij prawym przyciskiem myszy folder Controllers. Wybierz pozycję Dodaj, a następnie wybierz pozycję Kontroler. W oknie dialogowym Dodawanie kontrolera nadaj kontrolerowi nazwę "ProductsController". W obszarze Szablon wybierz pozycję Pusty kontroler interfejsu API.
Zastąp wszystko w pliku źródłowym następującym kodem:
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 nadal używa parametru OrdersContext
do wykonywania zapytań względem bazy danych. Jednak zamiast zwracać Product
wystąpienia bezpośrednio, wywołujemy MapProducts
metodę , aby projektować je na ProductDTO
wystąpieniach:
return from p in db.Products select new ProductDTO()
{ Id = p.Id, Name = p.Name, Price = p.Price };
Metoda MapProducts
zwraca wartość IQueryable, więc możemy utworzyć wynik z innymi parametrami zapytania. Można to zobaczyć w metodzie GetProduct
, która dodaje klauzulę where do zapytania:
var product = (from p in MapProducts()
where p.Id == 1
select p).FirstOrDefault();
Dodawanie kontrolera zamówień
Następnie dodaj kontroler, który umożliwia użytkownikom tworzenie i wyświetlanie zamówień.
Zaczniemy od innego celu DTO. W Eksplorator rozwiązań kliknij prawym przyciskiem myszy folder Models i dodaj klasę o nazwie OrderDTO
Użyj następującej implementacji:
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; }
}
}
Teraz dodaj kontroler. W Eksplorator rozwiązań kliknij prawym przyciskiem myszy folder Controllers. Wybierz pozycję Dodaj, a następnie wybierz pozycję Kontroler. W oknie dialogowym Dodawanie kontrolera ustaw następujące opcje:
- W obszarze Nazwa kontrolera wprowadź wartość "OrdersController".
- W obszarze Szablon wybierz pozycję "Kontroler interfejsu API z akcjami odczytu/zapisu przy użyciu platformy Entity Framework".
- W obszarze Klasa modelu wybierz pozycję "Order (ProductStore.Models)".
- W obszarze Klasa kontekstu danych wybierz pozycję "OrdersContext (ProductStore.Models)".
Kliknij pozycję Dodaj. Spowoduje to dodanie pliku o nazwie OrdersController.cs. Następnie musimy zmodyfikować domyślną implementację kontrolera.
Najpierw usuń PutOrder
metody i DeleteOrder
. W tym przykładzie klienci nie mogą modyfikować ani usuwać istniejących zamówień. W rzeczywistej aplikacji potrzebna byłaby duża logika zaplecza do obsługi tych przypadków. (Na przykład czy zamówienie zostało już wysłane?)
Zmień metodę, GetOrders
aby zwrócić tylko zamówienia należące do użytkownika:
public IEnumerable<Order> GetOrders()
{
return db.Orders.Where(o => o.Customer == User.Identity.Name);
}
Zmień metodę GetOrder
w następujący sposób:
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
}
};
}
Oto zmiany wprowadzone w metodzie:
- Wartość zwracana jest wystąpieniem
OrderDTO
zamiastOrder
. - Podczas wykonywania zapytań dotyczących bazy danych dla zamówienia użyjemy metody DbQuery.Include , aby pobrać powiązane
OrderDetail
jednostki iProduct
. - Spłaszczamy wynik przy użyciu projekcji.
Odpowiedź HTTP będzie zawierać tablicę produktów z ilościami:
{"Details":[{"ProductID":1,"Product":"Tomato Soup","Price":1.39,"Quantity":2},
{"ProductID":3,"Product":"Yo yo","Price":6.99,"Quantity":1}]}
Ten format jest łatwiejszy w przypadku klientów korzystających z oryginalnego grafu obiektów, który zawiera zagnieżdżone jednostki (kolejność, szczegóły i produkty).
Ostatnia metoda do rozważenia .PostOrder
W tej chwili ta metoda przyjmuje Order
wystąpienie. Rozważ jednak, co się stanie, jeśli klient wyśle treść żądania w następujący sposób:
{"Customer":"Alice","OrderDetails":[{"Quantity":1,"Product":{"Name":"Koala bears",
"Price":5,"ActualCost":1}}]}
Jest to dobrze ustrukturyzowana kolejność, a program Entity Framework szczęśliwie wstawi go do bazy danych. Zawiera jednak jednostkę Product, która nie istniała wcześniej. Klient właśnie utworzył nowy produkt w naszej bazie danych! To będzie zaskoczeniem dla działu realizacji zamówienia, kiedy widzą zamówienie na niedźwiedzie koala. Moralna jest, naprawdę uważaj na dane, które akceptujesz w żądaniu POST lub PUT.
Aby uniknąć tego problemu, zmień metodę PostOrder
OrderDTO
na wystąpienie. Użyj polecenia OrderDTO
, aby utworzyć element 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()
};
Zwróć uwagę, że używamy ProductID
właściwości i Quantity
i ignorujemy wszystkie wartości wysyłane przez klienta dla nazwy produktu lub ceny. Jeśli identyfikator produktu jest nieprawidłowy, będzie naruszać ograniczenie klucza obcego w bazie danych, a wstawianie zakończy się niepowodzeniem, tak jak powinno.
Oto kompletna 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);
}
}
Na koniec dodaj atrybut Autoryzuj do kontrolera:
[Authorize]
public class OrdersController : ApiController
{
// ...
Teraz tylko zarejestrowani użytkownicy mogą tworzyć lub wyświetlać zamówienia.