Поделиться через


Автономное использование в OData версии 4 с использованием веб-API 2.2

Дзинфу Тан

Обычно доступ к сущности можно было получить только в том случае, если она была инкапсулирована в наборе сущностей. Но OData версии 4 предоставляет два дополнительных варианта: Singleton и Containment, которые поддерживает WebAPI 2.2.

В этом разделе показано, как определить вложенность в конечной точке OData в WebApi 2.2. Дополнительные сведения об автономности см. в статье , посвященной включению в OData версии 4. Сведения о создании конечной точки OData версии 4 в веб-API см. в статье Создание конечной точки OData версии 4 с помощью веб-API ASP.NET 2.2.

Сначала мы создадим модель автономной предметной области в службе OData, используя следующую модель данных:

Модель данных

Учетная запись содержит много объектов PaymentInstruments (PI), но мы не определяем набор сущностей для pi. Вместо этого доступ к api-интерфейсам можно получить только через учетную запись.

Определение модели данных

  1. Определите типы среды 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; }     
    }
    

    Атрибут Contained используется для свойств навигации в составе.

  2. Создайте модель EDM на основе типов СРЕДЫ 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();         
    }
    

    будет ODataConventionModelBuilder обрабатывать построение модели EDM, Contained если атрибут добавляется в соответствующее свойство навигации. Если свойство является типом коллекции, GetCount(string NameContains) также будет создана функция.

    Созданные метаданные будут выглядеть следующим образом:

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

    Атрибут ContainsTarget указывает, что свойство навигации является вложенным.

Определение содержащего контроллера набора сущностей

Автономные сущности не имеют собственного контроллера; действие определяется в контроллере набора сущностей. В этом примере имеется AccountsController, но не 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;         
    }     
}

Если путь OData составляет 4 или более сегментов, работает только маршрутизация атрибутов, например [ODataRoute("Accounts({accountId})/PayinPIs({paymentInstrumentId})")] в приведенном выше контроллере. В противном случае работает как атрибут, так и обычная маршрутизация: например, GetPayInPIs(int key) соответствует GET ~/Accounts(1)/PayinPIs.

Спасибо Лео Ху за оригинальное содержание этой статьи.