ASP.NET Web API 2.2 Kullanarak OData v4'te Varlık İlişkileri

tarafından Mike Wasson

Ç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 öğreticide, ASP.NET Web API'sini kullanarak OData v4'te bu işlemlerin nasıl destek istediğiniz gösterilmektedir. Öğretici, ASP.NET Web API 2 Kullanarak OData v4 Uç Noktası Oluşturma öğreticisini temel alır.

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

  • Web API 2.1
  • OData v4
  • Visual Studio 2017 (Visual Studio 2017'i buradan indirin)
  • Entity Framework 6
  • .NET 4.5

Öğretici sürümleri

OData Sürüm 3 için bkz. OData v3'te Varlık İlişkilerini Destekleme.

Sağlayıcı Varlığı Ekleme

Uyarı

Öğretici, ASP.NET Web API 2 Kullanarak OData v4 Uç Noktası Oluşturma öğreticisini temel alır.

İlk olarak, ilgili bir varlığa ihtiyacımız var. Models klasörüne adlı Supplier bir sınıf ekleyin.

using System.Collections.Generic;

namespace ProductService.Models
{
    public class Supplier
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public ICollection<Product> Products { get; set; }
    }
}

Sınıfına Product bir gezinti özelliği ekleyin:

using System.ComponentModel.DataAnnotations.Schema;

namespace ProductService.Models
{
    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 int? SupplierId { get; set; }
        public virtual Supplier Supplier { get; set; }
    }
}

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

public class ProductsContext : DbContext
{
    static ProductsContext()
    {
        Database.SetInitializer(new ProductInitializer());
    }

    public DbSet<Product> Products { get; set; }
    // New code:
    public DbSet<Supplier> Suppliers { get; set; }
}

WebApiConfig.cs'da varlık veri modeline bir "Sağlayıcılar" varlık kümesi ekleyin:

public static void Register(HttpConfiguration config)
{
    ODataModelBuilder builder = new ODataConventionModelBuilder();
    builder.EntitySet<Product>("Products");
    // New code:
    builder.EntitySet<Supplier>("Suppliers");
    config.MapODataServiceRoute("ODataRoute", null, builder.GetEdmModel());
}

Sağlayıcı Denetleyicisi Ekleme

Denetleyiciler klasörüne bir SuppliersController sınıf ekleyin.

using ProductService.Models;
using System.Linq;
using System.Web.OData;

namespace ProductService.Controllers
{
    public class SuppliersController : ODataController
    {
        ProductsContext db = new ProductsContext();

        protected override void Dispose(bool disposing)
        {
            db.Dispose();
            base.Dispose(disposing);
        }
    }
}

Bu denetleyici için CRUD işlemlerinin nasıl ekleneceğini göstermeyeceğim. Adımlar Ürünler denetleyicisiyle aynıdır (bkz. OData v4 Uç Noktası Oluşturma).

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

GET /Products(1)/Supplier

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

public class ProductsController : ODataController
{
    // GET /Products(1)/Supplier
    [EnableQuery]
    public SingleResult<Supplier> GetSupplier([FromODataUri] int key)
    {
        var result = db.Products.Where(m => m.Id == key).Select(m => m.Supplier);
        return SingleResult.Create(result);
    }
 
   // Other controller methods not shown.
}

Bu yöntem varsayılan adlandırma kuralını kullanır

  • Yöntem adı: GetX, burada X gezinti özelliğidir.
  • Parametre adı: anahtar

Bu adlandırma kuralını izlerseniz, Web API'si HTTP isteğini otomatik olarak denetleyici yöntemiyle eşler.

Örnek HTTP isteği:

GET http://myproductservice.example.com/Products(1)/Supplier HTTP/1.1
User-Agent: Fiddler
Host: myproductservice.example.com

Örnek HTTP yanıtı:

HTTP/1.1 200 OK
Content-Length: 125
Content-Type: application/json; odata.metadata=minimal; odata.streaming=true
Server: Microsoft-IIS/8.0
OData-Version: 4.0
Date: Tue, 08 Jul 2014 00:44:27 GMT

{
  "@odata.context":"http://myproductservice.example.com/$metadata#Suppliers/$entity","Id":2,"Name":"Wingtip Toys"
}

Önceki örnekte, bir ürünün tek bir tedarikçisi vardır. Gezinti özelliği bir koleksiyon da döndürebilir. Aşağıdaki kod bir sağlayıcının ürünlerini alır:

public class SuppliersController : ODataController
{
    // GET /Suppliers(1)/Products
    [EnableQuery]
    public IQueryable<Product> GetProducts([FromODataUri] int key)
    {
        return db.Suppliers.Where(m => m.Id.Equals(key)).SelectMany(m => m.Products);
    }

