Bagikan melalui


Mendukung Tindakan OData di ASP.NET Web API 2

oleh Mike Wasson

Unduh Proyek yang Selesai

Di OData, tindakan adalah cara untuk menambahkan perilaku sisi server yang tidak mudah didefinisikan sebagai operasi CRUD pada entitas. Beberapa kegunaan untuk tindakan meliputi:

  • Menerapkan transaksi yang kompleks.
  • Memanipulasi beberapa entitas sekaligus.
  • Mengizinkan pembaruan hanya untuk properti entitas tertentu.
  • Mengirim informasi ke server yang tidak ditentukan dalam entitas.

Versi perangkat lunak yang digunakan dalam tutorial

  • WEB API 2
  • OData Versi 3
  • Entity Framework 6

Contoh: Memberi Peringkat Produk

Dalam contoh ini, kami ingin mengizinkan pengguna menilai produk, lalu mengekspos peringkat rata-rata untuk setiap produk. Pada database, kami akan menyimpan daftar peringkat, kunci untuk produk.

Berikut adalah model yang mungkin kita gunakan untuk mewakili peringkat dalam Entity Framework:

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; }
}

Tetapi kami tidak ingin klien MEMPOSTING ProductRating objek ke koleksi "Peringkat". Secara intuitif, peringkat dikaitkan dengan koleksi Produk, dan klien hanya perlu memposting nilai peringkat.

Oleh karena itu, alih-alih menggunakan operasi CRUD normal, kami menentukan tindakan yang dapat dipanggil klien pada Produk. Dalam terminologi OData, tindakan terikat dengan entitas Produk.

Tindakan memiliki efek samping pada server. Untuk alasan ini, mereka dipanggil menggunakan permintaan HTTP POST. Tindakan dapat memiliki parameter dan jenis pengembalian, yang dijelaskan dalam metadata layanan. Klien mengirim parameter dalam isi permintaan, dan server mengirim nilai pengembalian dalam isi respons. Untuk memanggil tindakan "Produk Tarif", klien mengirim POST ke URI seperti berikut:

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

Data dalam permintaan POST hanyalah peringkat produk:

{"Rating":2}

Mendeklarasikan Tindakan dalam Model Data Entitas

Dalam konfigurasi API Web Anda, tambahkan tindakan ke model data entitas (EDM):

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());
    }
}

Kode ini mendefinisikan "RateProduct" sebagai tindakan yang dapat dilakukan pada entitas Produk. Ini juga menyatakan bahwa tindakan mengambil parameter int bernama "Rating", dan mengembalikan nilai int .

Menambahkan Tindakan ke Pengontrol

Tindakan "RateProduct" terikat pada entitas Produk. Untuk menerapkan tindakan, tambahkan metode bernama RateProduct ke pengontrol Produk:

[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);
}

Perhatikan bahwa nama metode cocok dengan nama tindakan di EDM. Metode ini memiliki dua parameter:

  • kunci: Kunci untuk produk untuk menilai.
  • parameter: Kamus nilai parameter tindakan.

Jika Anda menggunakan konvensi perutean default, parameter kunci harus diberi nama "kunci". Penting juga untuk menyertakan atribut [FromOdataUri] , seperti yang ditunjukkan. Atribut ini memberi tahu Web API untuk menggunakan aturan sintaks OData saat mengurai kunci dari URI permintaan.

Gunakan kamus parameter untuk mendapatkan parameter tindakan:

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

Jika klien mengirim parameter tindakan dalam format yang benar, nilai ModelState.IsValid adalah true. Dalam hal ini, Anda dapat menggunakan kamus ODataActionParameters untuk mendapatkan nilai parameter. Dalam contoh ini, RateProduct tindakan mengambil satu parameter bernama "Peringkat".

Metadata Tindakan

Untuk melihat metadata layanan, kirim permintaan GET ke /odata/$metadata. Berikut adalah bagian metadata yang menyatakan RateProduct tindakan:

<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>

