26

J'essaie de trouver un moyen d'utiliser l'injection de dépendance avec les contrôles ASP.NET Web Forms.Comment utiliser l'injection de dépendance avec ASP.NET Web Forms

J'ai eu beaucoup de contrôles qui créent des dépôts directement, et les utiliser pour accéder et se lier aux données etc.

Je cherche un modèle où je peux passer des dépôts aux contrôles externes (CIO), donc mes contrôles ne savent pas comment les dépôts sont construits et d'où ils viennent, etc.

Je préférerais ne pas avoir de dépendance sur le conteneur IoC de mes contrôles, donc je veux juste pouvoir construire les contrôles avec le constructeur ou injection de propriété.

(Et pour compliquer les choses, ces contrôles sont construits et placés sur la page par un CMS lors de l'exécution!)

Toute pensée?

Répondre

30

Vous pouvez utiliser l'injection automatique de constructeur en remplaçant la valeur par défaut PageHandlerFactory par une valeur personnalisée. De cette façon, vous pouvez utiliser un constructeur surchargé pour charger les dépendances. Votre page peut ressembler à ceci:

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

Configuration personnalisée que PageHandlerFactory peut être fait dans le web.config comme suit:

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

Votre CustomPageHandlerFactory peut ressembler à ceci:

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

Inconvénient est que cela ne fonctionne que lorsque vous exécutez votre côté en confiance totale. Vous pouvez en savoir plus à ce sujet here. Mais notez que le développement d'applications ASP.NET en confiance partielle seems a lost cause.

+0

Salut Steven, j'ai quelque chose comme ça implémenté dans mon projet et ça marche vraiment bien. Mais je suis confronté à un problème maintenant. Il est décrit ici "http://stackoverflow.com/questions/15692499/page-routing-in-asp-net-4-0-extensionless-url-versus-pagehandlerfactory-asp". Pourriez-vous s'il vous plaît jeter un oeil et peut-être partager une opinion? –

+0

Château Windsor. Peu importe, je l'ai résolu en résolvant de Boostrapper dans les pages. Ce n'est pas cool, mais bon, ça marche et ça a l'air bien. –

+0

J'ai trouvé cet excellent article http://www.codemag.com/Article/1210031 (je pense lié d'une autre réponse SO, mais maintenant je ne peux pas trouver lequel) qui comprend plus d'exemple de code lié à la solution ci-dessus, et De plus, il est intéressant de noter que Microsoft MEaged Extensibility Framework (MEF) peut vous aider à résoudre ce problème d'injection de dépendance et d'autres similaires de manière très utile et légèrement non standard. –

3

La meilleure façon est d'avoir une classe de base pour les contrôles comme:

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

qui injectera tout contrôle qui hérite de cette classe de base (utilise StructureMap). La combinaison de cela avec une configuration à base de propriété, vous serez en mesure d'avoir des contrôles comme:

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

Mise à jour 1: Si vous ne pouvez pas avoir les contrôles hériter, peut-être le CMS a un crochet droit après avoir créé les contrôles , là vous pouvez appeler le BuildUp. De plus, si le CMS vous permet d'accrocher quelque chose pour récupérer l'instance, vous pouvez utiliser l'injection basée sur le constructeur, mais je préfère BuildUp sur ce scénario spécifique car asp.net n'a pas de hook pour cela.

+0

Merci pour la réponse. Le côté perfectionniste de moi aimerait que les contrôles ne soient pas dépendants du framework ObjectFactory, c'est-à-dire de l'injection de dépendance pure. Évidemment, cela implique quelque chose d'extérieur, créant les contrôles. – Schneider

+0

Re: Mise à jour 1. Je vais faire un tour dans le CMS et voir si je peux trouver quelque chose. Je suppose qu'un problème avec l'injection basée sur constructeur dans ASP.NET est que les contrôles deviennent "indesignable" à ce moment-là. À moins que le concepteur sache comment les construire. – Schneider

1

Vous pouvez également créer des instances singleton dans l'événement global Application.Start Start_Start et les rendre disponibles en tant que propriétés readonly statiques publiques.

4

Autofac supports injection de dépendance relativement discrète dans les formulaires Web ASP.NET. Ma compréhension est que cela se connecte au cycle de vie de la page ASP.NET en utilisant un module http et fait l'injection de propriété. Le seul hic est que pour les contrôles, je ne pense pas que cela arrive avant après l'événement Init.

0

Ceci est une solution que je récemment utilisé pour éviter l'accrochage dans le pipeline (je trouve cela brouille tout le monde qui regarde mon code dans l'avenir, mais oui, je vois ses avantages aussi bien):

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

Cela montre comment vous pouvez créer votre propre gestionnaire de temps de vie assez facilement ainsi que le crochet dans un conteneur IoC si vous le désirez. Oh, et j'utilise également une structure requête/commande qui est en quelque sorte sans rapport, mais plus sur le raisonnement derrière qui se trouve ici:

Limit your abstractions: Refactoring toward reduced abstractions