Web API 2 ile OData v3'te Varlık İlişkilerini Destekleme

tarafından Mike Wasson

Tamamlanan Projeyi İndir

Çoğu veri kümesi varlıklar arasındaki ilişkileri tanımlar: Müşterilerin siparişleri vardır; kitapların yazarları vardır; ürünlerin tedarikçileri vardır. İstemciler OData kullanarak varlık ilişkileri üzerinde gezinebilir. Bir ürün verildiğinde tedarikçiyi bulabilirsiniz. ayrıca ilişkileri oluşturabilir veya kaldırabilirsiniz. Örneğin, bir ürünün tedarikçisini ayarlayabilirsiniz.

Bu öğretici, ASP.NET Web API'de bu işlemlerin nasıl destekleneceğini göstermektedir. Öğretici, Web API 2 ile OData v3 Uç Noktası Oluşturma öğreticisini temel alır.

Öğreticide kullanılan yazılım sürümleri

  • Web API 2
  • OData Sürüm 3
  • Entity Framework 6

Sağlayıcı Varlığı Ekleme

İlk olarak OData akışımıza yeni bir varlık türü eklememiz gerekir. Bir sınıf ekleyeceğiz Supplier .

using System.ComponentModel.DataAnnotations;

namespace ProductService.Models
{
    public class Supplier
    {
        [Key]
        public string Key { get; set; }
        public string Name { get; set; }
    }
}

Bu sınıf, varlık anahtarı için bir dize kullanır. Pratikte bu, tamsayı anahtarı kullanmaktan daha az yaygın olabilir. Ancak OData'nın tamsayıların yanı sıra diğer anahtar türlerini nasıl işlediğini de görmeye değer.

Ardından, Product sınıfına Supplier özelliği ekleyerek bir ilişki oluşturacağız:

public class Product
{
    public int ID { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public string Category { get; set; }

    // New code
    [ForeignKey("Supplier")]
    public string SupplierId { get; set; }
    public virtual Supplier Supplier { get; set; }
}

Entity Framework'ün tabloyu veritabanına dahil edebilmesi için sınıfına ProductServiceContext yeni bir Supplier ekleyin.

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

    public System.Data.Entity.DbSet<ProductService.Models.Product> Products { get; set; }
    // New code:
    public System.Data.Entity.DbSet<ProductService.Models.Supplier> Suppliers { get; set; }
}

WebApiConfig.cs'da EDM modeline bir "Tedarikçiler" varlığı ekleyin:

ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Products");
// New code:
builder.EntitySet<Supplier>("Suppliers");

Bir ürünün sağlayıcısını almak için istemci bir GET isteği gönderir:

GET /Products(1)/Supplier

Burada "Supplier", Product türünün bir gezinti özelliğidir. Bu durumda, Supplier tek bir öğeye başvurur, ancak gezinti özelliği bir koleksiyon da döndürebilir (bire çok veya çoka çok ilişkisi).

Bu isteği desteklemek için sınıfına aşağıdaki yöntemi ProductsController ekleyin:

// GET /Products(1)/Supplier
public Supplier GetSupplier([FromODataUri] int key)
{
    Product product = _context.Products.FirstOrDefault(p => p.ID == key);
    if (product == null)
    {
        throw new HttpResponseException(HttpStatusCode.NotFound);
    }
    return product.Supplier;
}

Anahtar parametresi, ürünün anahtarıdır. yöntemi, ilgili varlığı (bu örnekte bir Supplier örneği) döndürür. Yöntem adı ve parametre adı önemlidir. Genel olarak, gezinti özelliği "X" olarak adlandırılmışsa, "GetX" adlı bir yöntem eklemeniz gerekir. yöntem, ebeveynin anahtarının veri türüyle eşleşen "key" adlı bir parametre almalıdır.

[FromOdataUri] özniteliğinin anahtar parametresine eklenmesi de önemlidir. Bu öznitelik, anahtarı istek URI'sinden ayrıştırdığında Web API'sine OData söz dizimi kurallarını kullanmasını bildirir.

OData, iki varlık arasındaki ilişkilerin oluşturulmasını veya kaldırılmasını destekler. OData terminolojisinde ilişki bir "bağlantıdır." Her bağlantı, form varlığı/$links/varlık ile bir URI'ye sahiptir. Örneğin, üründen sağlayıcıya bağlantı şu şekilde görünür:

/Products(1)/$links/Supplier

Yeni bir bağlantı oluşturmak için istemci, bağlantı URI'sine bir POST isteği gönderir. İsteğin gövdesi, hedef varlığın URI'sini oluşturur. Örneğin, "CTSO" anahtarına sahip bir sağlayıcı olduğunu varsayalım. "Product(1)" ile "Supplier('CTSO')" bağlantısı oluşturmak için istemci aşağıdakine benzer bir istek gönderir:

POST http://localhost/odata/Products(1)/$links/Supplier
Content-Type: application/json
Content-Length: 50

{"url":"http://localhost/odata/Suppliers('CTSO')"}

Bir bağlantıyı silmek için, istemci bağlantı URI'sine bir DELETE isteği gönderir.

