26

Estoy tratando de encontrar la forma de usar la inyección de dependencia con los controles ASP.NET Web Forms.Cómo usar Inyección de dependencia con formularios web ASP.NET

Tengo un montón de controles que crean repositorios directamente, y las usará para acceder a los datos y se unen etc.

Estoy buscando un patrón en el que puedo pasar repositorios a los controles externos (COI), por lo mis controles no son conscientes de cómo se construyen los repositorios y de dónde vienen, etc.

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

(Y para complicar las cosas, estos controles están siendo construidos y ubicados en la página mediante un CMS en tiempo de ejecución!)

¿Alguna idea?

Respuesta

30

Puede utilizar la inyección de constructor automática reemplazando la PageHandlerFactory predeterminada por una personalizada. De esta forma puede usar un constructor sobrecargado para cargar las dependencias. Su página podría tener este aspecto:

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

Configuración personalizada PageHandlerFactory que se puede hacer en el web.config de la siguiente manera:

<?xml version="1.0"?> 
<configuration> 
    <system.web> 
    <httpHandlers> 
     <add verb="*" path="*.aspx" 
     type="YourApp.CustomPageHandlerFactory, YourApp"/> 
    </httpHandlers> 
    </system.web> 
</configuration> 

su CustomPageHandlerFactory puede tener este aspecto:

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 se ejecuta a su lado en plena confianza. Puede leer más al respecto here. Pero tenga en cuenta que el desarrollo de aplicaciones ASP.NET en confianza parcial seems a lost cause.

+0

Hola Steven, tengo algo así implementado en mi proyecto y funciona realmente bien. Pero estoy enfrentando un problema ahora. Se describe aquí "http://stackoverflow.com/questions/15692499/page-routing-in-asp-net-4-0-extensionless-url-versus-pagehandlerfactory-asp". ¿Podría echar un vistazo y quizás compartir una opinión? –

+0

Castle Windsor. No importa, lo resolví Resolviendo de Boostrapper en páginas. No es tan genial, pero bueno, está funcionando y todavía se ve bien. –

+0

Encontré este excelente artículo http://www.codemag.com/Article/1210031 (creo que se vinculó desde otra respuesta SO, pero ahora no puedo encontrar cuál) que incluye más código de ejemplo relacionado con la solución anterior, y también, curiosamente, muestra cómo Microsoft Managed Extensibility Framework (MEF) puede ayudarlo a resolver este y otros problemas similares de inyección de dependencia de una manera muy útil y poco estándar. –

3

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

que inyectará ningún tipo de control que hereda de la clase base (utiliza StructureMap). Combinando eso con una configuración basada en la propiedad, usted será capaz de tener controles como:

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

Actualización 1: Si no se puede tener los controles de heredar, tal vez el CMS tiene un gancho de derecha después de crear los controles , allí puedes 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.

+0

Gracias por la respuesta. El lado perfeccionista de mí quisiera que los controles no tuvieran una dependencia en el marco de ObjectFactory, es decir, la inyección de dependencia pura. Obviamente esto implica algo externo, creando los controles. – Schneider

+0

Re: Actualización 1. Voy a echar un vistazo en el CMS y ver si puedo encontrar algo. Supongo que un problema con la inyección basada en el constructor en ASP.NET es que los controles se vuelven 'indesignables' en ese punto. A menos que el diseñador sepa cómo crearlos. – Schneider

1

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.

4

Autofac supports bastante discreta inyección de dependencia 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 de el evento Init.

0

Ésta es una solución recientemente Solía ​​no se enganche en la tubería (me parece que confunde a todos que se ve en mi código en el futuro, pero sí, veo sus beneficios, así):

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

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 es una especie de relación, pero más en el razonamiento detrás de que se pueden encontrar aquí:

Limit your abstractions: Refactoring toward reduced abstractions

Cuestiones relacionadas