Udostępnij za pośrednictwem


Część 6. Tworzenie kontrolerów produktów i zamówień

Autor: Rick Anderson

Pobieranie ukończonego projektu

Dodawanie kontrolera produktów

Kontroler administratora jest przeznaczony dla użytkowników, którzy posiadają przywileje 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 zwrócone dla produktu:

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

ActualCost Właściwość nie powinna być widoczna dla klientów! Rozwiązaniem jest zdefiniowanie obiektu transferu danych (DTO), który zawiera podzbiór właściwości, które powinny być widoczne dla klientów. Użyjemy LINQ do przekształcenia wystąpień Product w wystąpienia ProductDTO.

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. Kliknij prawym przyciskiem myszy na folderze Controllers w Eksploratorze Rozwiązań. 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.

Zrzut ekranu przedstawiający okno dialogowe Dodawanie kontrolera.

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ć bezpośrednio instancje Product, wywołujemy metodę MapProducts, aby przekształcać je w instancje ProductDTO.

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 przy użyciu innych parametrów 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 obiektu DTO. W Eksploratorze 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 Eksploratorze 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ę "API controller z akcjami odczytu/zapisu, przy użyciu Entity Framework".
  • W obszarze Klasa modelu wybierz pozycję "Order (ProductStore.Models)".
  • W obszarze Klasa kontekstu danych wybierz pozycję "OrdersContext (ProductStore.Models)".

Zrzut ekranu przedstawiający okno dialogowe Dodaj kontroler. OrdersController jest wpisany w polu tekstowym.

Kliknij przycisk Dodaj. Spowoduje to dodanie pliku o nazwie OrdersController.cs. Następnie musimy zmodyfikować domyślną implementację kontrolera.

Najpierw usuń PutOrder oraz DeleteOrder metody. W tym przykładzie klienci nie mogą modyfikować ani usuwać istniejących zamówień. W rzeczywistej aplikacji potrzebna byłaby wiele logiki 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
                      }
    };
}

Poniżej przedstawiono zmiany wprowadzone w metodzie :

  • Wartość zwracana jest wystąpieniem OrderDTO zamiast Order.
  • Podczas zapytania bazy danych o zamówienie używamy metody DbQuery.Include, aby pobrać powiązane jednostki OrderDetail i Product.
  • 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 do przyswojenia dla klientów niż oryginalny graf obiektów, który zawiera zagnieżdżone jednostki (zamówienie, szczegóły i produkty).

Ostatnia metoda, aby ją PostOrderrozważyć. W tej chwili ta metoda przyjmuje Order instancję. Należy jednak rozważyć, 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! Będzie to zaskoczeniem dla działu realizacji zamówień, kiedy widzą zamówienie na niedźwiedzie koala. Morał jest taki, bardzo uważaj na dane, które akceptujesz w żądaniu POST lub PUT.

Aby uniknąć tego problemu, zmień metodę PostOrder, aby przyjmowała instancję OrderDTO. Użyj elementu , 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 właściwości ProductID 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 Authorize do kontrolera:

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

Teraz tylko zarejestrowani użytkownicy mogą tworzyć lub wyświetlać zamówienia.