I am trying to wire up dependency injection with Windsor to standard asp.net web forms. I think I have achieved this using a HttpModule and a CustomAttribute (code shown below), although the solution seems a little clunky and was wondering if there is a better supported solution out of the box with Windsor?
我正在尝试将依赖注入与温莎连接到标准的asp.net web表单。我认为我已经使用HttpModule和CustomAttribute(如下所示的代码)实现了这一点,尽管解决方案看起来有点笨拙,并且想知道是否有一个更好的支持解决方案与温莎一起?
There are several files all shown together here
这里显示了几个文件
// index.aspx.cs
public partial class IndexPage : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Logger.Write("page loading");
}
[Inject]
public ILogger Logger { get; set; }
}
// WindsorHttpModule.cs
public class WindsorHttpModule : IHttpModule
{
private HttpApplication _application;
private IoCProvider _iocProvider;
public void Init(HttpApplication context)
{
_application = context;
_iocProvider = context as IoCProvider;
if(_iocProvider == null)
{
throw new InvalidOperationException("Application must implement IoCProvider");
}
_application.PreRequestHandlerExecute += InitiateWindsor;
}
private void InitiateWindsor(object sender, System.EventArgs e)
{
Page currentPage = _application.Context.CurrentHandler as Page;
if(currentPage != null)
{
InjectPropertiesOn(currentPage);
currentPage.InitComplete += delegate { InjectUserControls(currentPage); };
}
}
private void InjectUserControls(Control parent)
{
if(parent.Controls != null)
{
foreach (Control control in parent.Controls)
{
if(control is UserControl)
{
InjectPropertiesOn(control);
}
InjectUserControls(control);
}
}
}
private void InjectPropertiesOn(object currentPage)
{
PropertyInfo[] properties = currentPage.GetType().GetProperties();
foreach(PropertyInfo property in properties)
{
object[] attributes = property.GetCustomAttributes(typeof (InjectAttribute), false);
if(attributes != null && attributes.Length > 0)
{
object valueToInject = _iocProvider.Container.Resolve(property.PropertyType);
property.SetValue(currentPage, valueToInject, null);
}
}
}
}
// Global.asax.cs
public class Global : System.Web.HttpApplication, IoCProvider
{
private IWindsorContainer _container;
public override void Init()
{
base.Init();
InitializeIoC();
}
private void InitializeIoC()
{
_container = new WindsorContainer();
_container.AddComponent<ILogger, Logger>();
}
public IWindsorContainer Container
{
get { return _container; }
}
}
public interface IoCProvider
{
IWindsorContainer Container { get; }
}
5 个解决方案
#1
16
I think you're basically on the right track - If you have not already I would suggest taking a look at Rhino Igloo, an WebForms MVC framework, Here's a good blog post on this and the source is here - Ayende (the Author of Rhino Igloo) tackles the issue of using Windsor with webforms quite well in this project/library.
基本上我认为你是正确的,如果您还没有我建议看看犀牛屋,WebForms MVC框架,这里有一个很好的博客文章在这这里的来源是,Ayende犀牛屋(作者)解决的问题在这个项目使用温莎使用WebForms很好/库。
I would cache the reflection info if you're going to inject the entire nested set of controls, that could end up being a bit of a performance hog I suspect.
如果要注入整个嵌套的控件集,我将缓存反射信息,我怀疑这可能会造成性能负担。
Last of all spring.net approaches this in a more configuration-oriented way, but it might be worth taking a look at their implementation - here's a good reference blog post on this.
最后,spring.net以一种更面向配置的方式来实现这一点,但是值得一看它们的实现——这里有一篇关于这一点的很好的参考博客文章。
#2
3
Here's a modified version of the OP's code that (i) caches injected properties to avoid repeated reflection calls, (ii) releases all resolved components, (iii) encapsulates container access so as not to expose implementation.
这是OP代码的修改版本(i)缓存注入的属性以避免重复的反射调用,(ii)释放所有已解析的组件,(iii)封装容器访问以避免暴露实现。
// global.asax.cs
public class Global : HttpApplication
{
private static IWindsorContainer _container;
protected void Application_Start(object sender, EventArgs e)
{
_container = new WindsorContainer();
_container.Install(FromAssembly.This());
}
internal static object Resolve(Type type)
{
return _container.Resolve(type);
}
internal static void Release(object component)
{
_container.Release(component);
}
//...
}
// WindsorHttpModule.cs
public class WindsorHttpModule : IHttpModule
{
// cache the properties to inject for each page
private static readonly ConcurrentDictionary<Type, PropertyInfo[]> InjectedProperties = new ConcurrentDictionary<Type, PropertyInfo[]>();
private HttpApplication _context;
public void Init(HttpApplication context)
{
_context = context;
_context.PreRequestHandlerExecute += InjectProperties;
_context.EndRequest += ReleaseComponents;
}
private void InjectProperties(object sender, EventArgs e)
{
var currentPage = _context.Context.CurrentHandler as Page;
if (currentPage != null)
{
InjectProperties(currentPage);
currentPage.InitComplete += delegate { InjectUserControls(currentPage); };
}
}
private void InjectUserControls(Control parent)
{
foreach (Control control in parent.Controls)
{
if (control is UserControl)
{
InjectProperties(control);
}
InjectUserControls(control);
}
}
private void InjectProperties(Control control)
{
ResolvedComponents = new List<object>();
var pageType = control.GetType();
PropertyInfo[] properties;
if (!InjectedProperties.TryGetValue(pageType, out properties))
{
properties = control.GetType().GetProperties()
.Where(p => p.GetCustomAttributes(typeof(InjectAttribute), false).Length > 0)
.ToArray();
InjectedProperties.TryAdd(pageType, properties);
}
foreach (var property in properties)
{
var component = Global.Resolve(property.PropertyType);
property.SetValue(control, component, null);
ResolvedComponents.Add(component);
}
}
private void ReleaseComponents(object sender, EventArgs e)
{
var resolvedComponents = ResolvedComponents;
if (resolvedComponents != null)
{
foreach (var component in ResolvedComponents)
{
Global.Release(component);
}
}
}
private List<object> ResolvedComponents
{
get { return (List<object>)HttpContext.Current.Items["ResolvedComponents"]; }
set { HttpContext.Current.Items["ResolvedComponents"] = value; }
}
public void Dispose()
{ }
}
#3
1
I've recently started at a company where there are a lot of legacy webform apps, so this looks to be a real interesting approach, and could offer a way forward if we wanted to add DI to existing web pages, thanks.
我最近刚开始在一家公司工作,那里有很多遗留的webform应用程序,所以这看起来是一个非常有趣的方法,如果我们想在现有的web页面中添加DI,可以提供一种方法,谢谢。
One point I noticed is that the Injection method uses the container.Resolve to explicitly resolve components, therefore I think we may need to do a container.Release on the components when the Page Unloads.
我注意到一点,注入方法使用容器。解析以显式地解析组件,因此我认为我们可能需要做一个容器。在页面卸载时释放组件。
If we have transient components and don't do this then we may face memory leakages. Not sure how components with Per Web Request lifestyles would behave (i.e. would Windsor pick them up at the end of the web request, even though we explicitly resolved them) but here too may want to play safe.
如果我们有暂态元件而不这样做,那么我们可能会面临内存泄漏。不确定具有每个Web请求生活方式的组件的行为(例如,温莎会在Web请求结束时接收它们,即使我们显式地解析它们),但是这里也可能希望安全。
Therefore the module may need to be extended to keep track of the components that it resolves and release them so that Windsor knows when to clean up.
因此,可能需要对模块进行扩展,以跟踪它解析的组件并释放它们,以便温莎知道何时清理。
#4
1
One thing that was missing from the accepted answers was the fact that the http module needs to be registered in the web.config file (depending on the application) before the module will actually resolve the dependencies on the code-behind pages. What you need is :
从已接受的答案中缺失的一件事是http模块需要在web中注册。配置文件(取决于应用程序)在模块实际解析代码后页上的依赖项之前。你需要的是:
<system.webServer>
<modules>
<add name="ClassNameForHttpModuleHere" type="NamespaceForClass"/>
</modules>
</system.webServer>
Other than that the accepted solutions worked like a charm.
除此之外,被接受的解决方案就像一种魅力。
Reference to the Microsoft website for adding http modules: https://msdn.microsoft.com/en-us/library/ms227673.aspx
引用微软网站添加http模块:https://msdn.microsoft.com/en-us/library/ms227673.aspx
#5
-3
Rather than doing it like this, you could also use a type resolver directly with something like:
与其这样做,不如直接使用类型解析器,比如:
ILogger Logger = ResolveType.Of<ILogger>();
#1
16
I think you're basically on the right track - If you have not already I would suggest taking a look at Rhino Igloo, an WebForms MVC framework, Here's a good blog post on this and the source is here - Ayende (the Author of Rhino Igloo) tackles the issue of using Windsor with webforms quite well in this project/library.
基本上我认为你是正确的,如果您还没有我建议看看犀牛屋,WebForms MVC框架,这里有一个很好的博客文章在这这里的来源是,Ayende犀牛屋(作者)解决的问题在这个项目使用温莎使用WebForms很好/库。
I would cache the reflection info if you're going to inject the entire nested set of controls, that could end up being a bit of a performance hog I suspect.
如果要注入整个嵌套的控件集,我将缓存反射信息,我怀疑这可能会造成性能负担。
Last of all spring.net approaches this in a more configuration-oriented way, but it might be worth taking a look at their implementation - here's a good reference blog post on this.
最后,spring.net以一种更面向配置的方式来实现这一点,但是值得一看它们的实现——这里有一篇关于这一点的很好的参考博客文章。
#2
3
Here's a modified version of the OP's code that (i) caches injected properties to avoid repeated reflection calls, (ii) releases all resolved components, (iii) encapsulates container access so as not to expose implementation.
这是OP代码的修改版本(i)缓存注入的属性以避免重复的反射调用,(ii)释放所有已解析的组件,(iii)封装容器访问以避免暴露实现。
// global.asax.cs
public class Global : HttpApplication
{
private static IWindsorContainer _container;
protected void Application_Start(object sender, EventArgs e)
{
_container = new WindsorContainer();
_container.Install(FromAssembly.This());
}
internal static object Resolve(Type type)
{
return _container.Resolve(type);
}
internal static void Release(object component)
{
_container.Release(component);
}
//...
}
// WindsorHttpModule.cs
public class WindsorHttpModule : IHttpModule
{
// cache the properties to inject for each page
private static readonly ConcurrentDictionary<Type, PropertyInfo[]> InjectedProperties = new ConcurrentDictionary<Type, PropertyInfo[]>();
private HttpApplication _context;
public void Init(HttpApplication context)
{
_context = context;
_context.PreRequestHandlerExecute += InjectProperties;
_context.EndRequest += ReleaseComponents;
}
private void InjectProperties(object sender, EventArgs e)
{
var currentPage = _context.Context.CurrentHandler as Page;
if (currentPage != null)
{
InjectProperties(currentPage);
currentPage.InitComplete += delegate { InjectUserControls(currentPage); };
}
}
private void InjectUserControls(Control parent)
{
foreach (Control control in parent.Controls)
{
if (control is UserControl)
{
InjectProperties(control);
}
InjectUserControls(control);
}
}
private void InjectProperties(Control control)
{
ResolvedComponents = new List<object>();
var pageType = control.GetType();
PropertyInfo[] properties;
if (!InjectedProperties.TryGetValue(pageType, out properties))
{
properties = control.GetType().GetProperties()
.Where(p => p.GetCustomAttributes(typeof(InjectAttribute), false).Length > 0)
.ToArray();
InjectedProperties.TryAdd(pageType, properties);
}
foreach (var property in properties)
{
var component = Global.Resolve(property.PropertyType);
property.SetValue(control, component, null);
ResolvedComponents.Add(component);
}
}
private void ReleaseComponents(object sender, EventArgs e)
{
var resolvedComponents = ResolvedComponents;
if (resolvedComponents != null)
{
foreach (var component in ResolvedComponents)
{
Global.Release(component);
}
}
}
private List<object> ResolvedComponents
{
get { return (List<object>)HttpContext.Current.Items["ResolvedComponents"]; }
set { HttpContext.Current.Items["ResolvedComponents"] = value; }
}
public void Dispose()
{ }
}
#3
1
I've recently started at a company where there are a lot of legacy webform apps, so this looks to be a real interesting approach, and could offer a way forward if we wanted to add DI to existing web pages, thanks.
我最近刚开始在一家公司工作,那里有很多遗留的webform应用程序,所以这看起来是一个非常有趣的方法,如果我们想在现有的web页面中添加DI,可以提供一种方法,谢谢。
One point I noticed is that the Injection method uses the container.Resolve to explicitly resolve components, therefore I think we may need to do a container.Release on the components when the Page Unloads.
我注意到一点,注入方法使用容器。解析以显式地解析组件,因此我认为我们可能需要做一个容器。在页面卸载时释放组件。
If we have transient components and don't do this then we may face memory leakages. Not sure how components with Per Web Request lifestyles would behave (i.e. would Windsor pick them up at the end of the web request, even though we explicitly resolved them) but here too may want to play safe.
如果我们有暂态元件而不这样做,那么我们可能会面临内存泄漏。不确定具有每个Web请求生活方式的组件的行为(例如,温莎会在Web请求结束时接收它们,即使我们显式地解析它们),但是这里也可能希望安全。
Therefore the module may need to be extended to keep track of the components that it resolves and release them so that Windsor knows when to clean up.
因此,可能需要对模块进行扩展,以跟踪它解析的组件并释放它们,以便温莎知道何时清理。
#4
1
One thing that was missing from the accepted answers was the fact that the http module needs to be registered in the web.config file (depending on the application) before the module will actually resolve the dependencies on the code-behind pages. What you need is :
从已接受的答案中缺失的一件事是http模块需要在web中注册。配置文件(取决于应用程序)在模块实际解析代码后页上的依赖项之前。你需要的是:
<system.webServer>
<modules>
<add name="ClassNameForHttpModuleHere" type="NamespaceForClass"/>
</modules>
</system.webServer>
Other than that the accepted solutions worked like a charm.
除此之外,被接受的解决方案就像一种魅力。
Reference to the Microsoft website for adding http modules: https://msdn.microsoft.com/en-us/library/ms227673.aspx
引用微软网站添加http模块:https://msdn.microsoft.com/en-us/library/ms227673.aspx
#5
-3
Rather than doing it like this, you could also use a type resolver directly with something like:
与其这样做,不如直接使用类型解析器,比如:
ILogger Logger = ResolveType.Of<ILogger>();