MEFは、拡張可能なアプリケーションを作成するための優れたフレームワークです。 これにより、実装を抽象化から簡単に分離し、アプリケーションの実行中に実装を追加/変更/削除(再構成)し、抽象化の複数の実装を操作し、モノリシックアプリケーションを独立した部分に分離することができます。
MEFの動作方法のほとんどの例は、コンソールまたはWPFアプリケーションです。 なんで? この場合、構成可能な部品の寿命を制御するのが最も簡単だからです。 MEF自体がこれを処理し、開発者は例のタスクに集中します。
Webアプリケーションの状況は根本的に異なります。 開発者はページ、コントロールなどを作成する責任を負いません。 Asp.netランタイムがすべてを処理します。
したがって、WebアプリケーションにMEFサポートを実装するには、両方のツールのアルゴリズムを組み合わせる必要があります。
既知のソリューション
MEFをWebFormsアプリケーションに使用する方法に
は解決策があります。 ただし、この例にはいくつかの重要な制限があります。
- 開発者は、すべてのコントロール、ページ、およびユーザーコントロールを再継承する必要がありますが、これは常に可能ではありません
- この例は、
HttpModule
およびHttpHandler
をサポートするソリューションを提供していません
高レベルのアーキテクチャ
ソリューションの目的は、再継承メカニズムを使用せずに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の使用開始に役立つことを願っています。