ASP.NETWebフォームコントロールで依存性注入を使用する方法を見つけようとしています。
リポジトリを直接作成し、それらを使用してデータなどにアクセスしてバインドするコントロールがたくさんあります。
リポジトリを外部のコントロール(IoC)に渡すことができるパターンを探しているので、コントロールはリポジトリがどのように構築され、どこから来たのかなどを認識していません。
コントロールからIoCコンテナーに依存したくないので、コンストラクターまたはプロパティインジェクションを使用してコントロールを構築できるようにしたいだけです。
(そして、物事を複雑にするために、これらのコントロールは実行時にCMSによって構築され、ページに配置されています!)
何かご意見は?
UPDATE 2019:Webフォーム4.7.2の導入により、DIのサポートが改善されました。これにより、以下が無効になります。参照: 。NET 4.7.2のWebFormsでSimple Injectorを配線する
デフォルトのPageHandlerFactory
をカスタムのものに置き換えることで、自動コンストラクターインジェクションを使用できます。このようにして、オーバーロードされたコンストラクターを使用して依存関係をロードできます。ページは次のようになります。
public partial class HomePage : System.Web.UI.Page
{
private readonly IDependency dependency;
public HomePage(IDependency dependency)
{
this.dependency = dependency;
}
// Do note this protected ctor. You need it for this to work.
protected HomePage () { }
}
そのカスタムPageHandlerFactory
の構成は、web.configで次のように実行できます。
<?xml version="1.0"?>
<configuration>
<system.web>
<httpHandlers>
<add verb="*" path="*.aspx"
type="YourApp.CustomPageHandlerFactory, YourApp"/>
</httpHandlers>
</system.web>
</configuration>
CustomPageHandlerFactory
は次のようになります。
public class CustomPageHandlerFactory : PageHandlerFactory
{
private static object GetInstance(Type type)
{
// TODO: Get instance using your favorite DI library.
// for instance using the Common Service Locator:
return Microsoft.Practices.ServiceLocation
.ServiceLocator.Current.GetInstance(type);
}
public override IHttpHandler GetHandler(HttpContext cxt,
string type, string vPath, string path)
{
var page = base.GetHandler(cxt, type, vPath, path);
if (page != null)
{
// Magic happens here ;-)
InjectDependencies(page);
}
return page;
}
private static void InjectDependencies(object page)
{
Type pageType = page.GetType().BaseType;
var ctor = GetInjectableCtor(pageType);
if (ctor != null)
{
object[] arguments = (
from parameter in ctor.GetParameters()
select GetInstance(parameter.ParameterType)
.ToArray();
ctor.Invoke(page, arguments);
}
}
private static ConstructorInfo GetInjectableCtor(
Type type)
{
var overloadedPublicConstructors = (
from constructor in type.GetConstructors()
where constructor.GetParameters().Length > 0
select constructor).ToArray();
if (overloadedPublicConstructors.Length == 0)
{
return null;
}
if (overloadedPublicConstructors.Length == 1)
{
return overloadedPublicConstructors[0];
}
throw new Exception(string.Format(
"The type {0} has multiple public " +
"ctors and can't be initialized.", type));
}
}
欠点は、これが完全な信頼であなたの側を実行しているときにのみ機能することです。それについてもっと読むことができます ここ 。ただし、部分的な信頼でASP.NETアプリケーションを開発することに注意してください 失われた原因のようです 。
Autofacはサポートします ASP.NETWebFormsでのかなり目立たない依存性注入。私の理解では、httpモジュールを使用してASP.NETページのライフサイクルにフックし、プロパティインジェクションを実行します。唯一の落とし穴は、コントロールの場合、これはInitイベントのafterまで発生しないと思うことです。
最良の方法は、次のようなコントロールの基本クラスを用意することです。
public class PartialView : UserControl
{
protected override void OnInit(System.EventArgs e)
{
ObjectFactory.BuildUp(this);
base.OnInit(e);
}
}
これにより、その基本クラスから継承する(structuremapを使用する)コントロールが挿入されます。これをプロパティベースの構成と組み合わせると、次のようなコントロールを使用できるようになります。
public partial class AdminHeader : PartialView
{
IMyRepository Repository{get;set;}
}
更新1:コントロールを継承させることができない場合は、コントロールを作成した直後にCMSにフックがあり、そこでBuildUpを呼び出すことができます。また、CMSでインスタンスをフェッチするために何かをフックできる場合は、コンストラクターベースのインジェクションを使用できますが、asp.netにはこのためのフックがないため、この特定のシナリオではBuildUpを使用することをお勧めします。
Application_Start global.asaxイベントでいくつかのシングルトンインスタンスを作成し、それらをパブリック静的読み取り専用プロパティとして使用できるようにすることもできます。
これは、パイプラインへのフックを回避するために最近使用したソリューションです(将来、私のコードを見るすべての人を混乱させることがわかりますが、そうです、その利点もわかります):
public static class TemplateControlExtensions
{
static readonly PerRequestObjectManager perRequestObjectManager = new PerRequestObjectManager();
private static WIIIPDataContext GetDataContext(this TemplateControl templateControl)
{
var dataContext = (WIIIPDataContext) perRequestObjectManager.GetValue("DataContext");
if (dataContext == null)
{
dataContext = new WIIIPDataContext();
perRequestObjectManager.SetValue("DataContext", dataContext);
}
return dataContext;
}
public static IMailer GetMailer(this TemplateControl templateControl)
{
return (IMailer)IoC.Container.Resolve(typeof(IMailer));
}
public static T Query<T>(this TemplateControl templateControl, Query<T> query)
{
query.DataContext = GetDataContext(templateControl);
return query.GetQuery();
}
public static void ExecuteCommand(this TemplateControl templateControl, Command command)
{
command.DataContext = GetDataContext(templateControl);
command.Execute();
}
private class PerRequestObjectManager
{
public object GetValue(string key)
{
if (HttpContext.Current != null && HttpContext.Current.Items.Contains(key))
return HttpContext.Current.Items[key];
else
return null;
}
public void SetValue(string key, object newValue)
{
if (HttpContext.Current != null)
HttpContext.Current.Items[key] = newValue;
}
}
}
これは、独自のライフタイムマネージャーを非常に簡単に作成し、必要に応じてIoCコンテナーにフックする方法を示しています。ああ、私も一種の無関係なクエリ/コマンド構造を使用していますが、その背後にある理由の詳細はここにあります: