在 OData v4 中使用 Web API 2.2 的內含項目
演講者:Jinfu Tan
一般來說,只有封裝在實體集中的實體才能被存取。 但 OData v4 提供了兩個額外的選項:Singleton 和 Containment,WebAPI 2.2 都支援這兩個選項。
本主題將示範如何在 WebApi 2.2 的 OData 端點中定義內含項目。 有關內含項目的更多資訊,請參閱「OData v4 附帶的內含項目」。 若要在 Web API 中建立 OData V4 端點,請參閱「使用 ASP.NET Web API 2.2 建立 OData v4 端點」。
首先,我們將使用下列資料模型在 OData 服務中建立一個內含項目網域模型:
一個帳戶包含許多 PaymentInstruments (PI),但我們沒有為 PI 定義實體集。 相反,PI 只能透過帳戶存取。
定義資料模型
定義 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
屬性用於包含導覽屬性。根據 CLR 類型產生 EDM 模型。
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(); }
如果
Contained
屬性新增至對應的導覽屬性,則ODataConventionModelBuilder
將處理建置 EDM 模型。 如果屬性是集合類型,也會建立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
。
感謝 Leo Hu 提供本文原創內容。