ASP.NET Web API 2'de OData Eylemlerini Destekleme

tarafından Mike Wasson

Tamamlanan Projeyi İndir

OData'da eylemler , varlıklarda CRUD işlemleri olarak kolayca tanımlanmayan sunucu tarafı davranışları eklemenin bir yoludur. Eylemlerin bazı kullanımları şunlardır:

  • Karmaşık işlemler uygulama.
  • Aynı anda birkaç varlığı düzenleme.
  • Bir varlığın yalnızca belirli özellikleri için güncelleştirmelere izin verme.
  • Bir varlıkta tanımlanmayan bilgileri sunucuya gönderme.

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

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

Örnek: Bir Ürünü Derecelendirme

Bu örnekte kullanıcıların ürünleri derecelendirmesine izin vermek ve ardından her ürün için ortalama derecelendirmeleri kullanıma sunmalarını istiyoruz. Veritabanında, ürünler için anahtarlanmış bir derecelendirme listesi depolayacağız.

Entity Framework'teki derecelendirmeleri temsil etmek için kullanabileceğimiz model aşağıdadır:

public class ProductRating
{
    public int ID { get; set; }

    [ForeignKey("Product")]
    public int ProductID { get; set; }
    public virtual Product Product { get; set; }  // Navigation property

    public int Rating { get; set; }
}

Ancak istemcilerin bir ProductRating nesneyi "Ratings" koleksiyonuna GÖNDERMEsini istemeyiz. Sezgisel olarak, derecelendirme Products koleksiyonuyla ilişkilendirilir ve istemcinin yalnızca derecelendirme değerini göndermesi gerekir.

Bu nedenle, normal CRUD işlemlerini kullanmak yerine bir istemcinin bir Product üzerinde çağırabileceği bir eylem tanımlarız. OData terminolojisinde eylem, Ürün varlıklarına bağlıdır .

Eylemlerin sunucuda yan etkileri vardır. Bu nedenle, HTTP POST istekleri kullanılarak çağrılır. Eylemler, hizmet meta verilerinde açıklanan parametrelere ve dönüş türlerine sahip olabilir. İstemci, parametreleri istek gövdesine, sunucu ise yanıt gövdesindeki dönüş değerini gönderir. "Ürünü Derecelendir" eylemini çağırmak için istemci, aşağıdaki gibi bir URI'ye POST gönderir:

http://localhost/odata/Products(1)/RateProduct

POST isteğindeki veriler yalnızca ürün derecelendirmesidir:

{"Rating":2}

Varlık Veri Modelinde Eylemi Bildirme

Web API yapılandırmanızda, eylemi varlık veri modeline (EDM) ekleyin:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
        builder.EntitySet<Product>("Products");
        builder.EntitySet<Supplier>("Suppliers");
        builder.EntitySet<ProductRating>("Ratings");

        // New code: Add an action to the EDM, and define the parameter and return type.
        ActionConfiguration rateProduct = builder.Entity<Product>().Action("RateProduct");
        rateProduct.Parameter<int>("Rating");
        rateProduct.Returns<double>();

        config.Routes.MapODataRoute("odata", "odata", builder.GetEdmModel());
    }
}

Bu kod, "RateProduct" öğesini Ürün varlıkları üzerinde gerçekleştirilebilecek bir eylem olarak tanımlar. Ayrıca eylemin "Rating" adlı bir int parametresi aldığını ve bir int değeri döndürdüğünü bildirir.

Eylemi Denetleyiciye Ekleme

"RateProduct" eylemi Product varlıklarına bağlıdır. Eylemi uygulamak için Products denetleyicisine adlı RateProduct bir yöntem ekleyin:

[HttpPost]
public async Task<IHttpActionResult> RateProduct([FromODataUri] int key, ODataActionParameters parameters)
{
    if (!ModelState.IsValid)
    {
        return BadRequest();
    }

    int rating = (int)parameters["Rating"];

    Product product = await db.Products.FindAsync(key);
    if (product == null)
    {
        return NotFound();
    }

    product.Ratings.Add(new ProductRating() { Rating = rating });
    db.SaveChanges();

    double average = product.Ratings.Average(x => x.Rating);

    return Ok(average);
}

