しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

SignalR と ASP.NET MVC の類似点、そして Project Silk

いまいち良いタイトルが浮かびませんでした。しかし、SignalR と ASP.NET MVC は考え方的に非常に近いと思ってます。

例えば SignalR では Hub クラスを継承して新しいハブを作成することになっています。これはコントローラクラスに相当する部分になります。

public class SampleHub : Hub
{
    public string Hello(string name)
    {
        return "Hello, " + name;
    }
}

ASP.NET MVC ではご存じのとおり Controller クラスを継承して、新しいコントローラを作成しますよね。

public class SampleController : Controller
{
    public ActionResult Hello(string name)
    {
        return Content("Hello, " + name);
    }
}

メソッドの戻り値の型が多少異なりますが、非常に似ていますね。なので、考え方は大体 ASP.NET MVC と同じように考えて実装することができます。何故ならば、SignalR では Hub に実装したメソッドは XMLHttpRequest を使って呼び出されるので、おおざっぱに言ってしまえば Web API と同じようなものです。

では、何が違うかと言うと、ASP.NET MVC のコントローラは 1 リクエストごとに使い捨てられるのに対して、SignalR では基本的にハブは最初のリクエストに 1 度だけ作られて、後は全てのリクエスト間で同じものが利用されるようになっています。

public class SampleHub : Hub
{
    public SampleHub()
    {
        // コンストラクタは原則 1 回しか呼び出されない
    }

    public string Hello(string name)
    {
        // メソッドは複数回呼び出される
    }
}

なので、ASP.NET MVC の時のようにリクエスト単位での生存期間だと考えていると、リソースの扱いで戸惑うことがあります。例えば、Azure Table Storage で使う TableServiceContext などはコンストラクタでインスタンス化してしまうと、時間が経過するにつれて不安定になってしまうことがあります。

コンストラクタで初期化は避ける必要があるので、必要な時に必要なインスタンスを取得する必要があるので、Dependency Injection を使うことにしました。仕事で書いているプロジェクトでは Project Silk で使われていた Handler + Command を採用しました。

public class HelloHandler
{
    public string Execute(string name)
    {
        return "Hello, " + name;
    }
}

public abstract class AuthorizedHub : Hub
{
    protected THandler Using<THandler>()
    {
        // DI でインスタンスを作成する
        return GlobalHost.DependencyResolver.Resolve<THandler>();
    }
}

public class SampleHub : AuthorizedHub
{
    public string Hello(string name)
    {
        // Hub では Handler をインスタンス化してメソッドを呼び出すだけ
        return Using<HelloHandler>().Execute(name);
    }
}

この場合では Using を呼び出したタイミングで HelloHandler が常に作成されるので、ASP.NET MVC の時と同様に生存期間を特に考慮する必要がありませんね。

SignalR では Hub が肥大化しがちなので、ASP.NET MVC の時よりも注意して実装する必要がありそうです。