Podpora možností dotazů OData ve webovém rozhraní API ASP.NET 2

Mike Wasson

Tento přehled s příklady kódu ukazuje podporu možností dotazů OData ve webovém rozhraní API ASP.NET 2 pro ASP.NET 4.x.

OData definuje parametry, které lze použít k úpravě dotazu OData. Klient odešle tyto parametry v řetězci dotazu identifikátoru URI požadavku. Například k seřazení výsledků klient používá parametr $orderby:

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

Specifikace OData volá tyto parametry možnosti dotazu. Možnosti dotazů OData můžete povolit pro libovolný kontroler webového rozhraní API ve vašem projektu – kontroler nemusí být koncový bod OData. Získáte tak pohodlný způsob, jak do libovolné aplikace webového rozhraní API přidat funkce, jako je filtrování a řazení.

Než povolíte možnosti dotazů, přečtěte si téma Pokyny k zabezpečení OData.

Povolení možností dotazu OData

Webové rozhraní API podporuje následující možnosti dotazu OData:

Možnost Popis
$expand Rozbalí vložené související entity.
$filter Filtruje výsledky na základě logické podmínky.
$inlinecount Řekne serveru, aby do odpovědi zahrnul celkový počet odpovídajících entit. (Užitečné pro stránkování na straně serveru.)
$orderby Seřadí výsledky.
$select Vybere vlastnosti, které se mají zahrnout do odpovědi.
$skip Přeskočí prvních n výsledků.
$top Vrátí pouze první n výsledků.

Pokud chcete použít možnosti dotazu OData, musíte je povolit explicitně. Můžete je povolit globálně pro celou aplikaci nebo pro konkrétní kontrolery nebo konkrétní akce.

Pokud chcete povolit možnosti dotazu OData globálně, při spuštění volejte EnableQuerySupport pro třídu HttpConfiguration :

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

    config.EnableQuerySupport();

    // ...
}

EnableQuerySupport Metoda povoluje možnosti dotazu globálně pro všechny akce kontroleru, která vrací typ IQueryable. Pokud nechcete povolit možnosti dotazů pro celou aplikaci, můžete je povolit pro konkrétní akce kontroleru přidáním atributu [Queryable] do metody action.

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

Ukázky dotazů

Tato část ukazuje typy dotazů, které jsou možné pomocí možností dotazu OData. Konkrétní podrobnosti o možnostech dotazu najdete v dokumentaci o službě OData na www.odata.org.

Informace o $expand a $select najdete v tématu Použití $select, $expand a $value v ASP.NET Web API OData.

Stránkování řízené klientem

U velkých sad entit může klient chtít omezit počet výsledků. Klient může například zobrazit 10 položek najednou s "dalšími" odkazy pro získání další stránky výsledků. K tomu klient použije možnosti $top a $skip.

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

Možnost $top poskytuje maximální počet položek, které se mají vrátit, a možnost $skip dává počet položek, které se mají přeskočit. Předchozí příklad načte položky 21 až 30.

Filtrování

Možnost $filter umožňuje klientovi filtrovat výsledky použitím logického výrazu. Výrazy filtru jsou poměrně výkonné; Zahrnují logické a aritmetické operátory, řetězcové funkce a funkce kalendářních dat.

Vrátit všechny produkty, které mají kategorii rovnou "Hračky". http://localhost/Products?$filter=Category eq 'Toys'
Vrátí všechny produkty s cenou nižší než 10. http://localhost/Products?$filter=Price lt 10
Logické operátory: Vrátí všechny produkty, kde cena >= 5 a cena <= 15. http://localhost/Products?$filter=Price ge 5 a cena le 15
Řetězcové funkce: Vrátí všechny produkty, které mají v názvu "zz". http://localhost/Products?$filter=substringof('zz',Name)
Funkce data: Vrátí všechny produkty s datem vydání po roce 2005. http://localhost/Products?$filter=year(ReleaseDate) gt 2005

Řazení

Pokud chcete výsledky seřadit, použijte filtr $orderby.