Yöntem adının EDM'deki eylemin adıyla eşleştiğinden dikkat edin. yönteminin iki parametresi vardır:

  • key: Ürünün derecelendirecek anahtarı.
  • parameters: Eylem parametresi değerlerinin sözlüğü.

Varsayılan yönlendirme kurallarını kullanıyorsanız anahtar parametresi "key" olarak adlandırılmalıdır. [ FromOdataUri] özniteliğini gösterildiği gibi eklemek de önemlidir. Bu öznitelik, anahtarı istek URI'sinden ayrıştırdığında Web API'sine OData söz dizimi kurallarını kullanmasını bildirir.

Eylem parametrelerini almak için parametre sözlüğünü kullanın:

if (!ModelState.IsValid)
{
    return BadRequest();
}
int rating = (int)parameters["Rating"];

İstemci eylem parametrelerini doğru biçimde gönderirse , ModelState.IsValid değeri true olur. Bu durumda, parametre değerlerini almak için ODataActionParameters sözlüğü kullanabilirsiniz. Bu örnekte, RateProduct eylem "Derecelendirme" adlı tek bir parametre alır.

Eylem Meta Verileri

Hizmet meta verilerini görüntülemek için /odata/$metadata adresine bir GET isteği gönderin. Meta verilerin eylemi bildiren kısmı aşağıdadır RateProduct :

<FunctionImport Name="RateProduct" m:IsAlwaysBindable="true" IsBindable="true" ReturnType="Edm.Double">
  <Parameter Name="bindingParameter" Type="ProductService.Models.Product"/>
  <Parameter Name="Rating" Nullable="false" Type="Edm.Int32"/>
</FunctionImport>

FunctionImport öğesi eylemi bildirir. Alanların çoğu açıklayıcıdır, ancak iki tanesi dikkate değer:

  • IsBindable , eylemin en azından bir süre hedef varlıkta çağrılabileceği anlamına gelir.
  • IsAlwaysBindable , eylemin hedef varlıkta her zaman çağrılabileceği anlamına gelir.

Aradaki fark, bazı eylemlerin istemciler tarafından her zaman kullanılabilir olmasıdır, ancak diğer eylemler varlığın durumuna bağlı olabilir. Örneğin, bir "Satın Alma" eylemi tanımladığınız varsayalım. Yalnızca stokta bulunan bir ürünü satın alabilirsiniz. Öğe stokta değilse, istemci bu eylemi çağıramaz.

EDM'yi tanımladığınızda , Eylem yöntemi her zaman bağlanabilir bir eylem oluşturur:

builder.Entity<Product>().Action("RateProduct"); // Always bindable

Bu konunun devamında her zaman bağlanamayan eylemlerden ( geçici eylemler olarak da adlandırılır) bahsedeceğim.

Eylemi Çağırma

Şimdi bir istemcinin bu eylemi nasıl çağıracağını görelim. İstemcinin ürüne kimlik = 4 ile 2 derecelendirmesi vermek istediğini varsayalım. İstek gövdesi için JSON biçimini kullanan örnek bir istek iletisi aşağıda verilmiştir:

POST http://localhost/odata/Products(4)/RateProduct HTTP/1.1
Content-Type: application/json
Content-Length: 12

{"Rating":2}

Yanıt iletisi şöyledir:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
DataServiceVersion: 3.0
Date: Tue, 22 Oct 2013 19:04:00 GMT
Content-Length: 89

{
  "odata.metadata":"http://localhost:21900/odata/$metadata#Edm.Double","value":2.75
}

Bir Eylemi Varlık Kümesine Bağlama

Önceki örnekte, eylem tek bir varlığa bağlıdır: İstemci, tek bir ürünü fiyatlarına göre belirler. Bir eylemi varlık koleksiyonuna da bağlayabilirsiniz. Yalnızca aşağıdaki değişiklikleri yapın:

EDM'de eylemi varlığın Collection özelliğine ekleyin.

