Bagikan melalui


Mendukung Opsi Kueri OData di ASP.NET Web API 2

oleh Mike Wasson

Gambaran umum dengan contoh kode ini menunjukkan Opsi Kueri OData pendukung di ASP.NET Web API 2 untuk ASP.NET 4.x.

OData mendefinisikan parameter yang dapat digunakan untuk mengubah kueri OData. Klien mengirimkan parameter ini dalam string kueri URI permintaan. Misalnya, untuk mengurutkan hasil, klien menggunakan parameter $orderby:

http://localhost/Products?$orderby=Name

Spesifikasi OData memanggil opsi kueri parameter ini. Anda dapat mengaktifkan opsi kueri OData untuk pengontrol API Web apa pun di proyek Anda — pengontrol tidak perlu menjadi titik akhir OData. Ini memberi Anda cara mudah untuk menambahkan fitur seperti pemfilteran dan pengurutan ke aplikasi Web API apa pun.

Sebelum mengaktifkan opsi kueri, silakan baca topik Panduan Keamanan OData.

Mengaktifkan Opsi Kueri OData

API Web mendukung opsi kueri OData berikut:

Opsi Deskripsi
$expand Memperluas entitas terkait sebaris.
$filter Memfilter hasilnya, berdasarkan kondisi Boolean.
$inlinecount Memberi tahu server untuk menyertakan jumlah total entitas yang cocok dalam respons. (Berguna untuk halaman sisi server.)
$orderby Mengurutkan hasil.
$select Memilih properti mana yang akan disertakan dalam respons.
$skip Melompati hasil n pertama.
$top Mengembalikan hanya n hasil pertama.

Untuk menggunakan opsi kueri OData, Anda harus mengaktifkannya secara eksplisit. Anda dapat mengaktifkannya secara global untuk seluruh aplikasi, atau mengaktifkannya untuk pengontrol tertentu atau tindakan tertentu.

Untuk mengaktifkan opsi kueri OData secara global, panggil EnableQuerySupport pada kelas HttpConfiguration saat startup:

public static void Register(HttpConfiguration config)
{
    // ...

    config.EnableQuerySupport();

    // ...
}

Metode EnableQuerySupport memungkinkan opsi kueri secara global untuk setiap tindakan pengontrol yang mengembalikan jenis IQueryable . Jika Anda tidak ingin opsi kueri diaktifkan untuk seluruh aplikasi, Anda dapat mengaktifkannya untuk tindakan pengontrol tertentu dengan menambahkan atribut [Dapat Dikueri] ke metode tindakan.

public class ProductsController : ApiController
{
    [Queryable]
    IQueryable<Product> Get() {}
}

Contoh kueri

Bagian ini memperlihatkan jenis kueri yang dimungkinkan menggunakan opsi kueri OData. Untuk detail spesifik tentang opsi kueri, lihat dokumentasi OData di www.odata.org.

Untuk informasi tentang $expand dan $select, lihat Menggunakan $select, $expand, dan $value di ASP.NET Web API OData.

Halaman Berbasis Klien

Untuk set entitas besar, klien mungkin ingin membatasi jumlah hasil. Misalnya, klien mungkin menampilkan 10 entri pada satu waktu, dengan tautan "berikutnya" untuk mendapatkan halaman hasil berikutnya. Untuk melakukan ini, klien menggunakan opsi $top dan $skip.

http://localhost/Products?$top=10&$skip=20

Opsi $top memberikan jumlah entri maksimum untuk dikembalikan, dan opsi $skip memberikan jumlah entri yang akan dilewati. Contoh sebelumnya mengambil entri 21 hingga 30.

Pemfilteran

Opsi $filter memungkinkan klien memfilter hasil dengan menerapkan ekspresi Boolean. Ekspresi filter cukup kuat; mereka termasuk operator logis dan aritmatika, fungsi string, dan fungsi tanggal.

Mengembalikan semua produk dengan kategori yang sama dengan "Mainan". http://localhost/Products?$filter=Category eq 'Mainan'
Kembalikan semua produk dengan harga kurang dari 10. http://localhost/Products?$filter=Price lt 10
Operator logika: Mengembalikan semua produk di mana harga >= 5 dan harga <= 15. http://localhost/Products?$filter=Price ge 5 dan Harga le 15
Fungsi string: Mengembalikan semua produk dengan "zz" dalam nama. http://localhost/Products?$filter=substringof('zz',Name)
Fungsi tanggal: Mengembalikan semua produk dengan ReleaseDate setelah 2005. http://localhost/Products?$filter=year(ReleaseDate) gt 2005

