web-dev-qa-db-ja.com

MVC 4または5を備えたMEF-プラガブルアーキテクチャ(2014)

Orchard CMSのようなプラグ可能なアーキテクチャでMVC4/MVC5アプリケーションを構築しようとしています。だから私は、スタートアッププロジェクトであり、認証、ナビゲーションなどの世話をするMVCアプリケーションを持っています。それから、asp.netクラスライブラリとして別々に構築された複数のモジュール、またはmvcプロジェクトを取り除いたコントローラ、ビュー、データリポジトリなどがあります.

私は一日中ウェブ上でチュートリアルを行ったり、サンプルなどをダウンロードしたりして、ケニーが最高の例を持っていることがわかりました- 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でアプリを開発できたら嬉しいです。

79
Yashvit

私は、あなたが説明したものと同様のプラグ可能なアーキテクチャを持ち、同じ技術_ASP.NET MVC_とMEFを使用したプロジェクトに取り組みました。認証、承認、およびすべての要求を処理するホストASP.NET MVCアプリケーションがありました。プラグイン(モジュール)は、そのサブフォルダーにコピーされました。プラグインは、独自のモデル、コントローラー、ビュー、css、jsファイルを持つ_ASP.NET MVC_アプリケーションでもありました。これらを機能させるために行った手順は次のとおりです。

MEFのセットアップ

アプリケーションの起動時に構成可能なすべてのパーツを検出し、構成可能なパーツのカタログを作成するMEFに基づいてエンジンを作成しました。これは、アプリケーションの起動時に一度だけ実行されるタスクです。エンジンは、すべてのプラグ可能なパーツを検出する必要があります。この場合、ホストアプリケーションの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フォルダーの外部に存在するため、プラグイン(モジュール)で強く型付けされたビューを使用できません。この問題を解決するには、次の link に従ってください。

104
Ilija Dimov

MEFのコンテナには、作成するIDisposableオブジェクトへの参照を保持する「素敵な機能」があり、巨大なメモリリークが発生することに注意してください。伝えられるところでは、メモリリークはこのnugetで対処できます- http://nuget.org/packages/NCode.Composition.DisposableParts.Signed

5
Aleksandar

プラグインアーキテクチャを実装するプロジェクトがあります。これらのいずれかを使用するか、ソースコードを見て、これらのことをどのように達成するかを確認することをお勧めします。

また、 外部アセンブリのコントローラーの404 は興味深いアプローチを取っています。質問を読むだけで多くのことを学びました。

3
Dejan