MEF with MVC 4 or 5 - プラガブル・アーキテクチャ (2014)
質問
Orchard CMSのようなプラグイン可能なアーキテクチャを持つMVC4/MVC5アプリケーションを構築しようとしています。だから、私はスタートアップのプロジェクトであるMVCアプリケーションを持っていて、認証、ナビゲーションなどの世話をする。その後、複数のモジュールが asp.net クラスライブラリまたはストリップダウンされた MVC プロジェクトとして別々に構築され、コントローラ、ビュー、データリポジトリなどを持つことになります。
私は一日中 Web 上のチュートリアルを調べ、サンプルなどをダウンロードしましたが、Kenny が最も良い例を持っていることがわかりました。 http://kennytordeur.blogspot.in/2012/08/mef-in-aspnet-mvc-4-and-webapi.html
私は、モジュール (別の DLL) に参照を追加すれば、それらの DLL からコントローラをインポートすることができます。しかし、MEF を使用する理由は、実行時にモジュールを追加することができることです。私はビューと一緒にDLLをスタートアッププロジェクトの~/Modules//ディレクトリにコピーして(私はこれを行うことができました)、MEFがそれらを拾うようにしたいのです。MEFにこれらのライブラリをロードさせるために苦労しています。
この回答で説明されているように、MefContrib もあります。 ASP.NET MVC 4.0 コントローラーとMEF、この2つを一緒にする方法は? というのがあり、これは私が次に試そうとしているものです。しかし、私はMEFがMVCで箱から出して動かないことに驚いています。
誰か同じようなアーキテクチャを(MefContribの有無にかかわらず)動作させたことがありますか?当初は、Orchard CMS を取り外してフレームワークとして使用することも考えましたが、それはあまりにも複雑です。また、WebAPI2 を利用するために、MVC5 でアプリを開発するのもいいでしょう。
どのように解決するのですか?
私は、あなたが説明したようなプラグイン可能なアーキテクチャを持つプロジェクトで働いたことがあり、それは同じ技術ASP.NET MVCとMEFを使用しました。認証、承認、およびすべての要求を処理する、ホスト ASP.NET MVC アプリケーションがありました。私たちのプラグイン(モジュール)はそのサブフォルダーにコピーされました。プラグインは、独自のモデル、コントローラ、ビュー、CSS、JSファイルを持っているASP.NET MVCアプリケーションでした。これらは、私たちがそれを動作させるために従った手順です。
MEF をセットアップする
アプリケーション開始時にすべてのコンポーザブルパーツを発見し、コンポーザブルパーツのカタログを作成する MEF ベースのエンジンを作成しました。これは、アプリケーションの開始時に 1 回だけ実行されるタスクです。エンジンはすべてのプラグイン可能な部品を検出する必要があり、私たちの場合は
bin
フォルダーに、あるいは
Modules(Plugins)
フォルダーに保存されます。
public class Bootstrapper
{
private static CompositionContainer CompositionContainer;
private static bool IsLoaded = false;
public static void Compose(List<string> pluginFolders)
{
if (IsLoaded) return;
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new DirectoryCatalog(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin")));
foreach (var plugin in pluginFolders)
{
var directoryCatalog = new DirectoryCatalog(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Modules", plugin));
catalog.Catalogs.Add(directoryCatalog);
}
CompositionContainer = new CompositionContainer(catalog);
CompositionContainer.ComposeParts();
IsLoaded = true;
}
public static T GetInstance<T>(string contractName = null)
{
var type = default(T);
if (CompositionContainer == null) return type;
if (!string.IsNullOrWhiteSpace(contractName))
type = CompositionContainer.GetExportedValue<T>(contractName);
else
type = CompositionContainer.GetExportedValue<T>();
return type;
}
}
これは、すべての MEF パーツの検出を行うクラスのサンプルコードです。このクラスは
Compose
メソッドから呼び出されます。
Application_Start
メソッドから呼び出されます。
Global.asax.cs
ファイルを作成します。簡略化のため、コードを縮小しています。
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
var pluginFolders = new List<string>();
var plugins = Directory.GetDirectories(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Modules")).ToList();
plugins.ForEach(s =>
{
var di = new DirectoryInfo(s);
pluginFolders.Add(di.Name);
});
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
Bootstrapper.Compose(pluginFolders);
ControllerBuilder.Current.SetControllerFactory(new CustomControllerFactory());
ViewEngines.Engines.Add(new CustomViewEngine(pluginFolders));
}
}
すべてのプラグインが
Modules
フォルダーの別のサブフォルダーにコピーされることを想定しています。各プラグインのサブフォルダーには
Views
サブフォルダーと各プラグインのDLLが含まれています。このうち
Application_Start
メソッドでは、カスタムコントローラファクトリーと、これから定義するカスタムビューエンジンも初期化されています。
MEFから読み込むコントローラファクトリの作成
以下は、リクエストを処理する必要があるコントローラを検出する、カスタムコントローラファクトリを定義するコードです。
public class CustomControllerFactory : IControllerFactory
{
private readonly DefaultControllerFactory _defaultControllerFactory;
public CustomControllerFactory()
{
_defaultControllerFactory = new DefaultControllerFactory();
}
public IController CreateController(RequestContext requestContext, string controllerName)
{
var controller = Bootstrapper.GetInstance<IController>(controllerName);
if (controller == null)
throw new Exception("Controller not found!");
return controller;
}
public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName)
{
return SessionStateBehavior.Default;
}
public void ReleaseController(IController controller)
{
var disposableController = controller as IDisposable;
if (disposableController != null)
{
disposableController.Dispose();
}
}
}
さらに、各コントローラは
Export
属性が必要です。
[Export("Plugin1", typeof(IController))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class Plugin1Controller : Controller
{
//
// GET: /Plugin1/
public ActionResult Index()
{
return View();
}
}
の最初のパラメータは
Export
属性コンストラクタの最初のパラメータは、コントラクト名を指定し、各コントローラを一意に識別するため、一意でなければなりません。そのため
PartCreationPolicy
は NonShared に設定する必要があります。なぜなら、コントローラは複数のリクエストに再利用できないからです。
プラグインからビューを見つけることができるビューエンジンを作成する
カスタムビューエンジンの作成が必要なのは、ビューエンジンが慣習的に
Views
フォルダーにあるビューだけを探すためです。プラグインは別のフォルダにあるため、カスタムビューエンジンを作成する必要があります。
Modules
フォルダーに格納されているため、ビューエンジンにそこも参照するように指示する必要があります。
public class CustomViewEngine : RazorViewEngine
{
private List<string> _plugins = new List<string>();
public CustomViewEngine(List<string> pluginFolders)
{
_plugins = pluginFolders;
ViewLocationFormats = GetViewLocations();
MasterLocationFormats = GetMasterLocations();
PartialViewLocationFormats = GetViewLocations();
}
public string[] GetViewLocations()
{
var views = new List<string>();
views.Add("~/Views/{1}/{0}.cshtml");
_plugins.ForEach(plugin =>
views.Add("~/Modules/" + plugin + "/Views/{1}/{0}.cshtml")
);
return views.ToArray();
}
public string[] GetMasterLocations()
{
var masterPages = new List<string>();
masterPages.Add("~/Views/Shared/{0}.cshtml");
_plugins.ForEach(plugin =>
masterPages.Add("~/Modules/" + plugin + "/Views/Shared/{0}.cshtml")
);
return masterPages.ToArray();
}
}
プラグインで強く型付けされたビューの問題を解決する
上記のコードだけでは、プラグイン(モジュール)内で強く型付けされたビューを使うことができませんでした。なぜなら、モデルは
bin
フォルダの外にモデルが存在するためです。この問題を解決するために、以下のようにします。
リンク
.
関連
-
[解決済み] エンティティタイプ ApplicationUser は、現在のコンテキストのモデルの一部ではありません。
-
[解決済み】"The ConnectionString property has not been initialized "を修正する方法
-
[解決済み】ソケットのアドレス(プロトコル/ネットワークアドレス/ポート)は、通常1つしか使用できない?
-
[解決済み】トランスポート接続からデータを読み取れない:既存の接続は、リモートホストによって強制的に閉じられました。
-
[解決済み】クロススレッド操作が有効でない。作成されたスレッド以外のスレッドからアクセスされたコントロール
-
[解決済み] [Solved] 不正な文字列値: '\xEFxBFxBD' for column
-
[解決済み】「...は'型'であり、与えられたコンテキストでは有効ではありません」を解決するにはどうすればよいですか?(C#)
-
[解決済み】ファイルやアセンブリ、またはその依存関係の1つをロードできませんでした。
-
[解決済み] ファイルアップロード ASP.NET MVC 3.0
-
[解決済み] ASP.NET MVCでenumからドロップダウンリストを作成するにはどうすればよいですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] エンティティタイプ ApplicationUser は、現在のコンテキストのモデルの一部ではありません。
-
[解決済み】トランスポート接続からデータを読み取れない:既存の接続は、リモートホストによって強制的に閉じられました。
-
[解決済み】「namespace x already contains a definition for x」エラーの修正方法は?VS2010にコンバートした後に発生しました。
-
[解決済み] DBNullから他の型にオブジェクトをキャストすることができない
-
[解決済み】なぜこのコードはInvalidOperationExceptionを投げるのですか?
-
[解決済み] UnityでOnCollisionEnterが呼ばれない
-
[解決済み】Swashbuckle/Swagger + ASP.Net Core: "Failed to load API definition" (API定義の読み込みに失敗しました
-
[解決済み】インデックスが範囲外でした。コレクションパラメータname:indexのサイズより小さく、非負でなければなりません。
-
[解決済み】WebResource.axdとは何ですか?
-
[解決済み】データが存在しないのに読み込もうとする試みが無効である