Come usare Castle Windsor con i moduli web ASP.Net?

Sto cercando di colbind l’iniezione di dipendenza con Windsor ai moduli web standard di asp.net. Penso di aver raggiunto questo objective utilizzando un HttpModule e un CustomAttribute (codice mostrato sotto), anche se la soluzione sembra un po ‘grossolana e mi chiedevo se c’è una soluzione migliore supportata fuori dalla scatola con Windsor?

Ci sono diversi file tutti mostrati insieme qui

// 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(); } public IWindsorContainer Container { get { return _container; } } } public interface IoCProvider { IWindsorContainer Container { get; } } 

Penso che tu sia fondamentalmente sulla buona strada – Se non lo avessi già suggerirei di dare un’occhiata a Rhino Igloo, un framework MVF di WebForms, Ecco un buon post sul blog su questo e la fonte è qui – Ayende (l’autore di Rhino Igloo) affronta il tema dell’uso di Windsor con webforms abbastanza bene in questo progetto / libreria.

Metterei in cache le informazioni di riflessione se intendi iniettare l’intero set di controlli annidati, che potrebbe essere un po ‘un maiale da prestazione che sospetto.

Infine, spring.net si avvicina a questo in un modo più orientato alla configurazione, ma potrebbe valere la pena dare un’occhiata alla loro implementazione: ecco un buon post di blog di riferimento su questo.

Ecco una versione modificata del codice dell’OP che (i) memorizza nella cache le proprietà iniettate per evitare ripetute chiamate di riflessione, (ii) rilascia tutti i componenti risolti, (iii) incapsula l’accesso al contenitore in modo da non esporre l’implementazione.

 // 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 InjectedProperties = new ConcurrentDictionary(); 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(); 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 ResolvedComponents { get { return (List)HttpContext.Current.Items["ResolvedComponents"]; } set { HttpContext.Current.Items["ResolvedComponents"] = value; } } public void Dispose() { } } 

Recentemente ho iniziato a lavorare in un’azienda dove ci sono molte app legacy webform, quindi questo sembra essere un approccio davvero interessante e potrebbe offrire una soluzione se volessimo aggiungere DI alle pagine web esistenti, grazie.

Un punto che ho notato è che il metodo Injection utilizza il container. Risolvi la risoluzione dei componenti in modo esplicito, quindi penso che potremmo aver bisogno di fare un contenitore. Rilasciare i componenti quando la pagina Scarica.

Se abbiamo componenti transitori e non lo facciamo, potremmo dover affrontare perdite di memoria. Non sono sicuro di come si comporterebbero i componenti con stili di vita Per richiesta Web (ad esempio, Windsor li preleverebbe alla fine della richiesta web, anche se li risolvemmo esplicitamente), ma anche qui potrebbe voler giocare sicuro.

Pertanto, potrebbe essere necessario estendere il modulo per tenere traccia dei componenti che risolve e rilasciarli in modo che Windsor sappia quando pulire.

Una cosa che mancava dalle risposte accettate era il fatto che il modulo http deve essere registrato nel file web.config (a seconda dell’applicazione) prima che il modulo risolva effettivamente le dipendenze sulle pagine code-behind. Quello di cui hai bisogno è:

      

Oltre a questo le soluzioni accettate hanno funzionato come un fascino.

Riferimento al sito Web Microsoft per l’aggiunta di moduli http: https://msdn.microsoft.com/en-us/library/ms227673.aspx

Piuttosto che farlo in questo modo, potresti anche usare un resolver di tipo direttamente con qualcosa di simile:

 ILogger Logger = ResolveType.Of();