Freigeben über


Teil 6: Erstellen von Produkt- und Bestellcontrollern

von Rick Anderson

Abgeschlossenes Projekt herunterladen

Hinzufügen eines Produktcontrollers

Der Admin-Controller richtet sich an Benutzer, die über Administratorrechte verfügen. Kunden hingegen können Produkte anzeigen, aber nicht erstellen, aktualisieren oder löschen.

Wir können den Zugriff auf die Methoden Post, Put und Delete problemlos einschränken, während die Get-Methoden geöffnet bleiben. Sehen Sie sich jedoch die daten an, die für ein Produkt zurückgegeben werden:

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

Die ActualCost Eigenschaft sollte für Kunden nicht sichtbar sein! Die Lösung besteht darin, ein Datenübertragungsobjekt (Data Transfer Object , DTO) zu definieren, das eine Teilmenge von Eigenschaften enthält, die für Kunden sichtbar sein sollten. Wir verwenden LINQ, um Instanzen in Instanzen zu ProductDTO projizierenProduct.

Fügen Sie dem Ordner Models eine Klasse mit dem Namen ProductDTO hinzu.

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

Fügen Sie nun den Controller hinzu. Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf den Ordner „Controller“. Wählen Sie Hinzufügen und dann Controller aus. Geben Sie dem Controller im Dialogfeld Controller hinzufügen den Namen "ProductsController". Wählen Sie unter Vorlagedie Option Leere API-Controller aus.

Screenshot des Dialogfelds

Ersetzen Sie alles in der Quelldatei durch den folgenden Code:

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

Der Controller verwendet weiterhin den OrdersContext , um die Datenbank abzufragen. Aber anstatt Instanzen direkt zurückzugeben Product , rufen MapProducts wir auf, um sie auf ProductDTO Instanzen zu projizieren:

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

Die MapProducts Methode gibt ein IQueryable zurück, sodass wir das Ergebnis mit anderen Abfrageparametern zusammenstellen können. Dies wird in der GetProduct -Methode angezeigt, die der Abfrage eine where-Klausel hinzufügt:

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

Hinzufügen eines Auftragscontrollers

Fügen Sie als Nächstes einen Controller hinzu, mit dem Benutzer Bestellungen erstellen und anzeigen können.

Wir beginnen mit einem weiteren DTO. Klicken Sie in Projektmappen-Explorer mit der rechten Maustaste auf den Ordner Models, und fügen Sie eine Klasse mit dem Namen OrderDTO Verwenden Sie die folgende Implementierung hinzu:

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

Fügen Sie nun den Controller hinzu. Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf den Ordner „Controller“. Wählen Sie Hinzufügen und dann Controller aus. Legen Sie im Dialogfeld Controller hinzufügen die folgenden Optionen fest:

  • Geben Sie unter Controllername "OrdersController" ein.
  • Wählen Sie unter Vorlage die Option "API-Controller mit Lese-/Schreibaktionen mithilfe von Entity Framework" aus.
  • Wählen Sie unter Model-Klasse die Option "Order (ProductStore.Models)" aus.
  • Wählen Sie unter Datenkontextklasse die Option "OrdersContext (ProductStore.Models)" aus.

Screenshot des Dialogfelds

Klicken Sie auf Hinzufügen. Dadurch wird eine Datei namens OrdersController.cs hinzugefügt. Als Nächstes müssen wir die Standardimplementierung des Controllers ändern.

Löschen Sie zunächst die PutOrder Methoden und DeleteOrder . In diesem Beispiel können Kunden vorhandene Bestellungen nicht ändern oder löschen. In einer echten Anwendung benötigen Sie viel Back-End-Logik, um diese Fälle zu behandeln. (Wurde z. B. die Bestellung bereits ausgeliefert?)

Ändern Sie die GetOrders -Methode so, dass nur die Bestellungen zurückgegeben werden, die dem Benutzer gehören:

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

Ändern Sie die GetOrder -Methode wie folgt:

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

Hier sind die Änderungen, die wir an der -Methode vorgenommen haben:

  • Der Rückgabewert ist ein OrderDTO instance anstelle eines Order.
  • Wenn wir die Datenbank nach der Reihenfolge abfragen, verwenden wir die DbQuery.Include-Methode , um die zugehörigen OrderDetail Entitäten und Product abzurufen.
  • Das Ergebnis wird mithilfe einer Projektion abgeflacht.

Die HTTP-Antwort enthält ein Array von Produkten mit Mengen:

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

Dieses Format ist für Clients einfacher zu nutzen als das ursprüngliche Objektdiagramm, das geschachtelte Entitäten (Reihenfolge, Details und Produkte) enthält.

Die letzte Methode, die berücksichtigt werden PostOrdersoll. Im Moment benötigt diese Methode eine Order instance. Überlegen Sie jedoch, was passiert, wenn ein Client einen Anforderungstext wie folgt sendet:

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

Dies ist eine gut strukturierte Reihenfolge, und Entity Framework fügt sie gerne in die Datenbank ein. Sie enthält jedoch eine Product-Entität, die zuvor nicht vorhanden war. Der Kunde hat gerade ein neues Produkt in unserer Datenbank erstellt! Dies wird eine Überraschung für die Auftragsabwicklungsabteilung sein, wenn sie eine Bestellung für Koalabären sehen. Die Moral ist, seien Sie wirklich vorsichtig mit den Daten, die Sie in einer POST- oder PUT-Anforderung akzeptieren.

Um dieses Problem zu vermeiden, ändern Sie die PostOrder -Methode so, dass sie eine OrderDTO instance. Verwenden Sie den OrderDTO , um das Orderzu erstellen.

var order = new Order()
{
    Customer = User.Identity.Name,
    OrderDetails = (from item in dto.Details select new OrderDetail() 
        { ProductId = item.ProductID, Quantity = item.Quantity }).ToList()
};

Beachten Sie, dass wir die ProductID Eigenschaften und Quantity verwenden, und wir ignorieren alle Werte, die der Client entweder für den Produktnamen oder den Preis gesendet hat. Wenn die Produkt-ID ungültig ist, verstößt sie gegen die Fremdschlüsseleinschränkung in der Datenbank, und der Einfügevorgang schlägt wie erforderlich fehl.

Hier ist die vollständige PostOrder Methode:

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

Fügen Sie abschließend dem Controller das Attribut Authorize hinzu:

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

Jetzt können nur registrierte Benutzer Bestellungen erstellen oder anzeigen.