Elemen FunctionImport mendeklarasikan tindakan. Sebagian besar bidang cukup jelas, tetapi dua perlu dicatat:

  • IsBindable berarti tindakan dapat dipanggil pada entitas target, setidaknya beberapa waktu.
  • IsAlwaysBindable berarti tindakan selalu dapat dipanggil pada entitas target.

Perbedaannya adalah bahwa beberapa tindakan selalu tersedia untuk klien, tetapi tindakan lain mungkin bergantung pada status entitas. Misalnya, Anda menentukan tindakan "Beli". Anda hanya dapat membeli item yang ada di stok. Jika item kehabisan stok, klien tidak dapat memanggil tindakan tersebut.

Saat Anda menentukan EDM, metode Tindakan membuat tindakan yang selalu dapat diikat:

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

Saya akan berbicara tentang tindakan yang tidak selalu dapat diikat (juga disebut tindakan sementara ) nanti dalam topik ini.

Memanggil Tindakan

Sekarang mari kita lihat bagaimana klien akan memanggil tindakan ini. Misalkan klien ingin memberikan peringkat 2 untuk produk dengan ID = 4. Berikut adalah contoh pesan permintaan, menggunakan format JSON untuk isi permintaan:

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

{"Rating":2}

Berikut adalah pesan responsnya:

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
}

Mengikat Tindakan ke Kumpulan Entitas

Dalam contoh sebelumnya, tindakan terikat ke satu entitas: Klien menilai satu produk. Anda juga dapat mengikat tindakan ke kumpulan entitas. Cukup buat perubahan berikut:

Di EDM, tambahkan tindakan ke properti Koleksi entitas.

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

Dalam metode pengontrol, hilangkan parameter kunci .

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

Sekarang klien memanggil tindakan pada kumpulan entitas Produk:

http://localhost/odata/Products/RateAllProducts

Tindakan dengan Parameter Koleksi

Tindakan dapat memiliki parameter yang mengambil kumpulan nilai. Di EDM, gunakan CollectionParameter<T> untuk mendeklarasikan parameter.

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

Ini mendeklarasikan parameter bernama "Peringkat" yang mengambil kumpulan nilai int . Dalam metode pengontrol, Anda masih mendapatkan nilai parameter dari objek ODataActionParameters, tetapi sekarang nilainya adalah nilai int> ICollection<:

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

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

    // ...
}

Tindakan Sementara

Dalam contoh "RateProduct", pengguna selalu dapat menilai produk, sehingga tindakan selalu tersedia. Tetapi beberapa tindakan bergantung pada status entitas. Misalnya, dalam layanan penyewaan video, tindakan "CheckOut" tidak selalu tersedia. (Ini tergantung apakah salinan video tersebut tersedia.) Jenis tindakan ini disebut tindakan sementara .

Dalam metadata layanan, tindakan sementara memiliki IsAlwaysBindable sama dengan false. Itu sebenarnya nilai default, sehingga metadata akan terlihat seperti ini:

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

Inilah alasannya: Jika tindakan bersifat sementara, server perlu memberi tahu klien kapan tindakan tersedia. Ini dilakukan dengan menyertakan tautan ke tindakan dalam entitas. Berikut adalah contoh untuk entitas Film:

{
  "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"
}

Properti "#CheckOut" berisi tautan ke tindakan CheckOut. Jika tindakan tidak tersedia, server akan menghilangkan tautan.

Untuk mendeklarasikan tindakan sementara di EDM, panggil metode TransientAction :

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

Selain itu, Anda harus menyediakan fungsi yang mengembalikan tautan tindakan untuk entitas tertentu. Atur fungsi ini dengan memanggil HasActionLink. Anda dapat menulis fungsi sebagai ekspresi lambda:

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);

Jika tindakan tersedia, ekspresi lambda mengembalikan tautan ke tindakan. Serializer OData menyertakan tautan ini saat menserialisasikan entitas. Ketika tindakan tidak tersedia, fungsi mengembalikan null.

Sumber Daya Tambahan