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


80

OrchardCMSのようなプラグ可能なアーキテクチャで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でアプリを開発するとよいでしょう。


1
このセットアップをMVC5で動作させることができましたか?私はMVC5で同じことをセットアップしようとしています。あなたの助けに感謝します
ジュニア

1
これは、EFと海峡ASP.netの両方を実装するバージョンが完了しているように見える競合の例です。codeproject.com/Articles/1109475/...
BrownPony

より多くのアプリケーションがMEFを使用しないのはなぜですか?誰もがこれに自分自身を転がしているようです。
ジョニー2017

回答:


105

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

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コントローラは、複数の要求のために再利用することができないので、非共有に設定する必要があります。

プラグインからビューを見つけることを知っているビューエンジンを作成する

ビューエンジンは慣例により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フォルダーの外部に存在するため、プラグイン(モジュール)で強く型付けされたビューを使用できませんでした。この問題を解決するには、次のリンクをたどってください


1
個々のモジュールのカスタムルートはどうですか?各モジュールはroutetableとグローバルasaxの参照を取得する必要があると思いますが、ルートインターフェイスはモジュールフォルダとコアの両方で表示されます。
sharif y 2014

3
プラグインごとに個別の領域を定義することで、これを解決しました。各プラグインで、AreaRegistrationから継承するクラスを作成し、RegisterAreaメソッドをオーバーライドすることで、プラグインで使用するルートを定義することができました。
Ilija Dimov 2014年

10
このソリューションのサンプルプロジェクトはどこかにありますか?
cpoDesign 2014年

2
cpoDesignに同意します。サンプルプロジェクトがいいでしょう
chris vietor 2014

2
また、GitHubのサンプルプロジェクトをダウンロードするとよいと
思い

5

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


DryIocの方が優れていると言うもう1つの理由:)
Hassan Tareq 2018

3

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.