Actions et fonctions dans OData v4 à l’aide de API Web ASP.NET 2.2

par Mike Wasson

Dans OData, les actions et les fonctions permettent d’ajouter des comportements côté serveur qui ne sont pas facilement définis comme des opérations CRUD sur des entités. Ce tutoriel montre comment ajouter des actions et des fonctions à un point de terminaison OData v4 à l’aide de l’API Web 2.2. Le tutoriel s’appuie sur le didacticiel Créer un point de terminaison OData v4 à l’aide de API Web ASP.NET 2

Versions logicielles utilisées dans le tutoriel

  • API web 2.2
  • OData v4
  • Visual Studio 2013 (téléchargez Visual Studio 2017 ici)
  • .NET 4.5

Versions du didacticiel

Pour OData version 3, consultez Actions OData dans API Web ASP.NET 2.

La différence entre les actions et les fonctions est que les actions peuvent avoir des effets secondaires, et les fonctions ne le font pas. Les actions et les fonctions peuvent retourner des données. Voici quelques utilisations des actions :

  • Transactions complexes.
  • Manipulation de plusieurs entités à la fois.
  • Autorisation des mises à jour uniquement pour certaines propriétés d’une entité.
  • Envoi de données qui ne sont pas une entité.

Les fonctions sont utiles pour retourner des informations qui ne correspondent pas directement à une entité ou à une collection.

Une action (ou une fonction) peut cibler une seule entité ou une collection. Dans la terminologie OData, il s’agit de la liaison. Vous pouvez également avoir des actions/fonctions « non liées », qui sont appelées en tant qu’opérations statiques sur le service.

Exemple : Ajout d’une action

Définissons une action pour évaluer un produit.

Remarque

Ce tutoriel s’appuie sur le tutoriel Créer un point de terminaison OData v4 à l’aide de API Web ASP.NET 2

Tout d’abord, ajoutez un ProductRating modèle pour représenter les évaluations.

namespace ProductService.Models
{
    public class ProductRating
    {
        public int ID { get; set; }
        public int Rating { get; set; }
        public int ProductID { get; set; }
        public virtual Product Product { get; set; }  
    }
}

Ajoutez également un DbSet à la ProductsContext classe, afin qu’EF crée une table Ratings dans la base de données.

public class ProductsContext : DbContext
{
    public ProductsContext() 
            : base("name=ProductsContext")
    {
    }

    public DbSet<Product> Products { get; set; }
    public DbSet<Supplier> Suppliers { get; set; }
    // New code:
    public DbSet<ProductRating> Ratings { get; set; }
}

Ajouter l’action à l’EDM

Dans WebApiConfig.cs, ajoutez le code suivant :

ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Products");

// New code:
builder.Namespace = "ProductService";
builder.EntityType<Product>()
    .Action("Rate")
    .Parameter<int>("Rating");

La méthode EntityTypeConfiguration.Action ajoute une action au modèle de données d’entité (EDM). La méthode Parameter spécifie un paramètre typé pour l’action.

Ce code définit également l’espace de noms pour l’EDM. L’espace de noms est important, car l’URI de l’action inclut le nom complet de l’action :

http://localhost/Products(1)/ProductService.Rate

Remarque

Dans une configuration IIS classique, le point dans cette URL entraîne le retour de l’erreur 404 par IIS. Vous pouvez résoudre ce problème en ajoutant la section suivante à votre fichier Web.Config :

<system.webServer>
    <handlers>
      <clear/>
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="/*" 
          verb="*" type="System.Web.Handlers.TransferRequestHandler" 
          preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
</system.webServer>

Ajouter une méthode de contrôleur pour l’action

Pour activer l’action « Débit », ajoutez la méthode suivante à ProductsController:

