26

Sto cercando di trovare un modo per utilizzare l'integrazione delle dipendenze con i controlli Web Form di ASP.NET.Come utilizzare l'iniezione delle dipendenze con Web Form ASP.NET

ho avuto un sacco di controlli che creano repository direttamente, e usare quelli per l'accesso e l'associazione a dati, ecc

Cerco un modello in cui posso passare repository ai controlli dall'esterno (IOC), in modo da i miei controlli rimangono inconsapevoli di come vengono creati i repository e da dove provengono ecc.

Preferirei non avere una dipendenza dal contenitore IoC dai miei controlli, quindi voglio solo essere in grado di costruire i controlli con il costruttore o iniezione di proprietà.

(E tanto per complicare le cose, questi controlli sono in costruzione e poste sulla pagina da un CMS in fase di esecuzione!)

Qualche idea?

risposta

30

È possibile utilizzare l'iniezione automatica del costruttore sostituendo il valore predefinito PageHandlerFactory con uno personalizzato. In questo modo è possibile utilizzare un costruttore sovraccarico per caricare le dipendenze. La tua pagina potrebbe essere simile a questo:

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

Configurazione che personalizzato PageHandlerFactory può essere fatto nel web.config come segue:

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

tuo CustomPageHandlerFactory può assomigliare a questo:

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

Il lato negativo è che funziona solo quando si esegue il proprio lato in Full Trust. Puoi leggere di più su di esso here. Ma si noti che lo sviluppo di applicazioni ASP.NET in trust parziale seems a lost cause.

+0

Ciao Steven, ho implementato qualcosa di simile nel mio progetto e funziona davvero bene. Ma ora sto affrontando un problema. È descritto qui "http://stackoverflow.com/questions/15692499/page-routing-in-asp-net-4-0-extensionless-url-versus-pagehandlerfactory-asp". Potresti dare un'occhiata e magari condividere qualche opinione? –

+0

Castello Windsor. Non importa, l'ho risolto risolvendo da Boostrapper nelle pagine. Non è bello, ma hey, funziona e sembra ancora bello. –

+0

Ho trovato questo eccellente articolo http://www.codemag.com/Article/1210031 (penso collegato da un'altra risposta SO, ma ora non riesco a trovare quale) che include più codice di esempio relativo alla soluzione di cui sopra, e inoltre, interessante, mostra come Microsoft Managed Extensibility Framework (MEF) può aiutare a risolvere questo e problemi simili di iniezione di dipendenza in modo molto utile e leggermente non standard. –

3

Il modo migliore è quello di avere una classe base per i controlli come:

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

Questo inietta qualsiasi controllo che eredita da tale classe base (utilizza StructureMap). La combinazione di che con una configurazione base di proprietà, si sarà in grado di avere controlli come:

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

Update 1: Se non è possibile avere i controlli ereditano, forse il CMS ha un gancio destro dopo aver creato i controlli , lì puoi chiamare il BuildUp. Inoltre, se il CMS consente di agganciare qualcosa per recuperare l'istanza, è possibile utilizzare l'iniezione basata sul costruttore, ma preferisco BuildUp in questo specifico scenario in quanto asp.net non dispone di un hook per questo.

+0

Grazie per la risposta. Il lato perfezionista di me vorrebbe che i controlli non avessero una dipendenza dal framework ObjectFactory, vale a dire un'iniezione di dipendenza pura. Ovviamente questo implica qualcosa di esterno, creando i controlli. – Schneider

+0

Oggetto: Aggiornamento 1. Farò un giro nel CMS e vedrò se riesco a trovare qualcosa. Immagino che un problema con l'iniezione basata su costruttore in ASP.NET sia che i controlli diventano "non selezionabili" a quel punto. A meno che il progettista non sappia come costruirli. – Schneider

1

È inoltre possibile creare alcune istanze Singleton nell'evento global_asci_Evento Application_Start e renderle disponibili come proprietà statiche pubbliche di sola lettura.

4

Autofac supports Iniezione di dipendenza abbastanza discreta nei WebForm di ASP.NET. La mia comprensione è che si aggancia al ciclo di vita della pagina ASP.NET usando un modulo http e fa l'iniezione di proprietà. L'unico problema è che per i controlli non penso che ciò accada fino a dopo l' l'evento Init.

0

Si tratta di una soluzione di recente ho usato a non rimanere agganciati nella pipeline (trovo che confonde tutti che guarda il mio codice in futuro, ma sì, vedo i suoi benefici pure):

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

Questo mostra come è possibile creare il proprio Life Time Manager abbastanza facilmente e collegarsi ad un contenitore IoC se lo si desidera. Oh, e sto anche utilizzando una struttura/comando di interrogazione, che è una specie di estraneo, ma più sul ragionamento dietro che può essere trovato qui:

Limit your abstractions: Refactoring toward reduced abstractions

Problemi correlati