18

更新: 最小限の解決策については、この投稿の以下をお読みください

プラグインを使用した MVC4 ソリューションについて初心者向けの質問があります。少しググって、良いものを見つけましたが、私の要件に完全に適合していないので、ここでアドバイスを求めています。

MVC のウィジェットのようなプラグインの最適なソリューションは、移植可能な領域 (MvcContrib パッケージ内) のようです。ここで基本的なガイダンスを見つけました:

http://lostechies.com/erichexter/2009/11/01/asp-net-mvc-portable-areas-via-mvccontrib/

ここにいくつかの役立つヒントがあります:

http://geekswithblogs.net/michelotti/archive/2010/04/05/mvc-portable-areas-ndash-web-application-projects.aspx

この投稿のその他の内容:

ASP.NET MVC エリアをプラグイン DLL として作成するには?

それはすべてクールですが、残念ながら私の要件は少し異なります。

  1. 残念ながら、プラグインが動的に追加および検出されるシステムが必要ですが、これはメインの MVC サイト プロジェクトによって参照される必要があるポータブル領域には当てはまりません。サイトに何かをアップロードして、新しいコンポーネントを検出して使用したいので、MEF を使用します。

  2. 幸いなことに、私のプラグインは、非常に複雑で異質なウィジェットのようなものにはなりません。むしろ、それらは共通の共有パターンに従わなければならないコンポーネントです。それらを特殊なエディターのように考えてください。データ型ごとに、編集機能 (新規、編集、削除) を備えたコンポーネントを提供します。そこで、共通のインターフェースを実装し、新規、編集、削除などのアクションを提供するプラグイン コントローラーを考えていました。

  3. MVC4 を使用する必要があり、将来的にはローカリゼーションとモバイルのカスタマイズを追加する必要があります。

  4. 複雑なフレームワークからの依存を避け、コードをできるだけシンプルに保つ必要があります。

したがって、この Web サイトで編集用に新しいデータ型を追加するときはいつでも、ロジック (コントローラーなど) 用のプラグイン フォルダーに DLL をドロップし、いくつかのビューを正しい場所にドロップして、サイトを取得します。新しいエディターを見つけて使用します。

