友達をASP.NETコントロールとDIコンテナーにする方法

イントロ

最近、ASP.NETの知識を少し更新することに決めました。これに関連して、マークアップ制御コード(* .ascx、* .aspx)の生成プロセスに深く入り、伝えたいことについて非常に興味深い決定を下せることに気付きました。 そこで、今日は、コントロールによって生成されたコードを使用して、Dependency Injectionコンテナーで友達を作る方法を学びます。

行こう

DependencyInjection Microsoft UnityはDIコンテナとして機能しますが、これは重要ではありません。DIに関係するすべてのものは、使用されるコンテナに依存しません。 問題はこれです。依存関係を挿入し、Service Locatorのサービスを使用して関心のある依存関係を管理するASP.NETコントロールがいくつかあります。 Microsoft Unityには、多くの労力をかけずにこれを行うツールがいくつかあります。おおよそ次のように、興味のあるコントロールのプロパティに挿入できます。
  1. 依存属性で必要なプロパティをマークします
    パブリック クラス MyControl :UserControl
    {
    [依存関係]
    公開 MyPresenterプレゼンター
    {
    get { return _presenter; }
    セット
    {
    _presenter = value ;
    _presenter.View = this ;
    }
    }
    }

  2. コントロールは次のように初期化できます
    保護された オーバーライド void OnInit (EventArgs e)
    {
    base .OnInit(e);
    _ontainer.BuildUp(GetType()、 this );
    }

  3. アプリケーションでコンテナの場所を管理するには、これにHttpApplicationを使用し、それを継承し、 global.asaxファイルに小さな変更を加えることをお勧めします。コンテナに必要なストレージを取得し、このように処理する必要があります((Sapphire.Application)HttpContext.Current .ApplicationInstance).Container

ソリューションは非常に適していますが、純粋な意見ではこの段階でソリューションを残していません。特に、このようなアプローチはUnityから絞り出すことができるものからは程遠いため、プロパティの注入をコンストラクターへの注入に置き換えるだけでよいと思います。 つまり 私たちの関心は、MyUserControlクラスを次のように見せることです(ページビルダーはあまり気に入らないと思います)
パブリック クラス MyControl :UserControl
{
パブリック MyControl (MyPresenterプレゼンター)
{
_presenter =プレゼンター;
_presenter.View = this ;
}
}

これを行うことを提案します。 ページレイアウトで説明されているコントロールが、ページを生成するときにパラメーターなしでコンストラクターを示しているという事実から始めましょう。最初にweb.configで調べて、このプロセスをどのように制御できるか疑問に思います。
<buildProviders>
<add extension = ".aspx" type = "System.Web.Compilation.PageBuildProvider" />
<add extension = ".ascx" type = "System.Web.Compilation.UserControlBuildProvider" />
...
</ buildProviders>

ただし、PageBuildProviderの実装はかなり深刻なタスクであり、深刻なニーズのために延期することを考えています。 ただし、たとえば、BuildProvidersのおかげで、データアクセスレイヤーを生成できます。そのためには、以下を行う必要があります。* .dalなどの拡張機能のハンドラーを記述して登録し、 http://www.codeproject.com/のような操作を行うKB / aspnet / DALComp.aspx、ところで、同様のロジックはSubSonic http://dotnetslackers.com/articles/aspnet/IntroductionToSubSonic.aspxに実装されています。ジェネリック型http://stackoverflow.com/questions/1480373/genericからのページ継承の興味深い実装でもあります新規作成-ビューページを--inhertiedと-性は制限があるかどうか、例外、データ・オブジェクトなどを生成するために、例えば、まだ可能です あなたの想像力です。 一般に、このオプションは私たちには適さず、何かをより簡単にする必要があり、ControlBuilder属性を使用して、マークアップからコントロールをアセンブルするためのコントロールロジックを指定できる優れたソリューションがあります。
[ControlBuilder(typeof(MyControlBuilder))]
パブリック クラス UserControl :System.Web.UI.UserControl
{
}

