Cómo usar la Inyección de Dependencia con formularios Web ASP.NET

Estoy tratando de encontrar la forma de usar la dependency injection con los controles ASP.NET Web Forms.

Tengo muchos controles que crean repositorys directamente y los uso para acceder y vincular datos, etc.

Estoy buscando un patrón en el que pueda pasar repositorys a los controles de forma externa (IoC), por lo que mis controles no son conscientes de cómo se construyen los repositorys, de dónde vienen, etc.

Preferiría no tener una dependencia en el contenedor IoC de mis controles, por lo tanto, solo quiero poder construir los controles con la inyección de propiedad o constructor.

(Y para complicar las cosas, un CMS está construyendo y colocando estos controles en la página en tiempo de ejecución).

¿Alguna idea?

Puede usar la inyección automática del constructor reemplazando el PageHandlerFactory predeterminado por uno personalizado. De esta forma puede usar un constructor sobrecargado para cargar las dependencias. Tu página puede verse así:

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

La configuración de ese PageHandlerFactory personalizado se puede hacer en el web.config de la siguiente manera:

 < ?xml version="1.0"?>        

Su CustomPageHandlerFactory puede verse así:

 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)); } } 

Lo malo es que esto solo funciona cuando ejecutas tu lado en Full Trust. Puedes leer más sobre esto aquí . Pero tenga en cuenta que desarrollar aplicaciones ASP.NET en confianza parcial parece una causa perdida .

Autofac admite una dependency injection bastante discreta en ASP.NET WebForms. Según tengo entendido, simplemente se engancha en el ciclo de vida de la página ASP.NET utilizando un módulo http y se inyecta la propiedad. La única pega es que para los controles no creo que esto ocurra hasta después del evento Init.

La mejor manera es tener una clase base para los controles como:

 public class PartialView : UserControl { protected override void OnInit(System.EventArgs e) { ObjectFactory.BuildUp(this); base.OnInit(e); } } 

Eso inyectará cualquier control que herede de esa clase base (usa structuremap). Combinando eso con una configuración basada en propiedades, podrás tener controles como:

 public partial class AdminHeader : PartialView { IMyRepository Repository{get;set;} } 

Actualización 1: si no puede hacer que los controles hereden, quizás el CMS tenga un enlace justo después de crear los controles, allí puede llamar a BuildUp. Además, si el CMS te permite conectar algo para buscar la instancia, puedes usar la inyección basada en el constructor, pero prefiero BuildUp en este escenario específico ya que asp.net no tiene un gancho para esto.

También podría crear algunas instancias únicas en el evento Application_Start global.asax y tenerlas disponibles como propiedades públicas de solo lectura estáticas.

A partir de .NET 4.7.2 ( novedades ), ahora es fácil para los desarrolladores utilizar Dependency Injection en aplicaciones de WebForms. Con UnityAdapter, puede agregarlo a su aplicación WebForms existente en 4 sencillos pasos. Mira este blog

Esta es una solución que utilicé recientemente para evitar engancharme a la tubería (me parece que eso confunde a todos los que miran mi código en el futuro, pero sí, veo sus beneficios también):

 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(this TemplateControl templateControl, Query 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; } } } 

Esto muestra cómo puede crear su propio administrador de tiempo de vida con bastante facilidad, así como engancharlo en un contenedor IoC si lo desea. Ah, y también estoy usando una estructura de consulta / comando que no está relacionada, pero más sobre el razonamiento detrás de eso se puede encontrar aquí:

Limite sus abstracciones: Refactorización hacia abstracciones reducidas