最終的に、ビューを DLL 自体に含めることができました (これを見つけました: http://razorgenerator.codeplex.com、およびこのチュートリアル: http://www.chrisvandesteeg.nl/2010/11/22/embedding-pre-compiled -razor-views-in-your-dll/、参照するコードがVS2012と互換性がないため、codeplex razorgeneratorで使用できると思いますが、おそらくそれらを分離した方がよいでしょう(これもローカリゼーションとモバイル認識の要件); 私は自分のサイトの管理領域にアップロード メカニズムを追加することを考えていました。そこでは、コントローラーとビューを含むフォルダーを含む DLL を含む単一の zip をアップロードし、必要に応じてサーバーにファイルを解凍して保存させることができます。これにより、アドイン全体を再度展開しなくても、ビューを簡単に変更できます。

そこで、MEF と MVC を探し始めましたが、ほとんどの投稿は MVC2 を参照しており、互換性がありません。これは主に Web API に焦点を当てていますが、有望で十分に単純に見えます。

http://kennytordeur.blogspot.it/2012/08/mef-in-aspnet-mvc-4-and-webapi.html

これにより、基本的に、MEF ベースの依存関係リゾルバーとコントローラー ファクトリが "標準" MVC アプリケーションに追加されます。とにかく、投稿のサンプルは単一アセンブリ ソリューションを参照していますが、いくつかの異なるプラグインをデプロイする必要があります。そのため、プラグイン フォルダーを指す MEF DirectoryCatalog (AssemblyCatalog ではなく) を使用するようにコードを少し変更し、クラス ライブラリに単一のプラグインを含むテスト MVC ソリューションを作成しました。

とにかく、プラグイン コントローラーをロードしようとすると、フレームワークは null 型でファクトリー GetControllerInstance を呼び出すため、もちろん MEF は構成に進むことができません。おそらく明らかな何かが欠けていますが、MVC 4 は初めてなので、提案や有用な (MVC4 準拠の) リンクを歓迎します。ありがとう!

重要なコードは次のとおりです。

public static class MefConfig
{
    public static void RegisterMef()
    {
        CompositionContainer コンテナー = ConfigureContainer();

        ControllerBuilder.Current.SetControllerFactory(新しい MefControllerFactory(コンテナ));

        System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver =
            新しい MefDependencyResolver(コンテナ);
    }

    private static CompositionContainer ConfigureContainer()
    {
        //AssemblyCatalog assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());

        DirectoryCatalog カタログ = 新しい DirectoryCatalog(
            HostingEnvironment.MapPath("~/プラグイン"));
        コンポジションコンテナコンテナ=新しいコンポジションコンテナ(カタログ);
        返却コンテナ;
    }
}

パブリック クラス MefDependencyResolver : IDependencyResolver
{
    プライベート読み取り専用CompositionContainer _container;

    public MefDependencyResolver(CompositionContainer コンテナ)
    {
        _container = コンテナ;
    }

    public IDependencyScope BeginScope()
    {
        これを返します。
    }

    パブリック オブジェクト GetService(Type serviceType)
    {
        var export = _container.GetExports(serviceType, null, null).SingleOrDefault();
        return (export != null ? export.Value : null);
    }

    public IEnumerable GetServices(Type serviceType)
    {
        var exports = _container.GetExports(serviceType, null, null);
        リスト createdObjects = new List();

        if (exports.Any())
            createdObjects.AddRange(exports.Select(export => export.Value));

        作成されたオブジェクトを返します。
    }

    public void Dispose()
    {
    }
}

public class MefControllerFactory : DefaultControllerFactory
{
    プライベート読み取り専用CompositionContainer _compositionContainer;

    public MefControllerFactory(CompositionContainer コンポジションコンテナ)
    {
        _compositionContainer = コンポジションコンテナ;
    }

    保護されたオーバーライド IController GetControllerInstance(
        System.Web.Routing.RequestContext requestContext、タイプ controllerType)
    {
        if (controllerType == null) throw new ArgumentNullException("controllerType");
        var export = _compositionContainer.GetExports(controllerType, null, null).SingleOrDefault();

        IController の結果。

        if (null != export) result = export.Value as IController;
        そうしないと
        {
            結果 = base.GetControllerInstance(requestContext, controllerType);
            _compositionContainer.ComposeParts(結果);
        } //うーん

        結果を返します。
    }
}

完全なテスト ソリューションは、次の場所からダウンロードできます。

http://www.filedropper.com/mvcplugins

編集:最初に機能する最小限のソリューション

これが私の調査結果です。このようなものから始める他の初心者に役立つことを願っています:上記の返信で引用されているフレームワークを正常に実行できませんでした.VS2012およびMVC4用に更新する必要があると思います. とにかく、私はコードを見て、もう少しグーグルで検索しました:

1) まず第一に、私にとって混乱の原因は、同じ名前の 2 つの異なるインターフェース IDependencyResolver でした。私がよく理解していれば、1 つ (System.Web.Http.Dependencies.IDependencyResolver) は webapi に使用され、もう 1 つ (System.Web.Mvc.IDependencyResolver) は汎用 DI に使用されます。http://lucid-nonsense.co.uk/dependency-injection-web-api-and-mvc-4-rc/の投稿が役に立ちました。

2) また、3 つ目のコンポーネントは、DefaultControllerFactory から派生したコントローラー ファクトリです。これは、プラグインでホストされるコントローラーに使用されるファクトリであるため、この投稿にとって重要です。

これらすべての実装は、いくつかのサンプルからわずかに変更されています: 最初の HTTP リゾルバー:

パブリック シール クラス MefHttpDependencyResolver : IDependencyResolver
{
    プライベート読み取り専用CompositionContainer _container;

    public MefHttpDependencyResolver(CompositionContainer コンテナ)
    {
        if (container == null) throw new ArgumentNullException("container");
        _container = コンテナ;
    }

    パブリック オブジェクト GetService(Type serviceType)
    {
        if (serviceType == null) throw new ArgumentNullException("serviceType");

        文字列名 = AttributedModelServices.GetContractName(serviceType);

        試す
        {
            return _container.GetExportedValue(名前);
        }
        キャッチ
        {
            null を返します。
        }
    }

    public IEnumerable GetServices(Type serviceType)
    {
        if (serviceType == null) throw new ArgumentNullException("serviceType");

        文字列名 = AttributedModelServices.GetContractName(serviceType);

        試す
        {
            return _container.GetExportedValues(名前);
        }
        キャッチ
        {
            null を返します。
        }
    }

    public IDependencyScope BeginScope()
    {
        これを返します。
    }

    public void Dispose()
    {
    }
}

次に、このシナリオのダミー サンプルには厳密には必要ありませんが、MVC リゾルバーは非常によく似ています。

パブリック クラス MefDependencyResolver : IDependencyResolver
{
    プライベート読み取り専用CompositionContainer _container;

    public MefDependencyResolver(CompositionContainer コンテナ)
    {
        if (container == null) throw new ArgumentNullException("container");
        _container = コンテナ;
    }

    public object GetService(型 type)
    {
        if (type == null) throw new ArgumentNullException("type");

        文字列名 = AttributedModelServices.GetContractName(type);

        試す
        {
            return _container.GetExportedValue(名前);
        }
        キャッチ
        {
            null を返します。
        }
    }

    public IEnumerable GetServices(型 type)
    {
        if (type == null) throw new ArgumentNullException("type");

        文字列名 = AttributedModelServices.GetContractName(type);

        試す
        {
            return _container.GetExportedValues(名前);
        }
        キャッチ
        {
            null を返します。
        }
    }
}

そして最後にコントローラーファクトリー:

[エクスポート(typeof(IControllerFactory))]
public class MefControllerFactory : DefaultControllerFactory
{
    プライベート読み取り専用CompositionContainer _container;

    [コンストラクタのインポート]
    public MefControllerFactory(CompositionContainer コンテナ)
    {
        if (container == null) throw new ArgumentNullException("container");
        _container = コンテナ;
    }

    public override IController CreateController(RequestContext requestContext, string controllerName)
    {
        var コントローラー = _container
            .GetExports()
            .Where(c => c.Metadata.Name.Equals(controllerName, StringComparison.OrdinalIgnoreCase))
            .Select(c => c.Value)
            .FirstOrDefault();

        コントローラーを返す?? base.CreateController(requestContext, controllerName);
    }
}

サンプル コントローラーについては、クラス ライブラリ プロジェクトに作成しました。

[エクスポート(typeof(IController))]
[PartCreationPolicy(CreationPolicy.NonShared)]
[ExportMetadata("名前", "アルファ")]
パブリック シール クラス AlphaController : コントローラ
{
    public ActionResult インデックス()
    {
        ViewBag.Message = "こんにちは、これは PLUGIN コントローラです!";

        ビューを返します();
    }
}

メイン プロジェクト (MVC サイト) には、この DLL をコピーする Plugins フォルダーと、このコントローラーのビュー用のフォルダー内のビューの「標準」セットがあります。

これは考えられる最も単純なシナリオであり、おそらくもっと多くのことを見つけて改良する必要がありますが、最初は単純にする必要がありました。とにかく、どんな提案も大歓迎です。

4

2 に答える 2

2

私は現在、同じ問題に取り組んでいます。私はこの解決策を見つけました:

基本的に、指定された場所からアセンブリをロードし、Web アプリケーションの起動時にいくつかの名前パターンを使用します。

AssemblyInfo.cs:

[assembly: PreApplicationStartMethod(
  typeof(PluginAreaBootstrapper), "Init")]

PluginAreaBootstrapper.cs:

public class PluginAreaBootstrapper
{
    public static readonly List<Assembly> PluginAssemblies = new List<Assembly>();

    public static List<string> PluginNames()
    {
        return PluginAssemblies.Select(
            pluginAssembly => pluginAssembly.GetName().Name)
            .ToList();
    }

    public static void Init()
    {
        var fullPluginPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Areas");

        foreach (var file in Directory.EnumerateFiles(fullPluginPath, "*Plugin*.dll", SearchOption.AllDirectories))
            PluginAssemblies.Add(Assembly.LoadFile(file));

        PluginAssemblies.ForEach(BuildManager.AddReferencedAssembly);

        // Add assembly handler for strongly-typed view models
        AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve;
    }

    private static Assembly AssemblyResolve(object sender, ResolveEventArgs resolveArgs)
    {
        var currentAssemblies = AppDomain.CurrentDomain.GetAssemblies();
        // Check we don't already have the assembly loaded
        foreach (var assembly in currentAssemblies)
        {
            if (assembly.FullName == resolveArgs.Name || assembly.GetName().Name == resolveArgs.Name)
            {
                return assembly;
            }
        }

        return null;
    }
}

しかし、アセンブリを動的にロードできるディレクトリ オブザーバーを作成できるので、Web アプリケーションを再起動する必要さえないと思います。

私の意見では、それはあなたの 1、2、4 のニーズを満たしています。非常にシンプルで、フレームワークを必要とせず、構成が最小限で、プラグインの動的ロードが可能で、MVC 4 で動作します。

このソリューションは、アセンブリを Area ディレクトリにプラグインしますが、ルーティングを使用して好きなように再生するように簡単に調整できると思います。

于 2015-02-12T19:26:57.113 に答える
0

より完全なソリューションはこちらで、その他の背景はこちらで見つけることができます。私たちの要件もDIだったので、これも少し役立ちますが、必要ではありません(最初のリンクはDIのソリューションも提供します。これで頑張ってください。難しいことではありません。また、それらはMVC3用ですが、MVCに簡単に変換/移行できることに注意してください4

于 2012-09-19T10:44:03.603 に答える