Partie 6 : Création de contrôleurs de produit et de commande

par Rick Anderson

Télécharger le projet terminé

Ajouter un contrôleur products

Le contrôleur Administration est destiné aux utilisateurs disposant de privilèges d’administrateur. Les clients, en revanche, peuvent afficher des produits, mais ne peuvent pas les créer, les mettre à jour ou les supprimer.

Nous pouvons facilement restreindre l’accès aux méthodes Post, Put et Delete, tout en laissant les méthodes Get ouvertes. Mais examinez les données retournées pour un produit :

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

La ActualCost propriété ne doit pas être visible par les clients ! La solution consiste à définir un objet de transfert de données (DTO) qui inclut un sous-ensemble de propriétés qui doivent être visibles par les clients. Nous allons utiliser LINQ pour projeter Product des instances vers des ProductDTO instances.

Ajoutez une classe nommée ProductDTO au dossier Models.

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

Ajoutez maintenant le contrôleur. Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le dossier Contrôleurs. Sélectionnez Ajouter, puis contrôleur. Dans la boîte de dialogue Ajouter un contrôleur , nommez le contrôleur « ProductsController ». Sous Modèle, sélectionnez Contrôleur d’API vide.

Capture d’écran de la boîte de dialogue Ajouter un contrôleur.

Remplacez tout ce qui se trouve dans le fichier source par le code suivant :

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

Le contrôleur utilise toujours le OrdersContext pour interroger la base de données. Mais au lieu de retourner Product directement des instances, nous appelons MapProducts pour les projeter sur ProductDTO des instances :

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

La MapProducts méthode retourne un IQueryable, afin que nous puissions composer le résultat avec d’autres paramètres de requête. Vous pouvez le voir dans la GetProduct méthode , qui ajoute une clause where à la requête :

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

Ajouter un contrôleur Orders

Ensuite, ajoutez un contrôleur qui permet aux utilisateurs de créer et d’afficher des commandes.

Nous allons commencer par un autre DTO. Dans Explorateur de solutions, cliquez avec le bouton droit sur le dossier Models et ajoutez une classe nommée OrderDTO Utiliser l’implémentation suivante :

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

Ajoutez maintenant le contrôleur. Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le dossier Contrôleurs. Sélectionnez Ajouter, puis contrôleur. Dans la boîte de dialogue Ajouter un contrôleur , définissez les options suivantes :

  • Sous Nom du contrôleur, entrez « OrdersController ».
  • Sous Modèle, sélectionnez « Contrôleur d’API avec des actions de lecture/écriture, à l’aide d’Entity Framework ».
  • Sous Classe de modèle, sélectionnez « Order (ProductStore.Models) ».
  • Sous Classe de contexte de données, sélectionnez « OrdersContext (ProductStore.Models) ».

Capture d’écran de la boîte de dialogue Ajouter un contrôleur. OrdersController est écrit dans la zone de texte.

Cliquez sur Add. Cela ajoute un fichier nommé OrdersController.cs. Ensuite, nous devons modifier l’implémentation par défaut du contrôleur.

Tout d’abord, supprimez les PutOrder méthodes et DeleteOrder . Pour cet exemple, les clients ne peuvent pas modifier ou supprimer des commandes existantes. Dans une application réelle, vous avez besoin de nombreuses logiques back-end pour gérer ces cas. (Par exemple, la commande a-t-elle déjà été expédiée ?)

Modifiez la GetOrders méthode pour retourner uniquement les commandes qui appartiennent à l’utilisateur :

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

Modifiez la GetOrder méthode comme suit :

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

Voici les modifications que nous avons apportées à la méthode :

  • La valeur de retour est un OrderDTO instance, au lieu d’un Order.
  • Lorsque nous interrogeons la base de données pour l’ordre, nous utilisons la méthode DbQuery.Include pour extraire les entités et Product associéesOrderDetail.
  • Nous aplatissez le résultat à l’aide d’une projection.

La réponse HTTP contient un tableau de produits avec des quantités :

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

Ce format est plus facile à utiliser pour les clients que le graphique d’objet d’origine, qui contient des entités imbriquées (commande, détails et produits).

Dernière méthode à prendre en compte PostOrder. Pour l’instant, cette méthode prend une Order instance. Mais pensez à ce qui se passe si un client envoie un corps de requête comme suit :

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

Il s’agit d’un ordre bien structuré, et Entity Framework l’insérera avec plaisir dans la base de données. Mais il contient une entité Product qui n’existait pas auparavant. Le client vient de créer un nouveau produit dans notre base de données ! Ce sera une surprise pour le service de traitement des commandes, quand ils voient une commande pour les ours koala. La morale est, soyez très prudent sur les données que vous acceptez dans une demande POST ou PUT.

Pour éviter ce problème, modifiez la PostOrder méthode pour prendre un OrderDTO instance. Utilisez pour OrderDTO créer le 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()
};

Notez que nous utilisons les ProductID propriétés et Quantity et que nous ignorons toutes les valeurs que le client a envoyées pour le nom ou le prix du produit. Si l’ID de produit n’est pas valide, il enfreint la contrainte de clé étrangère dans la base de données et l’insertion échoue, comme il se doit.

Voici la méthode complète PostOrder :

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

Enfin, ajoutez l’attribut Authorize au contrôleur :

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

Désormais, seuls les utilisateurs inscrits peuvent créer ou afficher des commandes.