2008-11-16 16 views
32

Estoy intentando conectar la inyección de dependencia con Windsor a los formularios web asp.net estándar. Creo que lo he logrado usando un HttpModule y un CustomAttribute (código que se muestra a continuación), aunque la solución parece un poco torpe y me preguntaba si hay una mejor solución compatible con Windsor.¿Cómo usar Castle Windsor con los formularios web ASP.Net?

hay varios archivos muestran todos juntos aquí

// 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; } 
    } 
+0

simplemente decir gracias por el código anterior, ya que me permitió crear un marco MVP por algún código de formularios web heredados. –

+0

No hay problema Keith ...me alegro de que podría ser de alguna utilidad para alguien – Xian

+6

probé este código, pero en realidad borra el ViewState en cada solicitud. Parece que si accede a la propiedad Controles de la página actual antes del evento Load, ASP.NET no puede restaurar ViewState durante la etapa LoadViewState entre Init y Load (consulte forums.asp.net/p/1043999/1537884. aspx). Creo que esta es la razón por la cual Ayende usa el evento Init en clases base para Página, MasterPage y UserControl, respectivamente, para resolver cualquier dependencia de IoC. – gabe

Respuesta

16

Creo que está básicamente en el camino correcto - Si usted no tiene ya que sugeriría un vistazo a Rhino iglú, un marco MVC Web Forms, Here's a good blog post on this y la fuente es here - Ayende (el autor de Rhino Igloo) aborda el tema del uso de Windsor con webforms bastante bien en este proyecto/biblioteca.

Guardaría en caché la información de reflexión si va a inyectar todo el conjunto de controles anidados, que podría terminar siendo un poco de un cerdo de rendimiento que sospecho.

Última de todas las spring.net aborda esto de una manera más orientada a la configuración, pero podría valer la pena echar un vistazo a su implementación - aquí hay una buena reference blog post en esto.

+0

Igloo parece un gran ejemplo ... gracias – Xian

-3

en lugar de hacerlo de esta manera, también se puede utilizar un dispositivo de resolución de tipo directamente con algo como:

ILogger Logger = ResolveType.Of<ILogger>(); 
+0

Hola Ryan, gracias por la respuesta ... ¿tienes algún enlace o más información para este ResolveType? – Xian

+12

Esto no es realmente una dependencia _injection_, ¿verdad? :) –

1

Recientemente he comenzado a una empresa donde hay una gran cantidad de aplicaciones de legado formulario, por lo que este parece ser un enfoque realmente interesante, y podría ofrecer un camino a seguir si quisiéramos agregar DI a las páginas web existentes, gracias.

Un punto que noté es que el método de inyección utiliza el contenedor. Resuelva para resolver los componentes explícitamente, por lo tanto, creo que es posible que tengamos que hacer un contenedor. Libere los componentes cuando la página se descargue.

Si tenemos componentes transitorios y no hacemos esto, entonces podemos enfrentar fugas de memoria. No estoy seguro de cómo se comportarían los componentes con el estilo de vida de Per Web Request (es decir, Windsor los recogería al final de la solicitud web, aunque los hayamos resuelto explícitamente), pero también es posible que aquí quiera jugar a salvo.

Por lo tanto, es posible que sea necesario extender el módulo para realizar un seguimiento de los componentes que resuelve y soltarlos para que Windsor sepa cuándo realizar la limpieza.

3

Aquí hay una versión modificada del código del OP que (i) almacena en caché las propiedades inyectadas para evitar llamadas de reflexión repetidas, (ii) libera todos los componentes resueltos, (iii) encapsula el acceso al contenedor para no exponer la implementación.

// 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() 
    { } 

} 
1

Una cosa que le faltaba a las respuestas aceptadas fue el hecho de que el módulo HTTP debe estar registrado en el archivo web.config (dependiendo de la aplicación) antes de que el módulo de hecho resolver las dependencias en el código detrás de las páginas Lo que necesita es:

<system.webServer> 
    <modules> 
     <add name="ClassNameForHttpModuleHere" type="NamespaceForClass"/> 
    </modules> 
    </system.webServer> 

Aparte de eso, las soluciones aceptadas funcionaban como un amuleto.

referencia a la página web de Microsoft para añadir módulos http: https://msdn.microsoft.com/en-us/library/ms227673.aspx

Cuestiones relacionadas