var rateAllProducts = builder.Entity<Product>().Collection.Action("RateAllProducts");

denetleyici yönteminde anahtar parametresini atlar.

[HttpPost]
public int RateAllProducts(ODataActionParameters parameters)
{
    // ....
}

Şimdi istemci, Products varlık kümesinde eylemi çağırır:

http://localhost/odata/Products/RateAllProducts

Koleksiyon Parametrelerine Sahip Eylemler

Eylemler, bir değer koleksiyonu alan parametrelere sahip olabilir. EDM'de, parametreyi bildirmek için CollectionParameter<T> kullanın.

rateAllProducts.CollectionParameter<int>("Ratings");

Bu, int değerlerinin bir koleksiyonunu alan "Ratings" adlı bir parametre bildirir. denetleyici yönteminde parametre değerini yine ODataActionParameters nesnesinden alırsınız, ancak şimdi değer bir ICollection<int> değeridir:

[HttpPost]
public void RateAllProducts(ODataActionParameters parameters)
{
    if (!ModelState.IsValid)
    {
        throw new HttpResponseException(HttpStatusCode.BadRequest);
    }

    var ratings = parameters["Ratings"] as ICollection<int>; 

    // ...
}

Geçici Eylemler

"RateProduct" örneğinde kullanıcılar her zaman bir ürünü derecelendirebilir, böylece eylem her zaman kullanılabilir. Ancak bazı eylemler varlığın durumuna bağlıdır. Örneğin, bir video kiralama hizmetinde "Kullanıma Alma" eylemi her zaman kullanılamaz. (Bu videonun bir kopyasının kullanılabilir olup olmadığına bağlıdır.) Bu eylem türü geçici eylem olarak adlandırılır.

Hizmet meta verilerinde, geçici bir eylemin IsAlwaysBindable değeri false değerine eşittir. Bu aslında varsayılan değerdir, bu nedenle meta veriler şöyle görünür:

<FunctionImport Name="CheckOut" IsBindable="true">
    <Parameter Name="bindingParameter" Type="ProductsService.Models.Product" />
</FunctionImport>

Bunun önemli olmasının nedeni şudur: Bir eylem geçiciyse, sunucunun eylem kullanılabilir olduğunda istemciye bunu söylemesi gerekir. Bunu, varlığa eylemin bağlantısını ekleyerek yapar. Film varlığı için bir örnek aşağıda verilmiştir:

{
  "odata.metadata":"http://localhost:17916/odata/$metadata#Movies/@Element",
  "#CheckOut":{ "target":"http://localhost:17916/odata/Movies(1)/CheckOut" },
  "ID":1,"Title":"Sudden Danger 3","Year":2012,"Genre":"Action"
}

"#CheckOut" özelliği, Kullanıma Alma eyleminin bağlantısını içerir. Eylem kullanılamıyorsa, sunucu bağlantıyı atlar.

EDM'de geçici bir eylem bildirmek için TransientAction yöntemini çağırın:

var checkoutAction = builder.Entity<Movie>().TransientAction("CheckOut");

Ayrıca, belirli bir varlık için eylem bağlantısı döndüren bir işlev sağlamanız gerekir. HasActionLink'i çağırarak bu işlevi ayarlayın. İşlevi lambda ifadesi olarak yazabilirsiniz:

checkoutAction.HasActionLink(ctx =>
{
    var movie = ctx.EntityInstance as Movie;
    if (movie.IsAvailable) {
        return new Uri(ctx.Url.ODataLink(
            new EntitySetPathSegment(ctx.EntitySet), 
            new KeyValuePathSegment(movie.ID.ToString()),
            new ActionPathSegment(checkoutAction.Name)));
    }
    else
    {
        return null;
    }
}, followsConventions: true);

Eylem kullanılabilir durumdaysa lambda ifadesi eylemin bağlantısını döndürür. OData seri hale getirici, varlığı seri hale getirdiğinde bu bağlantıyı içerir. Eylem kullanılabilir olmadığında işlevi döndürür null.

Ek Kaynaklar