かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

ASP.NET WebAPIでODataクエリの美味しいところだけいただく方法

ページングとか、最大取得件数とかソートとかetc...そこらへんとかは、誰が実装しても同じようなコードになるので提供されているライブラリで楽して実装したい。そういうのが人情だと思います。今回はASP.NET WebAPIのOData対応の機能を使ってソート、フィルター、ページングなどの処理をODataのクエリにお任せしてしまう方法をちょろっとやってみようと思います。

ODataQueryOptionsクラス

そういう役目をしてくれるクラスがWebAPIのODataの機能の中にばっちりいたりします。

こいつをODataのクエリを有効にしたWebAPIの引数で受け取るようにすると勝手にクエリを解釈した結果が入ってます。ODataのクエリを有効化するには以下の方法でさくっとやっちゃいましょう。 - ASP.NET 空の Web アプリケーションからWebAPIを使えるようにするための道のり「作成からヘルプページ、ODataのクエリの有効化まで」

上記の方法は、空のテンプレートからですがASP.NET MVC4のプロジェクトを作ってWebAPIのテンプレートを選択して、WebApiConfigで以下の一行のコメントを外すでもOKです。

config.EnableQuerySupport();

さて、ODataQueryOptoionsを使うと何が嬉しいかというと、このオブジェクトをRepositoryあたりまで引数で渡してやると、ApplyToメソッドでIQueryableに対してODataで設定された条件を指定してくれること…!自前でパラメータ解析しなくていい点がいいですね。よくある、ControllerクラスにDbContextとか持たせてる方法だと、あんまり気にしなくてもいいですが、Controllerの下に何層かレイヤがあるときは、IQueryableに対する操作をControllerでしたくないということもあると思いますので、こいつを使うのがいいと思います。

さっそく使ってみましょう。

コントローラーの前にコントローラーから呼び出す予定のクラスを作ります。なんとんく、こいつにODataQueryOptionsを直で渡すのは、いやだったので、ラムダ式でフィルタを追加で指定できるようなシグネチャにしてみました。name引数で受け取った値をもとに絞り込み条件を設定したあとに、optionalFilterに値が設定されていたら、IQueryable<Person>を渡して追加処理を行います。

public class PeopleService
{
    private static readonly IList<Person> Store = Enumerable.Range(1, 100)
        .Select(i => new Person { Id = i, Name = "tanaka" + i })
        .ToList();

    public IQueryable<Person> GetPerson(
        string name,
        Func<IQueryable<Person>, IQueryable<Person>> optionalFilter = null)
    {
        var q = Store.AsQueryable()
            .Where(p => p.Name.Contains(name));
        if (optionalFilter != null)
        {
            q = optionalFilter(q);
        }

        var list = q.ToList();
        return list.AsQueryable();
    }
}

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
}

あとは、コントローラーだけです。サービスを呼ぶだけの簡単な感じで。

public class PeopleController : ApiController
{
    private PeopleService service = new PeopleService();

    public IQueryable<Person> Get(ODataQueryOptions<Person> options, string name)
    {
        return this.service.GetPerson(
            name,
            optionalFilter: q => options.ApplyTo(q) as IQueryable<Person>);
    }
}

ODataQueryOptions<Person>を引数で受け取ってApplyToメソッドを使ってOData関連のクエリをお任せしています。もちろんOData以外のパラメータを受けることはできるので、nameという引数も追加してみました。こいつを実行してFiddlerあたりから軽く呼び出してみます。名前にa1を含む人を3人とか

orderbyしてみたりとか

ばっちり動きますね!あとはクライアント側で、このクエリ文字列を組み立てるのが簡単にできたら文句ないんですが…。