Confinement dans OData v4 à l’aide de l’API web 2.2

par Jinfu Tan

Traditionnellement, une entité n’était accessible que si elle était encapsulée à l’intérieur d’un jeu d’entités. Mais OData v4 fournit deux options supplémentaires, Singleton et Containment, que WebAPI 2.2 prend en charge.

Cette rubrique montre comment définir un conteneur dans un point de terminaison OData dans WebApi 2.2. Pour plus d’informations sur le confinement, consultez Containment is coming with OData v4. Pour créer un point de terminaison OData V4 dans l’API web, consultez Créer un point de terminaison OData v4 à l’aide de API Web ASP.NET 2.2.

Tout d’abord, nous allons créer un modèle de domaine de confinement dans le service OData, à l’aide de ce modèle de données :

Modèle de données

Un compte contient de nombreux paymentInstruments (PI), mais nous ne définissons pas d’entité définie pour une pi. Au lieu de cela, les API sont accessibles uniquement par le biais d’un compte.

Définition du modèle de données

  1. Définissez les types CLR.

    public class Account     
    {         
        public int AccountID { get; set; }         
        public string Name { get; set; }         
        [Contained]         
        public IList<PaymentInstrument> PayinPIs { get; set; }     
    }     
    
    public class PaymentInstrument     
    {         
        public int PaymentInstrumentID { get; set; }        
        public string FriendlyName { get; set; }     
    }
    

    L’attribut Contained est utilisé pour les propriétés de navigation de confinement.

  2. Générez le modèle EDM en fonction des types CLR.

    public static IEdmModel GetModel()         
    {             
        ODataConventionModelBuilder builder = new ODataConventionModelBuilder();             
        builder.EntitySet<Account>("Accounts");             
        var paymentInstrumentType = builder.EntityType<PaymentInstrument>();             
        var functionConfiguration = 
            paymentInstrumentType.Collection.Function("GetCount");             
        functionConfiguration.Parameter<string>("NameContains");             
        functionConfiguration.Returns<int>();             
        builder.Namespace = typeof(Account).Namespace;             
        return builder.GetEdmModel();         
    }
    

    Le ODataConventionModelBuilder gère la génération du modèle EDM si l’attribut Contained est ajouté à la propriété de navigation correspondante. Si la propriété est un type de collection, une GetCount(string NameContains) fonction est également créée.

    Les métadonnées générées se présentent comme suit :

    <EntityType Name="Account">   
      <Key>     
        <PropertyRef Name="AccountID" />   
      </Key>   
      <Property Name="AccountID" Type="Edm.Int32" Nullable="false" />   
      <Property Name="Name" Type="Edm.String" />   
      <NavigationProperty 
        Name="PayinPIs" 
        Type="Collection(ODataContrainmentSample.PaymentInstrument)" 
        ContainsTarget="true" /> 
    </EntityType>
    

    L’attribut ContainsTarget indique que la propriété de navigation est un conteneur.

Définir le contrôleur de jeu d’entités contenant

Les entités autonomes n’ont pas leur propre contrôleur ; l’action est définie dans le contrôleur du jeu d’entités contenant. Dans cet exemple, il existe un AccountsController, mais aucun PaymentInstrumentsController.

public class AccountsController : ODataController     
{         
    private static IList<Account> _accounts = null;         
    public AccountsController()         
    {             
        if (_accounts == null)             
        {                 
            _accounts = InitAccounts();             
        }         
    }         
    // PUT ~/Accounts(100)/PayinPIs         
    [EnableQuery] 
    public IHttpActionResult GetPayinPIs(int key)         
    {             
        var payinPIs = _accounts.Single(a => a.AccountID == key).PayinPIs;             
        return Ok(payinPIs);         
    }         
    [EnableQuery]         
    [ODataRoute("Accounts({accountId})/PayinPIs({paymentInstrumentId})")]         
    public IHttpActionResult GetSinglePayinPI(int accountId, int paymentInstrumentId)         
    {             
        var payinPIs = _accounts.Single(a => a.AccountID == accountId).PayinPIs;             
        var payinPI = payinPIs.Single(pi => pi.PaymentInstrumentID == paymentInstrumentId);             
        return Ok(payinPI);         
    }         
    // PUT ~/Accounts(100)/PayinPIs(101)         
    [ODataRoute("Accounts({accountId})/PayinPIs({paymentInstrumentId})")]         
    public IHttpActionResult PutToPayinPI(int accountId, int paymentInstrumentId, [FromBody]PaymentInstrument paymentInstrument)         
    {             
        var account = _accounts.Single(a => a.AccountID == accountId);             
        var originalPi = account.PayinPIs.Single(p => p.PaymentInstrumentID == paymentInstrumentId);             
        originalPi.FriendlyName = paymentInstrument.FriendlyName;             
        return Ok(paymentInstrument);         
    }         
    // DELETE ~/Accounts(100)/PayinPIs(101)         
    [ODataRoute("Accounts({accountId})/PayinPIs({paymentInstrumentId})")]         
    public IHttpActionResult DeletePayinPIFromAccount(int accountId, int paymentInstrumentId)         
    {             
        var account = _accounts.Single(a => a.AccountID == accountId);             
        var originalPi = account.PayinPIs.Single(p => p.PaymentInstrumentID == paymentInstrumentId);             
        if (account.PayinPIs.Remove(originalPi))             
        {                 
            return StatusCode(HttpStatusCode.NoContent);             
        }             
        else             
        {                 
            return StatusCode(HttpStatusCode.InternalServerError);             
        }         
    }         
    // GET ~/Accounts(100)/PayinPIs/Namespace.GetCount() 
    [ODataRoute("Accounts({accountId})/PayinPIs/ODataContrainmentSample.GetCount(NameContains={name})")]         
    public IHttpActionResult GetPayinPIsCountWhoseNameContainsGivenValue(int accountId, [FromODataUri]string name)         
    {             
        var account = _accounts.Single(a => a.AccountID == accountId);             
        var count = account.PayinPIs.Where(pi => pi.FriendlyName.Contains(name)).Count();             
        return Ok(count);         
    }         
    private static IList<Account> InitAccounts()         
    {             
        var accounts = new List<Account>() 
        { 
            new Account()                 
            {                    
                AccountID = 100,                    
                Name="Name100",                    
                PayinPIs = new List<PaymentInstrument>()                     
                {                         
                    new PaymentInstrument()                         
                    {                             
                        PaymentInstrumentID = 101,                             
                        FriendlyName = "101 first PI",                         
                    },                         
                    new PaymentInstrument()                         
                    {                             
                        PaymentInstrumentID = 102,                             
                        FriendlyName = "102 second PI",                         
                    },                     
                },                 
            },             
        };            
        return accounts;         
    }     
}

Si le chemin OData est de 4 segments ou plus, seul le routage d’attributs fonctionne, comme [ODataRoute("Accounts({accountId})/PayinPIs({paymentInstrumentId})")] dans le contrôleur ci-dessus. Sinon, l’attribut et le routage conventionnel fonctionnent : pour instance, GetPayInPIs(int key) correspond GET ~/Accounts(1)/PayinPIsà .

Merci à Leo Hu pour le contenu original de cet article.