[HttpPost]
public async Task<IHttpActionResult> Rate([FromODataUri] int key, ODataActionParameters parameters)
{
    if (!ModelState.IsValid)
    {
        return BadRequest();
    }

    int rating = (int)parameters["Rating"];
    db.Ratings.Add(new ProductRating
    {
        ProductID = key,
        Rating = rating
    });

    try
    {
        await db.SaveChangesAsync();
    }
    catch (DbUpdateException e)
    {
        if (!ProductExists(key))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return StatusCode(HttpStatusCode.NoContent);
}

Notez que le nom de la méthode correspond au nom de l’action. L’attribut [HttpPost] spécifie que la méthode est une méthode HTTP POST.

Pour appeler l’action, le client envoie une requête HTTP POST comme suit :

POST http://localhost/Products(1)/ProductService.Rate HTTP/1.1
Content-Type: application/json
Content-Length: 12

{"Rating":5}

L’action « Débit » étant liée aux instances product, l’URI de l’action est le nom complet de l’action ajouté à l’URI d’entité. (Rappelez-vous que nous définissons l’espace de noms EDM sur « ProductService », de sorte que le nom complet de l’action est « ProductService.Rate ». )

Le corps de la requête contient les paramètres d’action sous forme de charge utile JSON. L’API web convertit automatiquement la charge utile JSON en objet ODataActionParameters , qui n’est qu’un dictionnaire de valeurs de paramètres. Utilisez ce dictionnaire pour accéder aux paramètres de votre méthode de contrôleur.

Si le client envoie les paramètres d’action dans un format incorrect, la valeur de ModelState.IsValid est false. Vérifiez cet indicateur dans votre méthode de contrôleur et retournez une erreur si IsValid est false.

if (!ModelState.IsValid)
{
    return BadRequest();
}

Exemple : ajout d’une fonction

Nous allons maintenant ajouter une fonction OData qui retourne le produit le plus coûteux. Comme précédemment, la première étape consiste à ajouter la fonction à l’EDM. Dans WebApiConfig.cs, ajoutez le code suivant.

ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Products");
builder.EntitySet<Supplier>("Suppliers");

// New code:
builder.Namespace = "ProductService";
builder.EntityType<Product>().Collection
    .Function("MostExpensive")
    .Returns<double>();

Dans ce cas, la fonction est liée à la collection Products, plutôt qu’à des instances Product individuelles. Les clients appellent la fonction en envoyant une requête GET :

GET http://localhost:38479/Products/ProductService.MostExpensive

Voici la méthode de contrôleur pour cette fonction :

public class ProductsController : ODataController
{
    [HttpGet]
    public IHttpActionResult MostExpensive()
    {
        var product = db.Products.Max(x => x.Price);
        return Ok(product);
    }

    // Other controller methods not shown.
}

Notez que le nom de la méthode correspond au nom de la fonction. L’attribut [HttpGet] spécifie que la méthode est une méthode HTTP GET.

Voici la réponse HTTP :

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal; odata.streaming=true
OData-Version: 4.0
Date: Sat, 28 Jun 2014 00:44:07 GMT
Content-Length: 85

{
  "@odata.context":"http://localhost:38479/$metadata#Edm.Decimal","value":50.00
}

Exemple : ajout d’une fonction non lié

L’exemple précédent était une fonction liée à une collection. Dans cet exemple suivant, nous allons créer une fonction non lié . Les fonctions non liées sont appelées en tant qu’opérations statiques sur le service. Dans cet exemple, la fonction retourne la taxe de vente pour un code postal donné.

Dans le fichier WebApiConfig, ajoutez la fonction à l’EDM :

ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Products");

// New code:
builder.Function("GetSalesTaxRate")
    .Returns<double>()
    .Parameter<int>("PostalCode");

Notez que nous appelons Function directement sur le ODataModelBuilder, au lieu du type d’entité ou de la collection. Cela indique au générateur de modèles que la fonction n’est pas lié.

Voici la méthode de contrôleur qui implémente la fonction :

[HttpGet]
[ODataRoute("GetSalesTaxRate(PostalCode={postalCode})")]
public IHttpActionResult GetSalesTaxRate([FromODataUri] int postalCode)
{
    double rate = 5.6;  // Use a fake number for the sample.
    return Ok(rate);
}

Peu importe le contrôleur d’API web dans lequel vous placez cette méthode. Vous pouvez le placer dans ProductsControllerou définir un contrôleur distinct. L’attribut [ODataRoute] définit le modèle d’URI pour la fonction .

Voici un exemple de demande cliente :

GET http://localhost:38479/GetSalesTaxRate(PostalCode=10) HTTP/1.1

Réponse HTTP :

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal; odata.streaming=true
OData-Version: 4.0
Date: Sat, 28 Jun 2014 01:05:32 GMT
Content-Length: 82

{
  "@odata.context":"http://localhost:38479/$metadata#Edm.Double","value":5.6
}