    // Other controller methods not shown.
}

Bu durumda, yöntem bir IQueryable döndürür, SingleResult<T> yerine

Örnek HTTP isteği:

GET http://myproductservice.example.com/Suppliers(2)/Products HTTP/1.1
User-Agent: Fiddler
Host: myproductservice.example.com

Örnek HTTP yanıtı:

HTTP/1.1 200 OK
Content-Length: 372
Content-Type: application/json; odata.metadata=minimal; odata.streaming=true
Server: Microsoft-IIS/8.0
OData-Version: 4.0
Date: Tue, 08 Jul 2014 01:06:54 GMT

{
  "@odata.context":"http://myproductservice.example.com/$metadata#Products","value":[
    {
      "Id":1,"Name":"Hat","Price":14.95,"Category":"Clothing","SupplierId":2
    },{
      "Id":2,"Name":"Socks","Price":6.95,"Category":"Clothing","SupplierId":2
    },{
      "Id":4,"Name":"Pogo Stick","Price":29.99,"Category":"Toys","SupplierId":2
    }
  ]
}

Varlıklar Arasında İlişki Oluşturma

OData, mevcut iki varlık arasında ilişki oluşturmayı veya kaldırmayı destekler. OData v4 terminolojisinde, ilişki bir "referanstır". (OData v3'te ilişkiye bağlantı adı verilirdi. Bu öğreticide protokol farklılıkları önemli değildir.)

Başvurunun kendi URI'si vardır ve biçimi /Entity/NavigationProperty/$ref şeklindedir. Örneğin, bir ürün ile tedarikçisi arasındaki başvuruyu ele almak için URI aşağıda verilmiştir:

http:/host/Products(1)/Supplier/$ref

İlişki eklemek için istemci bu adrese bir POST veya PUT isteği gönderir.

  • Gezinti özelliği Product.Supplier gibi tek bir varlıksa PUT.
  • Gezinti özelliği gibi Supplier.Productsbir koleksiyonsa POST

İsteğin gövdesi, ilişkideki diğer varlığın URI'sini içerir. Örnek bir istek aşağıda verilmiştir:

PUT http://myproductservice.example.com/Products(6)/Supplier/$ref HTTP/1.1
OData-Version: 4.0;NetFx
OData-MaxVersion: 4.0;NetFx
Accept: application/json;odata.metadata=minimal
Accept-Charset: UTF-8
Content-Type: application/json;odata.metadata=minimal
User-Agent: Microsoft ADO.NET Data Services
Host: myproductservice.example.com
Content-Length: 70
Expect: 100-continue

{"@odata.id":"http://myproductservice.example.com/Suppliers(4)"}

Bu örnekte istemci, kimliği = 6 olan ürünün $ref URI'si /Products(6)/Supplier/$ref olan öğesine bir PUT isteği Suppliergönderir. İstek başarılı olursa, sunucu bir 204 (İçerik Yok) yanıtı gönderir:

HTTP/1.1 204 No Content
Server: Microsoft-IIS/8.0
Date: Tue, 08 Jul 2014 06:35:59 GMT

bir ilişki Producteklemek için denetleyici yöntemi aşağıdadır:

public class ProductsController : ODataController
{
    [AcceptVerbs("POST", "PUT")]
    public async Task<IHttpActionResult> CreateRef([FromODataUri] int key, 
        string navigationProperty, [FromBody] Uri link)
    {
        var product = await db.Products.SingleOrDefaultAsync(p => p.Id == key);
        if (product == null)
        {
            return NotFound();
        }
        switch (navigationProperty)
        {
            case "Supplier":
                // Note: The code for GetKeyFromUri is shown later in this topic.
                var relatedKey = Helpers.GetKeyFromUri<int>(Request, link);
                var supplier = await db.Suppliers.SingleOrDefaultAsync(f => f.Id == relatedKey);
                if (supplier == null)
                {
                    return NotFound();
                }

                product.Supplier = supplier;
                break;

            default:
                return StatusCode(HttpStatusCode.NotImplemented);
        }
        await db.SaveChangesAsync();
        return StatusCode(HttpStatusCode.NoContent);
    }

    // Other controller methods not shown.
}

navigationProperty parametresi, ayarlanacağı ilişkiyi belirtir. (Varlıkta birden fazla navigasyon özelliği varsa, daha fazla case deyim ekleyebilirsiniz.)

Bağlantı parametresi, sağlayıcının URI'sini içerir. Web API'si bu parametrenin değerini almak için istek gövdesini otomatik olarak ayrıştırır.

Sağlayıcıyı aramak için bağlantı parametresinin bir parçası olan kimliğine (veya anahtarına) ihtiyacımız vardır. Bunu yapmak için aşağıdaki yardımcı yöntemi kullanın:

using Microsoft.OData.Core;
using Microsoft.OData.Core.UriParser;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web.Http.Routing;
using System.Web.OData.Extensions;
using System.Web.OData.Routing;

namespace ProductService
{
    public static class Helpers
    {
        public static TKey GetKeyFromUri<TKey>(HttpRequestMessage request, Uri uri)
        {
            if (uri == null)
            {
                throw new ArgumentNullException("uri");
            }

            var urlHelper = request.GetUrlHelper() ?? new UrlHelper(request);

            string serviceRoot = urlHelper.CreateODataLink(
                request.ODataProperties().RouteName, 
                request.ODataProperties().PathHandler, new List<ODataPathSegment>());
            var odataPath = request.ODataProperties().PathHandler.Parse(
                request.ODataProperties().Model, 
                serviceRoot, uri.LocalPath);

            var keySegment = odataPath.Segments.OfType<KeyValuePathSegment>().FirstOrDefault();
            if (keySegment == null)
            {
                throw new InvalidOperationException("The link does not contain a key.");
            }

            var value = ODataUriUtils.ConvertFromUriLiteral(keySegment.Value, ODataVersion.V4);
            return (TKey)value;
        }

    }
}

Temel olarak, bu yöntem URI yolunu segmentlere bölmek, anahtarı içeren kesimi bulmak ve anahtarı doğru türe dönüştürmek için OData kitaplığını kullanır.

Varlıklar Arasındaki İlişkiyi Silme

bir ilişkiyi silmek için istemci, $ref URI'sine bir HTTP DELETE isteği gönderir:

DELETE http://host/Products(1)/Supplier/$ref

Ürün ve Sağlayıcı arasındaki ilişkiyi silmek için denetleyici yöntemi aşağıdadır:

public class ProductsController : ODataController
{
    public async Task<IHttpActionResult> DeleteRef([FromODataUri] int key, 
        string navigationProperty, [FromBody] Uri link)
    {
        var product = db.Products.SingleOrDefault(p => p.Id == key);
        if (product == null)
        {
            return NotFound();
        }

        switch (navigationProperty)
        {
            case "Supplier":
                product.Supplier = null;
                break;

            default:
                return StatusCode(HttpStatusCode.NotImplemented);
        }
        await db.SaveChangesAsync();

        return StatusCode(HttpStatusCode.NoContent);
    }        

    // Other controller methods not shown.
}

Bu durumda, Product.Supplier 1-çok ilişkisinin "1" ucudur, bu yüzden ilişkiyi Product.Supplier'i null olarak ayarlayarak kaldırabilirsiniz.

İlişkinin "birden" ucunda, istemci hangi ilişkili nesnenin kaldırılacağını belirtmelidir. Bunu yapmak için istemci, isteğin sorgu dizesinde ilgili varlığın URI'sini gönderir. Örneğin, "Tedarikçi 1"den "Ürün 1" öğesini kaldırmak için:

DELETE http://host/Suppliers(1)/Products/$ref?$id=http://host/Products(1)

Web API'sinde bunu desteklemek için yöntemine ek bir parametre DeleteRef eklememiz gerekir. Bir ürünü Supplier.Products ilişkiden silmek için kontrolör yöntemi aşağıdadır.

public class SuppliersController : ODataController
{
    public async Task<IHttpActionResult> DeleteRef([FromODataUri] int key, 
        [FromODataUri] string relatedKey, string navigationProperty)
    {
        var supplier = await db.Suppliers.SingleOrDefaultAsync(p => p.Id == key);
        if (supplier == null)
        {
            return StatusCode(HttpStatusCode.NotFound);
        }

        switch (navigationProperty)
        {
            case "Products":
                var productId = Convert.ToInt32(relatedKey);
                var product = await db.Products.SingleOrDefaultAsync(p => p.Id == productId);

                if (product == null)
                {
                    return NotFound();
                }
                product.Supplier = null;
                break;
            default:
                return StatusCode(HttpStatusCode.NotImplemented);

        }
        await db.SaveChangesAsync();

        return StatusCode(HttpStatusCode.NoContent);
    }

    // Other controller methods not shown.
}

Anahtar parametresi sağlayıcının anahtarıdır ve ilgiliAnahtar parametresi, ürünü ilişkiden Products kaldırmak için kullanılan anahtardır. Web API'sinin anahtarı sorgu dizesinden otomatik olarak aldığını unutmayın.