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. OData kullanarak istemciler varlık ilişkileri üzerinde gezinebilir. Bir ürüne göre 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 adımları 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.

Tedarikçi Varlığı Ekleme

Not

Öğ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 ProductsContext veritabanında Sağlayıcı tablosunu içermesi için sınıfına yeni bir DbSet 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 dosyasında varlık veri modeline bir "Suppliers" 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ün denetleyicisiyle aynıdır (bkz. OData v4 Uç Noktası Oluşturma).

Bir ürünün tedarikçisini 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öntemine 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 sağlayıcısı 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öntemi SingleResult<T> yerine IQueryable döndürür

Ö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 "başvurudur". (OData v3'te ilişkiye bağlantı adı verildi. Bu öğreticide protokol farklılıkları önemli değildir.)

Başvurunun formuyla /Entity/NavigationProperty/$refbirlikte kendi URI'sine sahiptir. Örneğin, bir ürün ile sağlayıcısı arasındaki başvuruyu ele almak için URI aşağıda verilmiştir:

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

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

  • Gezinti özelliği gibi Product.Suppliertek 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 Supplier olan öğesine bir PUT isteği /Products(6)/Supplier/$refgö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 ile 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 gezinti özelliği varsa, daha fazla case deyim ekleyebilirsiniz.)

Bağlantı parametresi tedarikçinin URI'sini içerir. Web API'si bu parametrenin değerini almak için istek gövdesini otomatik olarak ayrıştırıyor.

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öntemini 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 ile Tedarikçi 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" sonudur, böylece yalnızca olarak ayarlayarak Product.Suppliernullilişkiyi kaldırabilirsiniz.

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

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

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

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 relatedKey parametresi de ürünün ilişkiden Products kaldırılacağı anahtardır. Web API'sinin anahtarı sorgu dizesinden otomatik olarak aldığına dikkat edin.