Udostępnij za pośrednictwem


Używanie $select, $expand i $value w internetowym interfejsie API 2 OData ASP.NET

Autor: Mike Wasson

Omówienie i przykłady kodu dla opcji $expand, $select i $value w internetowym interfejsie API OData 2 dla ASP.NET 4.x. Te opcje pozwalają klientowi kontrolować reprezentację, która wraca z serwera.

  • $expand powoduje, że powiązane jednostki muszą zostać uwzględnione w tekście w odpowiedzi.
  • $select wybiera podzbiór właściwości do uwzględnienia w odpowiedzi.
  • $value pobiera nieprzetworzone wartości właściwości.

Przykładowy schemat

W tym artykule użyję usługi OData, która definiuje trzy jednostki: Product, Supplier i Category. Każdy produkt ma jedną kategorię i jednego dostawcę.

Diagram przedstawiający przykładowy schemat usługi danych O, definiujący produkty, dostawcy i kategorie jako jednostki.

Poniżej przedstawiono klasy języka C#, które definiują modele jednostek:

public class Supplier
{
    [Key]
    public string Key {get; set; }
    public string Name { get; set; }
}
public class Category
{
    public int ID { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Product> Products { get; set; }
}

public class Product
{
    public int ID { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }

    [ForeignKey("Category")]
    public int CategoryId { get; set; }
    public Category Category { get; set; }