Bağlantı Oluşturma

İstemcinin ürün sağlayıcısı bağlantıları oluşturmasını sağlamak için sınıfına ProductsController aşağıdaki kodu ekleyin:

[AcceptVerbs("POST", "PUT")]
public async Task<IHttpActionResult> CreateLink([FromODataUri] int key, string navigationProperty, [FromBody] Uri link)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
            
    Product product = await db.Products.FindAsync(key);
    if (product == null)
    {
        return NotFound();
    }
            
    switch (navigationProperty)
    {
        case "Supplier":
            string supplierKey = GetKeyFromLinkUri<string>(link);
            Supplier supplier = await db.Suppliers.FindAsync(supplierKey);
            if (supplier == null)
            {
                return NotFound();
            }
            product.Supplier = supplier;
            await db.SaveChangesAsync();
            return StatusCode(HttpStatusCode.NoContent);

        default:
            return NotFound();
    }
}

Bu yöntem üç parametre alır:

  • key: Üst varlığın anahtarı (ürün)
  • navigationProperty: Gezinti özelliğinin adı. Bu örnekte, tek geçerli gezinti özelliği "Supplier" şeklindedir.
  • bağlantı: İlgili varlığın OData URI'sini. Bu değer istek gövdesinden alınır. Örneğin, bağlantı URI'si "http://localhost/odata/Suppliers('CTSO'), kimlik = 'CTSO' olan sağlayıcı anlamına gelir.

Yöntem, sağlayıcıyı aramak için bağlantı kullanır. Eşleşen sağlayıcı bulunursa yöntemi özelliği ayarlar Product.Supplier ve sonucu veritabanına kaydeder.

En zor kısmı bağlantı URI'sini ayrıştırmadır. Temel olarak, bu URI'ye bir GET isteği göndermenin sonucunu simüle etmeniz gerekir. Aşağıdaki yardımcı yöntem bunun nasıl yapılacağını gösterir. yöntemi, Web API yönlendirme işlemini çağırır ve ayrıştırılan OData yolunu temsil eden bir ODataPath örneğini geri alır. Bağlantı URI'sinde segmentlerden biri varlık anahtarı olmalıdır. (Aksi takdirde, istemci hatalı bir URI gönderdi.)

// Helper method to extract the key from an OData link URI.
private TKey GetKeyFromLinkUri<TKey>(Uri link)
{
    TKey key = default(TKey);

    // Get the route that was used for this request.
    IHttpRoute route = Request.GetRouteData().Route;

    // Create an equivalent self-hosted route. 
    IHttpRoute newRoute = new HttpRoute(route.RouteTemplate, 
        new HttpRouteValueDictionary(route.Defaults), 
        new HttpRouteValueDictionary(route.Constraints),
        new HttpRouteValueDictionary(route.DataTokens), route.Handler);

    // Create a fake GET request for the link URI.
    var tmpRequest = new HttpRequestMessage(HttpMethod.Get, link);

    // Send this request through the routing process.
    var routeData = newRoute.GetRouteData(
        Request.GetConfiguration().VirtualPathRoot, tmpRequest);

    // If the GET request matches the route, use the path segments to find the key.
    if (routeData != null)
    {
        ODataPath path = tmpRequest.GetODataPath();
        var segment = path.Segments.OfType<KeyValuePathSegment>().FirstOrDefault();
        if (segment != null)
        {
            // Convert the segment into the key type.
            key = (TKey)ODataUriUtils.ConvertFromUriLiteral(
                segment.Value, ODataVersion.V3);
        }
    }
    return key;
}

Bağlantıları Silme

Bağlantıyı silmek için sınıfına ProductsController aşağıdaki kodu ekleyin:

public async Task<IHttpActionResult> DeleteLink([FromODataUri] int key, string navigationProperty)
{
    Product product = await db.Products.FindAsync(key);
    if (product == null)
    {
        return NotFound();
    }

    switch (navigationProperty)
    {
        case "Supplier":
            product.Supplier = null;
            await db.SaveChangesAsync();
            return StatusCode(HttpStatusCode.NoContent);

        default:
            return NotFound();

    }
}

Bu örnekte gezinti özelliği tek bir Supplier varlıktır. Gezinti özelliği bir koleksiyonsa, bağlantıyı silmek için URI'nin ilgili varlık için bir anahtar içermesi gerekir. Örneğin:

DELETE /odata/Customers(1)/$links/Orders(1)

Bu istek, müşteri 1'den sipariş 1'i kaldırır. Bu durumda DeleteLink yöntemi aşağıdaki imzaya sahip olur:

void DeleteLink([FromODataUri] int key, string relatedKey, string navigationProperty);

relatedKey parametresi, ilgili varlığın anahtarını verir. Bu nedenle yönteminizde DeleteLinkanahtar parametresine göre birincil varlığı arayın, relatedKey parametresine göre ilgili varlığı bulun ve ilişkilendirmeyi kaldırın. Veri modelinize bağlı olarak, her iki sürümünü DeleteLinkde uygulamanız gerekebilir. Web API'sinin istek URI'sine göre doğru sürümü çağırması gerekir.