MEF(Managed Extensibility Framework)を使用してAsp.Net WebFormsアプリケーションを開発する

MEFは、拡張可能なアプリケーションを作成するための優れたフレームワークです。 これにより、実装を抽象化から簡単に分離し、アプリケーションの実行中に実装を追加/変更/削除(再構成)し、抽象化の複数の実装を操作し、モノリシックアプリケーションを独立した部分に分離することができます。

MEFの動作方法のほとんどの例は、コンソールまたはWPFアプリケーションです。 なんで? この場合、構成可能な部品の寿命を制御するのが最も簡単だからです。 MEF自体がこれを処理し、開発者は例のタスクに集中します。

Webアプリケーションの状況は根本的に異なります。 開発者はページ、コントロールなどを作成する責任を負いません。 Asp.netランタイムがすべてを処理します。

したがって、WebアプリケーションにMEFサポートを実装するには、両方のツールのアルゴリズムを組み合わせる必要があります。

既知のソリューション


MEFをWebFormsアプリケーションに使用する方法には解決策があります。 ただし、この例にはいくつかの重要な制限があります。

高レベルのアーキテクチャ


ソリューションの目的は、再継承メカニズムを使用せずにMEFコンテナのサポートを実装し、 HttpModuleおよびHttpHandlerサポートを提供することHttpHandler

上記のソリューションを拒否したという事実にもかかわらず、私はそれを基礎として使用します。 これは、ローカル(リクエストごと)とグローバルの2つのコンテナを使用することを意味します。

要求が到着するたびに、asp.netランタイムは要求されたページまたはハンドラーを作成し、すべての依存コントロールを作成します。 ページのすべての部分が作成された直後にインポートされた要素を初期化し、初期化されたすべてのインポートをローカルコンテナに保存することをお勧めします。

一方、 HttpModule 、アプリケーション全体の開始直後に1回作成されます。 したがって、それらのインポートはできるだけ早く実行し、すべてのHttpModuleはグローバルコンテナに格納する必要があります。

実装


ページとコントロール

ページとそのすべての依存関係のインポート操作を実行するには、追加のHttpModuleを使用する必要があります。 このモジュールでは、現在要求されているページのPre_InitおよびInitハンドラーを追加するPre_Initあります。 最初のハンドラーでは、ページ、マスターページ、ユーザーコントロールの構成を実行できます。 Initイベントを使用すると、サーバーコントロールの構成を次のように実行できます。 Pre_Init 、まだ存在しません。

例:
public class ComposeContainerHttpModule : IHttpModule<br>{<br> public void Init(HttpApplication context)<br> {<br> context.PreRequestHandlerExecute += ContextPreRequestHandlerExecute;<br> }<br><br> private void ContextPreRequestHandlerExecute( object sender, EventArgs e)<br> {<br> Page page = HttpContext .Current.CurrentHandler as Page ;<br> if (page != null )<br> {<br> page.PreInit += Page_PreInit;<br> page.Init += Page_Init;<br> }<br> }<br><br> private void Page_Init( object sender, EventArgs e)<br> {<br> Page handler = sender as Page ;<br><br> if (handler != null )<br> {<br> CompositionBatch batch = new CompositionBatch();<br> batch = ComposeWebPartsUtils.BuildUpControls(batch, handler.Controls);<br> ContextualCompositionHost.Container.Compose(batch);<br> }<br> }<br><br> private void Page_PreInit( object sender, EventArgs e)<br> {<br> Page handler = sender as Page ;<br><br> if (handler != null )<br> {<br> CompositionBatch batch = new CompositionBatch();<br> batch = ComposeWebPartsUtils.BuildUp(batch, handler);<br> batch = ComposeWebPartsUtils.BuildUpUserControls(batch, handler.Controls);<br> batch = ComposeWebPartsUtils.BuildUpMaster(batch, handler.Master);<br> ContextualCompositionHost.Container.Compose(batch);<br> }<br> }<br><br> public void Dispose()<br> {<br> }<br>}<br> <br> * This source code was highlighted with Source Code Highlighter .

最初にインポートがページに対して実行され、次にユーザーコントロールとマスターページに対して実行されます。最後に、サーバーコントロールとそのコントロールの再帰関数インポートが実行されます。

BuildUp(CompositionBatch batch, Object o)メソッドは、 Object oインポートされた要素Object oかどうかを確認し、構成用のオブジェクトのリストに追加します。 すべてのコントロールが処理されると、 CompositionBatchオブジェクトを使用してコンテナを初期化できます。 その後、すべてのインポートが初期化され、リクエストの存続期間中利用可能になります。

