Windows Azure Tips from Prospex

システム開発 × インフラ構築・運用 × グラフィックデザイン = プロスペックス

MVC コントローラーをDIする

clock June 20, 2011 21:49 by author Sekine

タイトル通り、ASP NET MVC のコントローラーをDIします。

MVCとは?という人のために、ASP.NETで至極簡単にコントローラーとビューを作成します。

作成プロジェクト名 MvcApp
C#、MVC3、Razorテンプレート使用、空のアプリケーションとして作成

作成直後は、こんな構成
image

空のアプリケーションなので、このまま実行しても、表示するものが無いため、

エラーとなります。


Global.asax(.cs) に注目

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapRoute(
        "Default", // ルート名
        "{controller}/{action}/{id}", // パラメーター付きの URL
        new { controller = "Home", action = "Index", id = UrlParameter.Optional } 
    );
}

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();

    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);
}

Application_Start内から、RegisterRoutesメソッドを呼び、

ルーティングマップの登録を最初に行っています。

MVCでは、マップに従い、コントローラーが呼ばれます。

routes.MapRouteで、処理するコントローラーを登録しています。

初期定義では、URL指定が無い場合に、Homeコントローラーが呼ばれると思ってください。

※マップ定義を変更すれば、処理コントローラーを変更できます。
詳しくは説明しませんが、様々なルール定義をすることで、念密に振り分けることが出来ます。


初期のままの実行では、Homeコントローラーが無いため、エラーとなるので、

Homeコントローラーを作成します。

ソリューションエクスプローラ上で、

Controllers 右クリック、追加、コントローラー を選択

image

こんな画面が出るので、HomeController とします。

HomeController.csが作成されます。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace MvcApp.Controllers
{
    public class HomeController : Controller
    {
        //
        // GET: /Home/

        public ActionResult Index()
        {
            return View();
        }

    }
}


次に、ビューの作成

csコード内の、Indexメソッドのところで、右クリックしてサブメニューを出すと、

ビューの追加という項目があるので、選択・クリックします。

image

ビュー名には、メソッド名と同じく Index を指定します。

レイアウトまたはマスターページを使用する、には空白でも良いです。

追加ボタン押下で、Views/Home/Index.cshtml が作成されます。

@{
    ViewBag.Title = "Index";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Index</h2>

これで、Homeコントローラーと、その表示ビューが作成できました。

image

実行します。Homeコントローラーがあるので今度はエラーにはなりません。

image


さて、

コントローラーとビューの関係は、コントローラー名とメソッド名、

それに、Viewsフォルダ配下のフォルダ名とファイル名とに関連付けされます。

コントローラーの戻り値にView(); として返してましたが、

引数を与えることで、別のビューを返す(=別の画面を表示させる)こともできます。


MVCの動作論理を明確に説明するには、このブログ記事では難しいので、

これ以上は割愛します。別の機会があれば書きたいと思いますが。



では、本題のコントローラーのDIについて。

MVCコントローラーは、Controllerクラス (System.Web.Mvc名前空間)を継承する必要があります。

Controllerクラスを継承さえしていれば、何処に配置しても良いのです。

※プロジェクト内のControllersフォルダに配置する必要もありません。


厳密には、IControllerインターフェースを継承すれば、コントローラーとして振る舞うことが可能ですが、

MVC内部コードでは、Controllerクラスの継承元のControllerBaseクラスの依存が、

非常に大きく、Controllerを継承するのが無難です。



コントローラーは、URLリクエストの解析(マップ定義も含め)を経て、

処理するコントローラークラスが決定されます。

この決定処理は、コントローラーファクトリが行います。

コントローラーが選ばれる条件としては、

IControllerインターフェースを継承し、クラス名がControllerで終わっていること。


即ち、コントローラーのDIは、既にコントローラーファクトリから、DIされているのです。



それでは、具体的に、

ソリューションに、クラスライブラリプロジェクトを追加します。

プロジェクト名 ImplLibrary
C# クラスライブラリ

参照設定として、

System.Web
System.Web.Routing
System.Web.Mvc

を加えます。

プロジェクトに、Controllerクラスを継承する、AboutControllerというクラスを作成します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Web.Mvc;

namespace ImplLibrary
{
    public class AboutController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
    }
}

