如何在ASP.NET Web窗体中使用dependency injection
我正在试图找出一种方法来使用ASP.NET Web窗体控件的dependency injection。
我有很多直接创build存储库的控件,并使用它们来访问和绑定数据等。
我正在寻找一种模式,我可以将库传递给控件(IoC),所以我的控件仍然不知道库的构build方式以及它们来自哪里等等。
我不希望从我的控件有一个依赖的IoC容器,因此我只是想能够构造具有构造函数或属性注入的控件。
(只是为了使事情复杂化,这些控件正在构build,并在运行时由CMS放置在页面上!)
有什么想法吗?
您可以使用自动构造函数注入来replace默认的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 () { } }
configuration该自定义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.NET WebForms中支持相当不显眼的dependency injection。 我的理解是,它只是挂钩到ASP.NET页面生命周期使用http模块,并进行属性注入。 唯一的问题是,对于控制,我认为这不会发生在 Init事件之后。
最好的办法是有一个像这样的控件的基类:
public class PartialView : UserControl { protected override void OnInit(System.EventArgs e) { ObjectFactory.BuildUp(this); base.OnInit(e); } }
这将注入从该基类inheritance的任何控制(使用结构图)。 结合基于属性的configuration,你将能够拥有如下的控制:
public partial class AdminHeader : PartialView { IMyRepository Repository{get;set;} }
更新1:如果你不能控制inheritance,也许CMS创build控件后有一个钩子,在那里你可以调用BuildUp。 另外,如果CMS允许你钩住一些东西来获取实例,你可以使用基于构造函数的注入,但是我更喜欢BuildUp这个特定的场景,因为asp.net没有这个钩子。
您也可以在Application_Start global.asax事件中创build一些单例实例,并使它们可用作公共静态只读属性。
这是我最近使用的一种解决scheme,以避免陷入stream水线(我发现,将来会看到我的代码的每个人都感到困惑,但是,我也看到了它的好处):
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; } } }
这显示了如何很容易地创build自己的生活时间pipe理器,以及如果你愿意的话挂钩到IoC容器。 噢,我也使用了一个不相干的查询/命令结构,但更多的背后的推理可以在这里find:
限制你的抽象:重构抽象