例:
public static class ComposeWebPartsUtils<br>{<br> public static CompositionBatch BuildUp(CompositionBatch batch, Object o)<br> {<br> ComposablePart part = AttributedModelServices.CreatePart(o);<br><br> if (part.ImportDefinitions.Any())<br> {<br> if (part.ExportDefinitions.Any())<br> throw new Exception( string .Format( "'{0}': Handlers cannot be exportable" , o.GetType().FullName));<br><br> batch.AddPart(part);<br> }<br><br> return batch;<br> }<br><br> public static CompositionBatch BuildUpUserControls(CompositionBatch batch, ControlCollection controls)<br> {<br> foreach (Control c in controls)<br> {<br> if (c is UserControl)<br> batch = ComposeWebPartsUtils.BuildUp(batch, c);<br> batch = BuildUpUserControls(batch, c.Controls);<br> }<br><br> return batch;<br> }<br><br> public static CompositionBatch BuildUpControls(CompositionBatch batch, ControlCollection controls)<br> {<br> foreach (Control c in controls)<br> {<br> batch = ComposeWebPartsUtils.BuildUp(batch, c);<br> batch = BuildUpControls(batch, c.Controls);<br> }<br><br> return batch;<br> }<br><br> public static CompositionBatch BuildUpMaster(CompositionBatch batch, MasterPage master)<br> {<br> if (master != null )<br> batch = BuildUpMaster(ComposeWebPartsUtils.BuildUp(batch, master), master.Master);<br><br> return batch;<br> }<br>}<br> <br> * This source code was highlighted with Source Code Highlighter .

注:
codeplexサイトの例で使用されているのと同じ手法( PageHandlerFactoryを継承し、 GetHandler()メソッドをオーバーライドするGetHandler()使用することはできません。 現時点では、ページのコントロールはまだ作成されていません。

Httphandler

ハンドラーには、すべてのインポートが満たされるイベントはありません。 適切なHandlerFactoryを使用して、コードプレックスの例のページに対して行われたようにGetHandler()メソッドをオーバーライドすることが理想的です。 そして、そのようなクラスは存在しますが( SimpleWebHandlerFactory )、それは内部的なものです。 Microsoftプログラマーがこれを行った理由はわかりませんが、奇妙に見えます。 ページのファクトリはパブリックです。

.netフレームワークに存在する代わりに、独自のファクトリSimpleWebHandlerFactory実装する以外のオプションはありません。 HandlerFactoryの主な目標は、現在の要求に対してインスタンス化する必要がある型を決定することです。 HandlerFactoryは、要求されたリソースの解析を通じてのみ型を取得できます。 そのため、 HttpHandlerコードを解析できるパーサーが必要です。 幸いなことに、そのようなパーサーが存在し( SimpleWebHandlerParser )、パブリックであり、そのためのラッパー( WebHandlerParser )を作成するだけです。

以下は、 HttpHandler構成を作成するアルゴリズムの操作を説明するシーケンス図ですHttpHandler

以下は、上記の機能を実装するクラスのコードです。
public class SimpleWebHandlerFactory : IHttpHandlerFactory<br>{<br> public virtual IHttpHandler GetHandler( HttpContext context, string requestType, string virtualPath, string path)<br> {<br> Type type = WebHandlerParser.GetCompiledType(context, virtualPath, path);<br> if (!( typeof (IHttpHandler).IsAssignableFrom(type)))<br> throw new HttpException( "Type does not implement IHttpHandler: " + type.FullName);<br><br> return Activator.CreateInstance(type) as IHttpHandler;<br> }<br><br> public virtual void ReleaseHandler(IHttpHandler handler)<br> {<br> }<br>}<br><br> internal class WebHandlerParser : SimpleWebHandlerParser<br>{<br> internal WebHandlerParser( HttpContext context, string virtualPath, string physicalPath)<br> : base (context, virtualPath, physicalPath)<br> {<br> }<br><br> public static Type GetCompiledType( HttpContext context, string virtualPath, string physicalPath)<br> {<br> WebHandlerParser parser = new WebHandlerParser(context, virtualPath, physicalPath);<br> Type type = parser.GetCompiledTypeFromCache();<br> if (type != null )<br> return type;<br> else <br> throw new HttpException( string .Format( "File '{0}' is not a web handler." , virtualPath));<br> }<br> <br> protected override string DefaultDirectiveName<br> {<br> get <br> {<br> return "webhandler" ;<br> }<br> }<br>}<br><br> public class ComposableWebHandlerFactory : SimpleWebHandlerFactory<br>{<br> public override IHttpHandler GetHandler( HttpContext context, string requestType, string virtualPath, string path)<br> {<br> IHttpHandler handler = base .GetHandler(context, requestType, virtualPath, path);<br><br> if (handler != null )<br> {<br> CompositionBatch batch = new CompositionBatch();<br> batch = ComposeWebPartsUtils.BuildUp(batch, handler);<br> ContextualCompositionHost.Container.Compose(batch);<br> }<br><br> return handler;<br> }<br>}<br> <br> * This source code was highlighted with Source Code Highlighter .

HttpModule

前述したように、すべてのHttpModulesはアプリケーションの開始時に作成されます。 したがって、アプリケーションの開始直後に構成を作成する必要があります。
例:
public class ScopedContainerHttpModule : IHttpModule<br>{<br> private CompositionContainer _container;<br><br> public void Init(HttpApplication app)<br> {<br> ComposeModules(app);<br> }<br><br> private void ComposeModules(HttpApplication app)<br> {<br> CompositionBatch batch = ComposeWebPartsUtils.BuildUpModules(app);<br> _container.Compose(batch);<br> }<br>}<br><br> public static class ComposeWebPartsUtils<br>{<br> public static CompositionBatch BuildUpModules(HttpApplication app)<br> {<br> CompositionBatch batch = new CompositionBatch();<br><br> for ( int i = 0; i < app.Modules.Count - 1; i++)<br> batch = BuildUp(batch, app.Modules.Get(i));<br><br> return batch;<br> }<br>} <br><br> * This source code was highlighted with Source Code Highlighter .

HttpApplicationオブジェクトを取得し、そこからすべてのモジュールを抽出し、この情報に基づいてグローバルコンテナーに入力します。

そのため、ソリューションが機能するように、WebFormsプロジェクトのweb.configファイルに数行を追加する必要があります。
< httpHandlers > <br> ......<br> < remove path ="*.ashx" verb ="*" /> <br> < add path ="*.ashx" verb ="*" type ="common.composition.WebExtensions.ComposableWebHandlerFactory, common.composition" /> <br> ......<br> </ httpHandlers > <br> < httpModules > <br> < add name ="ContainerCreator" type ="common.composition.WebExtensions.ScopedContainerHttpModule, common.composition" /> <br> ......<br> < add name ="ComposeContainerModule" type ="common.composition.WebExtensions.ComposeContainerHttpModule, common.composition" /> <br> </ httpModules > <br><br> * This source code was highlighted with Source Code Highlighter .

デフォルトで存在する* .ashxファイルハンドラーを削除し、鉱山( ComposableWebHandlerFactory )を追加します。

ローカルおよびグローバルコンテナのインフラストラクチャを作成および初期化するContainerCreatorモジュールを追加しています。

ComposeContainerModuleは、ローカルコンテナを初期化するために使用されます。

以上です!

コントロールの継承を使用する必要はなく、メインプログラムに追加のコードを記述します。 このソリューションは、Webフォームに基づく任意のWebプロジェクトに追加でき、表示されるすべての機能が利用可能になるようにweb.configのみを更新する必要があります。


サンプルのWebFomsAndMefから少し変更を加えたデモを使用しています。

各アプリケーション要素は、 [PartCreationPolicy(CreationPolicy.NonShared)]属性を持つSampleCompositionPartクラスをインポートします。これにより、インポート動作中にSampleCompositionPart新しいインスタンスSampleCompositionPartれます。 このクラスには、Guidが返す単一のIdフィールドが含まれます。

アプリケーションは、現在表示されている要素(ページ、コントロール、ユーザーコントロール、ページマスターHttpHandlerおよびHttpModuleのGuid値を表示します。

例:
///PageSample.aspx <br><asp:Content ID= "BodyContent" runat= "server" ContentPlaceHolderID= "MainContent" ><br> <% =SamplePart.Id %><br></asp:Content><br> <br> ///PageSample.cs <br> public partial class PageSample : System.Web.UI. Page <br>{<br> [Import]<br> public SampleCompositionPart SamplePart<br> {<br> get ;<br> set ;<br> }<br>} <br><br> * This source code was highlighted with Source Code Highlighter .


ソースコードはこちらからダウンロードできます

この記事が、asp.net WebFormsアプリケーションでのMEFの使用開始に役立つことを願っています。

Source: https://habr.com/ru/post/J106762/


All Articles