Penyortiran

Untuk mengurutkan hasilnya, gunakan filter $orderby.

Urutkan berdasar harga. http://localhost/Products?$orderby=Price
Urutkan berdasar harga dalam urutan turun (tertinggi hingga terendah). http://localhost/Products?$orderby=Price desc
Urutkan berdasar kategori, lalu urutkan menurut harga dalam urutan turun dalam kategori. http://localhost/odata/Products?$orderby=Category,Price desc

halaman Server-Driven

Jika database Anda berisi jutaan rekaman, Anda tidak ingin mengirim semuanya dalam satu payload. Untuk mencegah hal ini, server dapat membatasi jumlah entri yang dikirimnya dalam satu respons. Untuk mengaktifkan halaman server, atur properti PageSize di atribut Queryable . Nilai adalah jumlah maksimum entri yang akan dikembalikan.

[Queryable(PageSize=10)]
public IQueryable<Product> Get() 
{
    return products.AsQueryable();
}

Jika pengontrol Anda mengembalikan format OData, isi respons akan berisi tautan ke halaman data berikutnya:

{
  "odata.metadata":"http://localhost/$metadata#Products",
  "value":[
    { "ID":1,"Name":"Hat","Price":"15","Category":"Apparel" },
    { "ID":2,"Name":"Socks","Price":"5","Category":"Apparel" },
    // Others not shown
  ],
  "odata.nextLink":"http://localhost/Products?$skip=10"
}

Klien dapat menggunakan tautan ini untuk mengambil halaman berikutnya. Untuk mempelajari jumlah total entri dalam kumpulan hasil, klien dapat mengatur opsi kueri $inlinecount dengan nilai "allpages".

http://localhost/Products?$inlinecount=allpages

Nilai "allpages" memberi tahu server untuk menyertakan jumlah total dalam respons:

{
  "odata.metadata":"http://localhost/$metadata#Products",
  "odata.count":"50",
  "value":[
    { "ID":1,"Name":"Hat","Price":"15","Category":"Apparel" },
    { "ID":2,"Name":"Socks","Price":"5","Category":"Apparel" },
    // Others not shown
  ]
}

Catatan

Tautan halaman berikutnya dan jumlah sebaris keduanya memerlukan format OData. Alasannya adalah bahwa OData mendefinisikan bidang khusus dalam isi respons untuk menahan tautan dan jumlah.

Untuk format non-OData, masih dimungkinkan untuk mendukung tautan halaman berikutnya dan jumlah sebaris, dengan membungkus hasil kueri dalam objek T> PageResult<. Namun, ini membutuhkan sedikit lebih banyak kode. Ini contohnya:

public PageResult<Product> Get(ODataQueryOptions<Product> options)
{
    ODataQuerySettings settings = new ODataQuerySettings()
    {
        PageSize = 5
    };

    IQueryable results = options.ApplyTo(_products.AsQueryable(), settings);

    return new PageResult<Product>(
        results as IEnumerable<Product>, 
        Request.GetNextPageLink(), 
        Request.GetInlineCount());
}

Berikut adalah contoh respons JSON:

{
  "Items": [
    { "ID":1,"Name":"Hat","Price":"15","Category":"Apparel" },
    { "ID":2,"Name":"Socks","Price":"5","Category":"Apparel" },

    // Others not shown
    
  ],
  "NextPageLink": "http://localhost/api/values?$inlinecount=allpages&$skip=10",
  "Count": 50
}

Membatasi Opsi Kueri

Opsi kueri memberi klien banyak kontrol atas kueri yang dijalankan di server. Dalam beberapa kasus, Anda mungkin ingin membatasi opsi yang tersedia untuk alasan keamanan atau performa. Atribut [Queryable] memiliki beberapa properti bawaan untuk ini. Berikut adalah beberapa contohnya.

Izinkan hanya $skip dan $top, untuk mendukung halaman dan tidak ada yang lain:

[Queryable(AllowedQueryOptions=
    AllowedQueryOptions.Skip | AllowedQueryOptions.Top)]

Perbolehkan pengurutan hanya dengan properti tertentu, untuk mencegah pengurutan pada properti yang tidak diindeks dalam database:

[Queryable(AllowedOrderByProperties="Id")] // comma-separated list of properties

Izinkan fungsi logika "eq" tetapi tidak ada fungsi logis lainnya:

[Queryable(AllowedLogicalOperators=AllowedLogicalOperators.Equal)]

Jangan izinkan operator aritmatika apa pun:

[Queryable(AllowedArithmeticOperators=AllowedArithmeticOperators.None)]

Anda dapat membatasi opsi secara global dengan membuat instans QueryableAttribute dan meneruskannya ke fungsi EnableQuerySupport :

var queryAttribute = new QueryableAttribute()
{
    AllowedQueryOptions = AllowedQueryOptions.Top | AllowedQueryOptions.Skip,
    MaxTop = 100
};
                
config.EnableQuerySupport(queryAttribute);

Memanggil Opsi Kueri Secara Langsung

Alih-alih menggunakan atribut [Queryable] , Anda dapat memanggil opsi kueri langsung di pengontrol Anda. Untuk melakukannya, tambahkan parameter ODataQueryOptions ke metode pengontrol. Dalam hal ini, Anda tidak memerlukan atribut [Queryable ].

public IQueryable<Product> Get(ODataQueryOptions opts)
{
    var settings = new ODataValidationSettings()
    {
        // Initialize settings as needed.
        AllowedFunctions = AllowedFunctions.AllMathFunctions
    };

    opts.Validate(settings);

    IQueryable results = opts.ApplyTo(products.AsQueryable());
    return results as IQueryable<Product>;
}

API Web mengisi ODataQueryOptions dari string kueri URI. Untuk menerapkan kueri, teruskan IQueryable ke metode ApplyTo . Metode mengembalikan IQueryable lain.

Untuk skenario tingkat lanjut, jika Anda tidak memiliki penyedia kueri IQueryable , Anda dapat memeriksa ODataQueryOptions dan menerjemahkan opsi kueri ke dalam formulir lain. (Misalnya, lihat posting blog RaghuRam Nadiminti Menerjemahkan kueri OData ke HQL)

Validasi Kueri

Atribut [Queryable] memvalidasi kueri sebelum mengeksekusinya. Langkah validasi dilakukan dalam metode QueryableAttribute.ValidateQuery . Anda juga dapat menyesuaikan proses validasi.

Lihat juga Panduan Keamanan OData.

Pertama, ganti salah satu kelas validator yang ditentukan dalam namespace Web.Http.OData.Query.Validators . Misalnya, kelas validator berikut menonaktifkan opsi 'desc' untuk opsi $orderby.

public class MyOrderByValidator : OrderByQueryValidator
{
    // Disallow the 'desc' parameter for $orderby option.
    public override void Validate(OrderByQueryOption orderByOption,
                                    ODataValidationSettings validationSettings)
    {
        if (orderByOption.OrderByNodes.Any(
                node => node.Direction == OrderByDirection.Descending))
        {
            throw new ODataException("The 'desc' option is not supported.");
        }
        base.Validate(orderByOption, validationSettings);
    }
}

Subkelas atribut [Dapat Dikueri] untuk mengambil alih metode ValidateQuery .

public class MyQueryableAttribute : QueryableAttribute
{
    public override void ValidateQuery(HttpRequestMessage request, 
        ODataQueryOptions queryOptions)
    {
        if (queryOptions.OrderBy != null)
        {
            queryOptions.OrderBy.Validator = new MyOrderByValidator();
        }
        base.ValidateQuery(request, queryOptions);
    }
}

Kemudian atur atribut kustom Anda baik secara global atau per pengontrol:

// Globally:
config.EnableQuerySupport(new MyQueryableAttribute());

// Per controller:
public class ValuesController : ApiController
{
    [MyQueryable]
    public IQueryable<Product> Get()
    {
        return products.AsQueryable();
    }
}

Jika Anda menggunakan ODataQueryOptions secara langsung, atur validator pada opsi:

public IQueryable<Product> Get(ODataQueryOptions opts)
{
    if (opts.OrderBy != null)
    {
        opts.OrderBy.Validator = new MyOrderByValidator();
    }

    var settings = new ODataValidationSettings()
    {
        // Initialize settings as needed.
        AllowedFunctions = AllowedFunctions.AllMathFunctions
    };

    // Validate
    opts.Validate(settings);

    IQueryable results = opts.ApplyTo(products.AsQueryable());
    return results as IQueryable<Product>;
}