    [ForeignKey("Supplier")]
    public string SupplierId { get; set; }
    public virtual Supplier Supplier { get; set; }
}

Zwróć uwagę, że Product klasa definiuje właściwości nawigacji dla elementu Supplier i Category. Klasa Category definiuje właściwość nawigacji dla produktów w każdej kategorii.

Aby utworzyć punkt końcowy OData dla tego schematu, użyj szkieletu Visual Studio 2013, zgodnie z opisem w temacie Tworzenie punktu końcowego OData w interfejsie API sieci Web ASP.NET. Dodaj oddzielne kontrolery dla produktu, kategorii i dostawcy.

Włączanie $expand i $select

W Visual Studio 2013 szkielet OData interfejsu API sieci Web tworzy kontroler, który automatycznie obsługuje $expand i $select. Poniżej przedstawiono wymagania dotyczące obsługi $expand i $select w kontrolerze.

W przypadku kolekcji metoda kontrolera Get musi zwrócić zapytanie IQueryable.

[Queryable]
public IQueryable<Category> GetCategories()
{
    return db.Categories;
}

W przypadku pojedynczych jednostek zwróć element SingleResult<T>, gdzie T jest elementem IQueryable zawierającym zero lub jedną jednostkę.

[Queryable]
public SingleResult<Category> GetCategory([FromODataUri] int key)
{
    return SingleResult.Create(db.Categories.Where(c => c.ID == key));
}

Ponadto udekoruj Get metody za pomocą atrybutu [Queryable], jak pokazano w poprzednich fragmentach kodu. Alternatywnie wywołaj metodę EnableQuerySupport w obiekcie HttpConfiguration podczas uruchamiania . (Aby uzyskać więcej informacji, zobacz Włączanie opcji zapytania OData).

Korzystanie z $expand

Podczas wykonywania zapytań względem jednostki lub kolekcji OData domyślna odpowiedź nie obejmuje powiązanych jednostek. Na przykład poniżej przedstawiono domyślną odpowiedź dla zestawu jednostek Kategorie:

{
  "odata.metadata":"http://localhost/odata/$metadata#Categories",
  "value":[
    {"ID":1,"Name":"Apparel"},
    {"ID":2,"Name":"Toys"}
  ]
}

Jak widać, odpowiedź nie zawiera żadnych produktów, mimo że jednostka Category ma link nawigacji Produkty. Jednak klient może użyć $expand, aby uzyskać listę produktów dla każdej kategorii. Opcja $expand jest w ciągu zapytania żądania:

GET http://localhost/odata/Categories?$expand=Products

Teraz serwer będzie zawierać produkty dla każdej kategorii, wbudowane z kategoriami. Oto ładunek odpowiedzi:

{
  "odata.metadata":"http://localhost/odata/$metadata#Categories",
  "value":[
    {
      "Products":[
        {"ID":1,"Name":"Hat","Price":"15.00","CategoryId":1,"SupplierId":"CTSO"},
        {"ID":2,"Name":"Scarf","Price":"12.00","CategoryId":1,"SupplierId":"CTSO"},
        {"ID":3,"Name":"Socks","Price":"5.00","CategoryId":1,"SupplierId":"FBRK"}
      ],
      "ID":1,
      "Name":"Apparel"
    },
    {
      "Products":[
        {"ID":4,"Name":"Yo-yo","Price":"4.95","CategoryId":2,"SupplierId":"WING"},
        {"ID":5,"Name":"Puzzle","Price":"8.00","CategoryId":2,"SupplierId":"WING"}
      ],
      "ID":2,
      "Name":"Toys"
    }
  ]
}

Zwróć uwagę, że każdy wpis w tablicy "value" zawiera listę Products (Produkty).

Opcja $expand powoduje rozwinięcie listy właściwości nawigacji rozdzielanych przecinkami. Poniższe żądanie rozszerza zarówno kategorię, jak i dostawcę produktu.

GET http://localhost/odata/Products(1)?$expand=Category,Supplier

Oto treść odpowiedzi:

{
  "odata.metadata":"http://localhost/odata/$metadata#Products/@Element",
  "Category": {"ID":1,"Name":"Apparel"},
  "Supplier":{"Key":"CTSO","Name":"Contoso, Ltd."},
  "ID":1,
  "Name":"Hat",
  "Price":"15.00",
  "CategoryId":1,
  "SupplierId":"CTSO"
}

Możesz rozwinąć więcej niż jeden poziom właściwości nawigacji. Poniższy przykład obejmuje wszystkie produkty dla kategorii, a także dostawcę dla każdego produktu.

GET http://localhost/odata/Categories(1)?$expand=Products/Supplier

Oto treść odpowiedzi:

{
  "odata.metadata":"http://localhost/odata/$metadata#Categories/@Element",
  "Products":[
    {
      "Supplier":{"Key":"CTSO","Name":"Contoso, Ltd."},
      "ID":1,"Name":"Hat","Price":"15.00","CategoryId":1,"SupplierId":"CTSO"
    },
    {
      "Supplier":{"Key":"CTSO","Name":"Contoso, Ltd."},
      "ID":2,"Name":"Scarf","Price":"12.00","CategoryId":1,"SupplierId":"CTSO"
    },{
      "Supplier":{
        "Key":"FBRK","Name":"Fabrikam, Inc."
      },"ID":3,"Name":"Socks","Price":"5.00","CategoryId":1,"SupplierId":"FBRK"
    }
  ],"ID":1,"Name":"Apparel"
}

Domyślnie internetowy interfejs API ogranicza maksymalną głębokość rozszerzenia do 2. Uniemożliwia to klientowi wysyłanie złożonych żądań, takich jak $expand=Orders/OrderDetails/Product/Supplier/Region, które mogą być nieefektywne w przypadku wykonywania zapytań i tworzenia dużych odpowiedzi. Aby zastąpić wartość domyślną, ustaw właściwość MaxExpansionDepth w atrybucie [Queryable].

[Queryable(MaxExpansionDepth=4)]
public IQueryable<Category> GetCategories()
{
    return db.Categories;
}

Aby uzyskać więcej informacji na temat opcji $expand, zobacz Rozwiń opcję zapytania systemowego ($expand) w oficjalnej dokumentacji OData.

Korzystanie z $select

Opcja $select określa podzestaw właściwości do uwzględnienia w treści odpowiedzi. Aby na przykład uzyskać tylko nazwę i cenę każdego produktu, użyj następującego zapytania:

GET http://localhost/odata/Products?$select=Price,Name

Oto treść odpowiedzi:

{
  "odata.metadata":"http://localhost/odata/$metadata#Products&$select=Price,Name",
  "value":[
    {"Price":"15.00","Name":"Hat"},
    {"Price":"12.00","Name":"Scarf"},
    {"Price":"5.00","Name":"Socks"},
    {"Price":"4.95","Name":"Yo-yo"},
    {"Price":"8.00","Name":"Puzzle"}
  ]
}

Możesz połączyć $select i $expand w tym samym zapytaniu. Pamiętaj, aby uwzględnić rozwiniętą właściwość w opcji $select. Na przykład następujące żądanie pobiera nazwę i dostawcę produktu.

GET http://localhost/odata/Products?$select=Name,Supplier&$expand=Supplier

Oto treść odpowiedzi:

{
  "odata.metadata":"http://localhost/odata/$metadata#Products&$select=Name,Supplier",
  "value":[
    {
      "Supplier":{"Key":"CTSO","Name":"Contoso, Ltd."},
      "Name":"Hat"
    },
    {
      "Supplier":{"Key":"CTSO","Name":"Contoso, Ltd."},
      "Name":"Scarf"
    },
    {
      "Supplier":{"Key":"FBRK","Name":"Fabrikam, Inc."},
      "Name":"Socks"
    },
    {
      "Supplier":{"Key":"WING","Name":"Wingtip Toys"},
      "Name":"Yo-yo"
    },
    {
      "Supplier":{"Key":"WING","Name":"Wingtip Toys"},
      "Name":"Puzzle"
   }
  ]
}

Możesz również wybrać właściwości w rozwiniętej właściwości. Poniższe żądanie rozwija pozycję Products (Produkty) i wybiera nazwę kategorii oraz nazwę produktu.

GET http://localhost/odata/Categories?$expand=Products&$select=Name,Products/Name

Oto treść odpowiedzi:

{
  "odata.metadata":"http://localhost/odata/$metadata#Categories&$select=Name,Products/Name",
  "value":[ 
    {
      "Products":[ {"Name":"Hat"},{"Name":"Scarf"},{"Name":"Socks"} ],
      "Name":"Apparel"
    },
    {
      "Products":[ {"Name":"Yo-yo"},{"Name":"Puzzle"} ],
      "Name":"Toys"
    }
  ]
}

Aby uzyskać więcej informacji na temat opcji $select, zobacz Wybieranie opcji zapytania systemowego ($select) w oficjalnej dokumentacji OData.

Pobieranie poszczególnych właściwości jednostki ($value)

Istnieją dwa sposoby uzyskiwania przez klienta OData pojedynczej właściwości z jednostki. Klient może pobrać wartość w formacie OData lub uzyskać nieprzetworzone wartości właściwości.

Następujące żądanie pobiera właściwość w formacie OData.

GET http://localhost/odata/Products(1)/Name

Oto przykładowa odpowiedź w formacie JSON:

HTTP/1.1 200 OK
Content-Type: application/json; odata=minimalmetadata; streaming=true; charset=utf-8
DataServiceVersion: 3.0
Content-Length: 90

{
  "odata.metadata":"http://localhost:14239/odata/$metadata#Edm.String",
  "value":"Hat"
}

Aby uzyskać nieprzetworzona wartość właściwości, dołącz $value do identyfikatora URI:

GET http://localhost/odata/Products(1)/Name/$value

Oto odpowiedź. Zwróć uwagę, że typ zawartości to "text/plain", a nie JSON.

HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
DataServiceVersion: 3.0
Content-Length: 3

Hat

Aby obsługiwać te zapytania w kontrolerze OData, dodaj metodę o nazwie GetProperty, gdzie Property jest nazwą właściwości. Na przykład metoda pobierania właściwości Name ma nazwę GetName. Metoda powinna zwrócić wartość tej właściwości:

public async Task<IHttpActionResult> GetName(int key)
{
    Product product = await db.Products.FindAsync(key);
    if (product == null)
    {
        return NotFound();
    }
    return Ok(product.Name);
}