Seřadit podle ceny. http://localhost/Products?$orderby=Price
Řazení podle ceny v sestupném pořadí (od nejvyšší po nejnižší). http://localhost/Products?$orderby=Price desc
Seřaďte je podle kategorie a pak v kategoriích seřadíte podle ceny v sestupném pořadí. http://localhost/odata/Products?$orderby=Category,Price desc

stránkování Server-Driven

Pokud vaše databáze obsahuje miliony záznamů, nechcete je posílat všechny v jedné datové části. Chcete-li tomu zabránit, může server omezit počet položek, které odesílá v jedné odpovědi. Chcete-li povolit stránkování serveru, nastavte vlastnost PageSize v atributu Queryable . Hodnota je maximální počet položek, které se mají vrátit.

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

Pokud kontroler vrátí formát OData, text odpovědi bude obsahovat odkaz na další stránku dat:

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

Klient může použít tento odkaz k načtení další stránky. Pokud chcete zjistit celkový počet položek v sadě výsledků dotazu, může klient nastavit možnost dotazu $inlinecount s hodnotou "allpages".

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

Hodnota allpages říká serveru, aby do odpovědi zahrnul celkový počet:

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

Poznámka

Odkazy na další stránku i počet vložených položek vyžadují formát OData. Důvodem je, že OData definuje speciální pole v textu odpovědi, která obsahují odkaz a počet.

U jiných formátů než OData je stále možné podporovat odkazy na další stránku a počet vložených řádků tak, že výsledky dotazu zabalíte do objektu PageResult<T> . Vyžaduje ale trochu více kódu. Zde naleznete příklad:

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

Tady je příklad odpovědi 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
}

Omezení možností dotazu

Možnosti dotazu poskytují klientovi velkou kontrolu nad dotazem, který se spouští na serveru. V některých případech můžete chtít omezit dostupné možnosti z důvodů zabezpečení nebo výkonu. Atribut [Queryable] má některé předdefinované vlastnosti. Tady je několik příkladů.

Pokud chcete podporovat stránkování, povolte jenom $skip a $top:

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

Pokud chcete zabránit řazení vlastností, které nejsou indexované v databázi, povolte řazení pouze podle určitých vlastností:

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

Povolte logickou funkci "eq", ale žádné jiné logické funkce:

[Queryable(AllowedLogicalOperators=AllowedLogicalOperators.Equal)]

Nepovolujte žádné aritmetické operátory:

[Queryable(AllowedArithmeticOperators=AllowedArithmeticOperators.None)]

Možnosti můžete globálně omezit tak, že vytvoříte instanci QueryableAttribute a předáte ji funkci EnableQuerySupport :

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

Přímé vyvolání možností dotazu

Místo použití atributu [Queryable] můžete vyvolat možnosti dotazu přímo v kontroleru. Provedete to tak, že do metody kontroleru přidáte parametr ODataQueryOptions . V tomto případě nepotřebujete 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>;
}

Webové rozhraní API naplní ODataQueryOptions z řetězce dotazu URI. Pokud chcete dotaz použít, předejte metodu IQueryablemetodě ApplyTo . Metoda vrátí další IQueryable.

V pokročilých scénářích, pokud nemáte poskytovatele dotazů IQueryable , můžete prozkoumat ODataQueryOptions a přeložit možnosti dotazu do jiného formuláře. (Podívejte se například na blogový příspěvek RaghuRam Nadimintiho Překlad dotazů OData do HQL.)

Ověření dotazu

Atribut [Queryable] před jeho spuštěním dotaz ověří. Krok ověření se provádí v metodě QueryableAttribute.ValidateQuery . Proces ověřování můžete také přizpůsobit.

Projděte si také pokyny k zabezpečení OData.

Nejprve přepište jednu z tříd validátoru, která je definována v oboru názvů Web.Http.OData.Query.Validators . Například následující třída validátoru zakáže možnost "desc" pro možnost $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);
    }
}

Podtřídí atribut [Queryable] pro přepsání metody 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);
    }
}

Pak nastavte vlastní atribut globálně nebo pro jednotlivé kontroleře:

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

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

Pokud používáte ODataQueryOptions přímo, nastavte validátor podle možností:

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