しばやん雑記

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

SignalR で Unity を使ってみた

Twitter や Facebook で ASP.NET には Dependency Resolver が多すぎる、統一してくれ!!と叫んでるんですが、今日は SignalR の Dependency Resolver と戯れてました。

SignalR はデフォルトで Dependency Resolver を持っているし、使っているんですが、コンストラクタへのインジェクションとか使えないので、例によって Unity を使ってデフォルトのリゾルバをオーバーライドしてみました。

public class UnityDependencyResolver : DefaultDependencyResolver
{
    public UnityDependencyResolver(IUnityContainer container)
    {
        _container = container;
    }

    private readonly IUnityContainer _container;

    public override object GetService(Type serviceType)
    {
        return _container.IsRegistered(serviceType) ? _container.Resolve(serviceType) : base.GetService(serviceType);
    }

    public override IEnumerable<object> GetServices(Type serviceType)
    {
        return _container.IsRegistered(serviceType) ? _container.ResolveAll(serviceType) : base.GetServices(serviceType);
    }
}

SignalR の場合は DefaultDependencyResolver が内部で使っているクラスをあらかじめ登録するようになっているので、IDependencyResolver だけを実装しようとすると 500 で死にます。同じ理由で UnityContainer に登録されていない型の場合は、基底クラスのメソッドを呼び出してやらないと同じく 500 で死にます。

Dependency Resolver を用意したら、次は Global.asax.cs の Application_Start でリゾルバをオーバーライドします。

protected void Application_Start(object sender, EventArgs e)
{
    var container = new UnityContainer();

    container.RegisterType<IUserService, UserService>();

    GlobalHost.DependencyResolver = new UnityDependencyResolver(container);
    RouteTable.Routes.MapHubs();
}

古い記事では AspNetHost.SetResolver を呼び出してる場合がありますが、最新の 0.5.2 では GlobalHost.DependencyResolver プロパティになってます。注意点としては、DependencyResolver を入れ替えた後には必ず MapHubs メソッドを呼び出す必要があります。

ちゃんと呼び出しておかないと、新しいリゾルバが使われないのでめんどくさいバグの元になります。

これで DI の設定は終わったので Hub を簡単に用意します。

public class SampleHub : Hub
{
    public SampleHub(IUserService userService)
    {
    }
}

何の意味もない Hub ですが、コンストラクタで IUserService のインスタンスを受け取るようにしています。これで実行時にはちゃんとインジェクションされそうですが、何故か正しくインジェクションが行われず、お馴染みのエラーが表示されます。

このプロジェクトで、引数なしコンストラクターは定義されていません。

ASP.NET MVC の場合は同じような実装で正しくインジェクションがされるので、SignalR は多少挙動が違うようです。なので、回避するために Hub 自体を予め Unity に登録しておきます。

container.RegisterType<SampleHub>();

これで SignalR でもコンストラクタへのインジェクションが動作するようになりました。