Contenção no OData v4 usando a API Web 2.2
por Jinfu Tan
Tradicionalmente, uma entidade só poderia ser acessada se fosse encapsulada dentro de um conjunto de entidades. Mas o OData v4 fornece duas opções adicionais, Singleton e Containment, ambas compatíveis com o WebAPI 2.2.
Este tópico mostra como definir uma contenção em um ponto de extremidade OData no WebApi 2.2. Para obter mais informações sobre a contenção, consulte Containment está chegando com OData v4. Para criar um ponto de extremidade OData V4 na API Web, consulte Criar um ponto de extremidade OData v4 usando ASP.NET Web API 2.2.
Primeiro, criaremos um modelo de domínio de contenção no serviço OData, usando este modelo de dados:
Uma conta contém muitos PI (PaymentInstruments), mas não definimos um conjunto de entidades para um PI. Em vez disso, os PIs só podem ser acessados por meio de uma Conta.
Definindo o modelo de dados
Defina os tipos 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; } }
O
Contained
atributo é usado para propriedades de navegação de contenção.Gere o modelo EDM com base nos tipos 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(); }
O
ODataConventionModelBuilder
manipulará a criação do modelo EDM se oContained
atributo for adicionado à propriedade de navegação correspondente. Se a propriedade for um tipo de coleção, umaGetCount(string NameContains)
função também será criada.Os metadados gerados serão semelhantes aos seguintes:
<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>
O
ContainsTarget
atributo indica que a propriedade de navegação é uma contenção.
Definir o controlador de conjunto de entidades que contém
As entidades contidas não têm seu próprio controlador; a ação é definida no controlador do conjunto de entidades que contém. Neste exemplo, há um AccountsController, mas nenhum 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;
}
}
Se o caminho OData for 4 ou mais segmentos, somente o roteamento de atributo funcionará, como [ODataRoute("Accounts({accountId})/PayinPIs({paymentInstrumentId})")]
no controlador acima. Caso contrário, o atributo e o roteamento convencional funcionarão: por exemplo, GetPayInPIs(int key)
corresponde GET ~/Accounts(1)/PayinPIs
a .
Obrigado a Leo Hu pelo conteúdo original deste artigo.