ASP.NET MVC Framework x Spring.NET

MVCフレームワーク調べ物はまだ途中だけど、ちょっと脱線してSpring.NETとの連携を考えてみる。

Spring.NET自体はASP.NETに対応していてWebフォームへのインジェクションを実現している。しかしMVCフレームワークではWebフォームの役割は純粋なビュー(表示オンリー)になるため、インジェクションする先は自然とコントローラになる。

しかし、コントローラの生成と管理はフレームワークが行っているため、そのままではコントローラへのインジェクションは困難なので、このプロセスに割り込む方法がないかまずは調べてみた。

どうやら、「MvcHandler」と「MvcRouteHandler」が鍵を握っているようだ。

MvcHandlerはIHttpHandlerを実装するクラスで、このクラスには「GetControllerInstance」というコントローラを生成するメソッドが定義されている。
しかし通常IHttpHandlerを実装するクラスを使う場合、Web.configのHttpHandlersセクションに設定するが、その設定はどこにも書かれていない。ということは別の方法でこのクラスがインスタンス化されている事になる。

それを担当しているクラスがMvcRouteHandlerクラスのようだ。このクラスの「GetHttpHandler」メソッドがIHttpHandlerを戻り値としているので、ここでMvcHandlerのインスタンスを戻してあげればいいだろう。

MvcRouteHandlerクラスはアプリケーションファイルでルーティングルールを設定する時にRouteHandlerプロパティに指定する。

RouteTable.Routes.Add(new Route {
    Url = "[controller]/[action]",
    Defaults = new { action = "Index" },
    // ↓これ
    RouteHandler = typeof(MvcRouteHandler)
});

この部分を独自のクラスに変えてやればいい。

Spring.NETとの連携

連携するSpring.NETのバージョンは1.1にする。事前にSpring.Core.dllを参照設定しておく。

まずはMvcHandlerのコントローラ生成プロセスに割り込むためのクラス「SpringMvcHandler」クラスを作る。このクラスはMvcHandlerから継承する。

App_Code/SpringMvcHandler.cs
using System;
using System.Web.Mvc;

using Spring.Context;
using Spring.Context.Support;

public class SpringMvcHandler : MvcHandler {
    /// <summary>
    /// Springのコンテキスト
    /// </summary>
    private IApplicationContext springContext = WebApplicationContext.Current;

    protected override IController GetControllerInstance(Type controllerType) {
        IController controller = base.GetControllerInstance(controllerType);

        string[] names = springContext.GetObjectNamesForType(controllerType);
        if(names.Length > 0) {
            springContext.ConfigureObject(controller, names[0]);
        }
        return controller;
    }
}

Springのアプリケーションコンテキストをメンバ変数に格納しておく。

private IApplicationContext springContext = WebApplicationContext.Current;

「GetControllerInstance」メソッドをオーバーライドして、まずはコントローラのインスタンスを生成する。

IController controller = base.GetControllerInstance(controllerType);

Springのアプリケーションコンテキストから、コントローラの型でオブジェクト名を検索する。(オブジェクト定義ファイルにコントローラと同じ型で定義されたオブジェクト定義がある前提、定義の仕方については後述)

string[] names = springContext.GetObjectNamesForType(controllerType);

そして、そのオブジェクト定義に定義されたインジェクション設定*1をコントローラのインスタンスに適用する。こうすることでコントローラにオブジェクトがインジェクションされる。

springContext.ConfigureObject(controller, names[0]);

あとはコントローラのインスタンスを戻すだけ。

次はこのクラスをインスタンス化して、MVCフレームワークに渡す「SpringMvcHandler」クラスを作る。このクラスはMvcRouteHandlerから継承する。

App_Code/SpringMvcRouteHandler.cs
using System;
using System.Web;
using System.Web.Mvc;

public class SpringMvcRouteHandler : MvcRouteHandler {
    protected override IHttpHandler GetHttpHandler(RequestContext requestContext) {
        return new SpringMvcHandler {
            RequestContext = requestContext
        };
    }
}

「GetHttpHandler」メソッドをオーバーライドして、SpringMvcHandlerのインスタンスを戻すだけ。

これらを有効にするにはアプリケーションファイルを以下のように変更する。

Global.asax
<%@ Application Language="C#" %>
<%@ Import Namespace="System.Web.Mvc" %>

<script RunAt="server">

    void Application_Start(object sender, EventArgs e) {
        RouteTable.Routes.Add(new Route {
            Url = "[controller]/[action]",
            Defaults = new { action = "Index" },
            // ↓を変更
            RouteHandler = typeof(SpringMvcRouteHandler)
        });
        RouteTable.Routes.Add(new Route {
            Url = "Default.aspx",
            Defaults = new { controller = "Home", action = "Index", id = (string)null },
            RouteHandler = typeof(MvcRouteHandler)
        });
    }
       
</script>

じゃあ実際にコントローラにオブジェクトをインジェクションしてみる。

以下のようなクラスを定義する。

Models/HogeManager.cs
using System;

public class HogeManager {
}

これをHomeControllerにプロパティで設定できるようにする。

App_Code/Controllers/HomeController.cs
using System;
using System.Web.Mvc;

public class HomeController : Controller {
    private HogeManager hogeMgr;

    public HogeManager HogeManager {
        get { return hogeMgr; }
        set { hogeMgr = value; }
    }

    [ControllerAction]
    public void Index(string message) {
        RenderView("Home", (object)message);
    }
}

そしてオブジェクト定義ファイルを作成する。

App_Data/spring/objects.config
<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net">

    <object type="HomeController" lazy-init="true">
        <property name="HogeManager" ref="hogeMgr" />
    </object>

    <object name="hogeMgr" type="HogeManager">  
    </object>
    
</objects>

おっと、Web.configにSpring.NETを使うための設定がいる。

Web.config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/>
        </sectionGroup>
    </configSections>

    <appSettings/>
    <connectionStrings/>

    <spring>
        <context>
            <resource uri="~/App_Data/spring/objects.config" />
        </context>
    </spring>
    
    <!-- 省略 -->
</configuration>

これで実行すると無事HogeManagerのインスタンスがコントローラにインジェクションされた。

案外簡単に連携できた。これもMVCフレームワークが柔軟に設計されているおかげかな。

*1:こういうのをなんて言えばいいのかわからない