次に、MVCプロジェクト側に、Aboutを表示するビューを追加します。

Indexメソッド付近を右クリックしても、MVCプロジェクト内ではないので、

前のようにビューの追加メニューは出てきません。

そのため、Viewsフォルダに直接フォルダとビューファイルを作成します。

Views右クリック、追加、新しいフォルダー: About フォルダーを作る。

作成した、Aboutフォルダーを右クリック、追加、新しい項目で、

MVC 3 ビューページ (Razor) を選択し、ファイル名を、Index.cshtml とします。


Index.cshtml 作成後は、内容・構造を、Home/Index.cshtml と同じにし、

文言を少し変更しましょう。

-- About/Index.cshtmlの内容 --

@{
    ViewBag.Title = "Index";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Index</h2>

ここは、Aboutです。


MVCアプリケーションでは、ImplLibraryをプロジェクト参照します。

作成したソリューションは、以下のようになります。
image

実行してみます。
image

/Aboutへアクセスし、AboutControllerが呼ばれて、About/Index.cshtml が表示されます。

これで、コントローラーを別のプロジェクトに分離することができます。

保守性もグッと良くなります。



次に、動的DIへ向けて

生成されるモジュールは、MVCプロジェクトのモジュール(本体)と、ImplLibrary.dllですが、

ImplLibrary.dll は、/bin 配下にあります。

ASP.NETの恩恵で、/bin のアセンブリは自動的にロードされるのですが、

/bin のモジュール変更に際しては、プロセスがリサイクルされるので、

/bin以外の場所に配置して、リサイクルされないようにします。

これにより、動作中のWEBサイトを無停止でモジュール変更をします。


コントローラーをインスタンス化しているのは、コントローラーファクトリなので、

コントローラーファクトリをカスタマイズし、コントローラーをMEFに対応させ、

MEFによってインスタンス化させます。


次回へ・・・



関根:



DI コンテナーとしてのManaged Extensibility Framework (MEF)

clock January 24, 2011 21:06 by author Sekine

さてさて、

社内では、いろいろとリリース作業におわれている今日この頃

 

ところで、

修正やリリースなど新しいソースコードで、トラブルなど見舞われた場合どうしてますか?

ソースコードを元に戻すのがとりあえずの対処法ですが、

そういう時に、前のメソッドを呼ぶように直そう・・・と思っても、

実際には、前のコードに直したり、ビルドし直して再配置したりと

かなりめんどうな作業になりますね。

 

そういう対処に備える方法論としてDIコンテナーという概念があります。

.NET系ではそれほど目立つ存在ではないのですが、

JAVAの世界では、フレームワーク側の考えで当たり前のようになっています。

 

その前に、DIとは何かという事に触れておきましょう。

DI(Dependency Injectionの略)とは、依存性の注入と訳されて良く言われます。

クラス継承の関係から実装を取り除き、依存性を少なくするデザインパターンです。

具体的には、インターフェースクラスと実装クラスに分離させて設計し、

物理的に、利用する側はインターフェース型のインスタンス変数に実装クラスを注入して利用するパターンです。

仮想メソッドを宣言した基底クラスとしても良いでしょう。

クラス設計をする人には、特に難しくは無いでしょう。

 

ここで、依存性の注入というところがミソで、

生成する実装クラスが、定義によって決める事ができるというのが、

DIコンテナーの大きな役割です。

 

以下.NETで扱う方法を簡略にまとめました。

実際に、今後の開発指針として組み入れたい物です。

 

.NETでDIを実装する方法

1. Assemblyを読み込み、Reflection(リフレクション)を使い、対象を探す。

2. Sessar.NET を使う

3. Spring.NET を使う

4.Managed Extensibility Framework (MEF) を使う

 

実は、1.の方法は、私も以前に実装し、稼働させたコードがあります。

2. 3. の方法は、オープンソースとして利用する方法ですが、

内部動作は、1の方法と同じで、設定などが定義しやすい作りになっています。

4. のMEF は、.NET Framework 4.0 より利用できるMS純正コンポーネントです。

MEFは、クラスコンストラクタのインジェクション以外にも、

プロパティ追加などの注入もできてしまう優れものです。

他、MSコンポーネントと言うこともありますが、使い勝手も分かりやすい面もあるので、

ここでは、4. の Managed Extensibility Frameworkの簡単な方法を示します。

 

まずは、インターフェース (HelloInterface.DLL として作成)
public interface IHello
{
    string GetMessage();
}

次に実装クラス (HelloImpl.DLL として作成)
using System.ComponentModel.Composition;
..
[Export("Hello", typeof(IHello))]
public class Hello : IHello
{
    public string GetMessage()
    {
        return "Hello";
    }
}

コンテナー側
using System.Reflection;
using System.ComponentModel.Composition.Hosting;
..
public class MefContainer
{
    private static T getInjectionObject<T>(string contractName, AggregateCatalog catalog)
    {
        using (CompositionContainer container = new CompositionContainer(catalog))
        {
            return container.GetExportedValue<T>(contractName);
        }
    }

    public static T GetComponent<T>(string contractName = null)
    {
        using (AggregateCatalog catalog = new AggregateCatalog())
        {
            catalog.Catalogs.Add(new DirectoryCatalog("./bin"));

            return getInjectionObject<T>(contractName, catalog);
        }
    }
}

./bin を指定しているのは、Webアプリケーションを想定しています。

 

次に呼び出し側Webアプリケーション(ASP.NET MVC コントローラ)

public class helloController : Controller
{
    public ActionResult index()
    {
        IHello hello;

        hello = MefContainer.GetComponent<IHello>("Hello");

        ViewData["Message"] = hello.GetMessage();

        return View();
    }
}

Webアプリケーション側の参照は、HelloInterface.DLLだけで良いのですが、

binディレクトリには、HelloImpl.DLLが無いと実行時にエラーが出ます。

 

これを実行すると、画面には、HelloInterface.DLLの実装部の"Hello"が表示されます。

ここから更に、別の実装クラス (HelloNewImpl.DLL として作成)
using System.ComponentModel.Composition;
..
[Export("HelloNew", typeof(IHello))]
public class HelloNew : IHello
{
    public string GetMessage()
    {
        return "Happy new Year";
    }

}
HelloNewImpl.DLLをWebアプリケーションの bin に配置

Webアプリケーション側のコードを、以下に変更

        hello = MefContainer.GetComponent<IHello>("HelloNew");

実行すると、今度は、"Happy new Year"と表示jされます。

 

具体的な動作は、実装クラスに付く、Export属性に付けた コントラクト名と、

CompositionContainer の GetExportedValueによって、

対象コントラクト名を持つクラスがロードされてインスタンス化されているという事です。

 

この動作の現実的な利点は何かというと、

DLLファイルの置き換えによって、動作を変更できる点です。

上記の例では、"HelloNew"というコントラクト名を作ったもの作成していますが、

同一のコントラクト名を持てば、利用側のコードを修正せずに動作を変更できます。

 

リリース後に、不具合発生。リリース前に戻すという流れを、DLLの置き換えだけですむようになります。

 

また、コントラクト名は、文字列というのも大きな点です。

コントラクト名を羅列した定義ファイルをWeb.Config等に持たせれば、

動作変更を設定ファイルの変更と、修正DLLのコピーで済むようになり、

本体アプリケーションの再ビルドは不要になります。

これがDIコンテナーの一番の利点ですね。

 

Managed Extensibility Framework には、プロパティの注入という、

ある意味恐ろしい?!機能がありますが、

また、別の機会に書きたいと思います。

 

:関根



Sign In