次に、MyControlBuilderの実装を扱います。この型はControlBuilderから継承し、ProcessGeneratedCodeオーバーロードの助けを借りて、コントロールの属性なしでコンストラクターを呼び出す代わりにコードを使用するようコレクターに指示できます。
パブリック オーバーライド void ProcessGeneratedCode (CodeCompileUnit codeCompileUnit、
CodeTypeDeclaration baseType、
CodeTypeDeclarationderivedType、
CodeMemberMethod buildMethod、
CodeMemberMethod dataBindingMethod)
{
codeCompileUnit.Namespaces [ 0 ] .Imports.Add( new CodeNamespaceImport( "Sapphire.Web.UI" ));
ReplaceConstructorWithContainerResolveMethod(buildMethod);
base .ProcessGeneratedCode(codeCompileUnit、baseType、derivedType、buildMethod、dataBindingMethod);
}

最も興味深いことに、ReplaceConstructorWithContainerResolveMethodメソッドを非表示にします
private void ReplaceConstructorWithContainerResolveMethod (CodeMemberMethod buildMethod)
{
foreach (buildMethod.StatementsのCodeStatementステートメント)
{
var assign =ステートメントas CodeAssignStatement;

ifnull !=割り当て)
{
var constructor = assign.Right as CodeObjectCreateExpression;

ifnull !=コンストラクター)
{
assign.Right =
新しい CodeSnippetExpression
string .Format( "SapphireControlBuilder.Build <{0}>()"
ControlType.FullName));
休憩 ;
}
}
}
}

コードに従って、コンストラクター呼び出しをBuildジェネリックメソッド呼び出しに置き換えます。この呼び出しでは、コントロールを呼び出して必要な依存関係を使用してコンストラクターを初期化する要求でコンテナーに戻ります。 ただし、これはタスクの解決策ではありません。 Page.LoadControl()コントロールを動的にロードするためのメソッドがあります;それには、独自のバージョンのpublic static クラス PageExtensionsを記述する必要があります
{
public static UserControl LoadAndBuildUpControlこのページページ、 文字列 virtualPath)
{
var control = page.LoadControl(virtualPath);
return SapphireControlBuilder.Build <UserControl>(control.GetType());
}
}

そのため、私たちはこのタスクに対処しましたが、それだけではありません。 Unityを今すぐ最大限に活用し、 Unity Interceptionを使用してランタイムAOPコントロールに挿入してみませんか? たとえば、次のパブリック クラス MyControlを実行できます。UserControl
{
[HandleException]
public Override void DataBind ()
{
base .DataBind();
}
}

これは、実行時に変更する機会を与えることに加えて、例外処理をオンザフライで追加する必要があることを意味します。開始のために、次のように実装します。 [AttributeUsage(AttributeTargets.Method | )]
パブリック クラス HandleExceptionAttribute :HandlerAttribute
{
パブリック オーバーライド ICallHandler CreateHandler (IUnityContainerコンテナー)
{
新しい ExceptionHandler ()を返します。
}
}

パブリック クラス ExceptionHandler :ICallHandler
{
/// <exception cref = "SapphireUserFriendlyException"> <c> SapphireUserFriendlyException </ c>。</ exception>
public IMethodReturn Invoke (IMethodInvocation入力、GetNextHandlerDelegate getNext)
{
var result = getNext()(input、getNext);
if (result.Exception == null
結果を返す ;
新しい SapphireUserFriendlyException ()をスローします。
}

public int Order { get ; セット ; }
}

そしてもちろん、プロキシハンドラを作成するようにコンテナを構成する必要がありますpublic static T Build <T>()
{
return (T)((Application)HttpContext.Current.ApplicationInstance)
。コンテナ
。 AddNewExtension <インターセプション>()
。<Interception>の構成()
.SetInterceptorFor <T>( 新しい VirtualMethodInterceptor())
。コンテナ
.Resolve <T>();
}



資源

Sapphire.Application-すべて実装されたものについてhttp://github.com/butaji/Sapphire/tree/master/trunk/Sapphire.Application/

Davidは、同様のアプローチに基づいた「Databinding 3.0」の次世代データバインディング実装を提供していますhttp://weblogs.asp.net/davidfowler/archive/2009/11/13/databinding-3-0.aspx

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


All Articles