2009-08-24 8 views
0

Question: Est-il possible dans le code back-end (pas dans le code derrière mais dans une classe d'arrière réelle) pour charger et rendre une page ou un contrôle défini dans .aspx ou .ascx sans avoir à utiliser Load (chemin) et créer simplement une instance de la classe page/control?Créer instance Aspx Page ASCX Contrôle Dans une classe Aval sans chargement CheminFichier

Je veux être en mesure de le faire (d'une classe d'arrière pas un code derrière):

MyControl myCtl = new MyApp.Views.Shared.MyControl(); 
String html = Util.ControlToString(myCtl); //I get an empty string & hidden errors 

au lieu de cette

public static string ControlToString(string path) 
{ 
    Page pageHolder = new Page(); 
    MyControl myCtl = (MyControl)pageHolder.LoadControl(path); 
    pageHolder.Controls.Add(myCtl); 
    StringWriter output = new StringWriter(); 
    HttpContext.Current.Server.Execute(pageHolder, output, false); 
    return output.ToString(); 
} 

Détails: Dans un Asp. netApp Web J'ai parfois besoin de rendre un contrôle utilisateur (.ascx) ou une page (.aspx) en tant que chaîne HTML. Lorsqu'une page ou un contrôle hérite d'un code, sa classe apparaît dans intellisense dans mon code de retour et je peux créer une instance et définir des propriétés sans avoir d'erreurs de compilation ou d'exécution. Toutefois, lorsque j'essaie de rendre la page ou le contrôle, j'obtiens toujours une chaîne vide et lors de l'inspection, la page ou le contrôle affiche des erreurs de rendu internes supprimées sauf si je charge la page ou le contrôle en utilisant son chemin physique.

Je pense que le problème clé a à voir avec quand & comment les fichiers .aspx/.ascx sont compilés à l'exécution. Je ne veux pas créer une bibliothèque de commandes utilisateur pré-compilée, car cela rendrait le processus de conception difficile et j'apprécie beaucoup les fonctionnalités de concepteur offertes par les pages .aspx/.ascx, et j'adorerais trouver un moyen de faites que les pages soient compilées dans la solution de manière à ce qu'elles soient utilisables comme n'importe quelle autre classe back-end mais peuvent encore être créées en utilisant le concepteur. Je veux le meilleur des deux mondes (1) pour pouvoir éditer des pages et des contrôles dans le concepteur et (2) créer des instances et placer leurs propriétés en utilisant des classes arrières.

Répondre

1

Voici une approche qui peut aider dans des situations comme celle-ci.

Le code «back-end» peut ne pas savoir où se trouve le contrôle utilisateur, mais le contrôle utilisateur sait où il se trouve.

Ainsi, dans le contrôle de l'utilisateur, ajoutez une méthode statique comme ceci: Si vous n'avez pas besoin

public partial class MyControl : UserControl 
{ 
    ... 
    public static MyControl LoadControl(CustomDto initialData) 
    { 
    var myControl = 
     (MyControl) 
     ((Page) HttpContext.Current.Handler) 
     .LoadControl("~\\UserControlsSecretPath\\MyControl.ascx"); 
    myControl._initialData = initialData; 
    return myControl; 
    } 
    ... 
    private CustomDto _initialData; 
} 

(Le CustomDto est inclus pour illustrer la façon dont les données initiales peuvent être transmises au contrôle d'utilisateur. faites-le, retirez-le!)

Avec ceci, le code qui charge le contrôle utilisateur pas besoin de connaître le chemin d'accès où le contrôle utilisateur est physiquement situé. Si cet emplacement change, mettez à jour cet emplacement. Tout autre code qui utilise ce UserControl est inchangé.

Dans votre code back-end, ou nulle part ailleurs, vous pouvez faire quelque chose ceci:

var control = MyControl.LoadControl(customDto); 
PlaceHolder1.Controls.Add(control); 
+0

J'aime cette approche car elle place les chaînes du chemin du fichier magique près de l'endroit où elles sont utilisées et rend les exigences d'initialisation explicites dans l'appel LoadControl. J'avais fait le chargement indirectement en utilisant des génériques, puis en définissant les propriétés par la suite, ce qui rend les erreurs plus faciles et place les chaînes du chemin du fichier magique plus loin de l'endroit où elles sont réellement nécessaires. – Glenn

0

En général: non. Autant que je sache, ASP.NET hérite de de vos classes pour combiner le modèle .aspx/.ascx avec votre code. C'est pourquoi vos contrôles apparaissent vides: le code pour combiner le modèle avec votre code est manquant. Cela est généralement effectué par ASP.NET la première fois que vous accédez à une page ou à un contrôle utilisateur (c'est précisément la raison pour laquelle le premier hit est un peu lent: il génère et compile le code de branchement).

Pour les sites Web précompilés, ASP.NET génère à l'avance ce code dans le fichier .dll de votre site Web précompilé, ce qui explique pourquoi ces sites se chargent plus rapidement. Toutefois, IIRC vous devrez toujours instancier les classes générées plutôt que vos classes d'origine.

C'est une demande plutôt courante, mais jusqu'ici MS n'a pas fourni les outils pour le faire.

Modifier: Bien que je ne vois pas pourquoi vous voudriez rendre un contrôle à une chaîne en mémoire, je pourrais avoir une solution aux problèmes de construction. Si vous respectez des fichiers .ascx non compilés (en utilisant le modèle de site Web plutôt que le modèle d'application Web), vous pouvez les développer séparément en les plaçant physiquement dans le sous-dossier de votre projet principal et les traiter comme du contenu. fichiers seulement. Ensuite, vous pouvez créer un projet distinct avec ce sous-dossier en tant que dossier racine. Vous devez seulement traiter les fichiers de ce sous-dossier comme des fichiers de site Web, le projet principal peut toujours être une application Web. (En fait recommandé, parce que vous ne voulez pas le.Toutefois, le code partagé (c'est-à-dire partagé entre le projet controls et le projet principal) doit être placé dans un projet de bibliothèque distinct, vous permettant de compiler chaque projet séparément sans interdépendance. L'utilisation de LoadControl dans le projet principal les compilera à la volée (le code derrière est possible);

Si vous devez définir des propriétés, vous devez toutefois définir des interfaces dans le projet partagé, les implémenter sur les contrôles utilisateur appropriés et convertir le contrôle créé par LoadControl vers l'interface appropriée.

+0

j'avais peur que la réponse était. Je pense à mettre en place un flux de travail avec des contrôles compilés. Ai-je raison qu'une bibliothèque de classes compilées me permette de faire ce que je veux? Des conseils sur les meilleures pratiques pour utiliser une bibliothèque comme ça dans une application plus grande? Merci! – Glenn

+0

Ma pratique actuelle dans VisualStudio consiste à ajouter un "EmptyWebSite" dans la solution qui contient mes WebApp & Tests. Je crée mes pages et contrôles dans ce site Web vide, puis référence la DLL publiée dans mon site principal qui doit les activer. Le tout est trop de tracas parce que je dois publier manuellement mettre à jour la DLL de la bibliothèque de classes. Je pense juste à utiliser load (controlPath) pour simplifier les choses – Glenn

+0

Je ne sais pas si ça aide, mais j'ai peut-être une solution pour les problèmes de construction. (Réponse originale éditée.) – Ruben

0

J'ai développé une solution qui résout mon problème dans VS 2008:

  1. Créer principal site Solution: Créer un MVC 1 solution de site web en VS 2008
  2. Créer un modèle Bibliothèque de classes: Ajouter une bibliothèque de classes pour le code modèle
  3. Créer Afficher le code: Ajouter un "site Web vide" pour contenir les pages .ascx et ajouter une référence à la bibliothèque de modèles
  4. Créer un site de déploiement: Ajouter un projet de déploiement qui compile le "Site Web vide" dans la "page de propriétés" et Vérifiez: "Fusionner toutes les sorties dans un seul assemblage" et "Traiter comme composant de bibliothèque" et assurez-vous à Désactivez l'option: « Autoriser ce site précompilé à être actualisable »
  5. sortie de déploiement référence: Dans le projet principal ajouter une référence à la sortie du site de déploiement.
  6. ASP. - Contrôles compilés: Les contrôles apparaissent sous l'ASP. namespace et sont nommés de deux façons A. si la page .ascx/aspx n'a pas déclaré de "ClassName", ils sont nommés en utilisant leur dossier et leur nom de fichier avec des underscores ex. <% @ Control Language = "C#" ClassName = "admin_index" %> B. si elles ne déclarent un nom de classe c'est leur nom

  7. Liste item

Utilisation: Exemple le code est ci-dessous

Voici un exemple d'utilisation

public ActionResult Index() 
{ 
    var ctl = new ASP.Directory_FirmProfile(); //create an instance 
    ctl.Setup(new MyDataModel);     //assign data 

    //string test = CompiledControl.Render(ctl); //output to string 
    return new HtmlCtl.StrongView(ctl);   //output to response 
}  



    public class CompiledControl 
    { 
     public static string Render(Control c) 
     { 
      Page pageHolder = new Page(); 
      pageHolder.Controls.Add(c); 
      StringWriter output = new StringWriter(); 
      HttpContext.Current.Server.Execute(pageHolder, output, false); 
      return output.ToString(); 
     } 

     public static void Render(Control c, StringWriter output) 
     { 
      Page pageHolder = new Page(); 
      pageHolder.Controls.Add(c); 
      HttpContext.Current.Server.Execute(pageHolder, output, false); 
     } 

     public static void Render(Control c, HttpResponseBase r) 
     { 
      Page pageHolder = new Page(); 
      pageHolder.Controls.Add(c); 
      HttpContext.Current.Server.Execute(pageHolder, r.Output, false); 
     } 


    } 


    public class StrongView : ActionResult 
    { 
     private Control ctl; 
     public StrongView(Control ctl) 
     { 
      this.ctl = ctl; 
     } 

     public string VirtualPath{get;set;} 


     public override void ExecuteResult(ControllerContext context) 
     { 
      if (context == null) 
       throw new ArgumentNullException("context"); 

      HtmlCtl.CompiledControl.Render(ctl, context.HttpContext.Response); 

     } 
    } 
+0

Après avoir utilisé ma solution pendant quelques semaines, je recommanderais de ne pas le faire. Les étapes de compilation supplémentaires et les bugs occasionnels pèsent largement sur les petits avantages de cette méthode. – Glenn

0

Je suis venu w C'est une solution plus simple dans le sens des conseils de Ruben. Il a travaillé sans problème pendant environ un mois:

//Example usage 

//reference the control 
var emailCTL = new HtmlCtl.ControlOnDisk<MyControlType>(@"~\Views\EmailTemplates\MyControlType.ascx"); 

//if you have a code behind you will get intellisense allowing you to set these properties 
// and re-factoring support works most places except the template file. 
emailCTL.c.title = "Hello World "; //title is a property in the code behind 
emailCTL.c.data = data; //data is a property in the code behind 

string emailBody = emailCTL.RenderStateless(); 



//Helper Class 
    public class ControlOnDisk<ControlType> where ControlType : UserControl 
    { 
     public ControlType c; 
     Page pageHolder = new Page(); 
     public ControlOnDisk(string path) 
     { 
      var o = pageHolder.LoadControl(path); 
      c = (ControlType)o; 
      pageHolder.Controls.Add(c); 
     } 

     public string RenderStateless() 
     { 

      StringWriter output = new StringWriter(); 

      // set up dumby context for use in rendering to email stream 
      StringBuilder emailMessage = new StringBuilder(); 
      TextWriter tw = new StringWriter(emailMessage); 
      HttpResponse dumbyResponse = new HttpResponse(tw); 
      HttpRequest dumbyRequest = new HttpRequest("", "http://InsertURL.com/", ""); //dummy url requierd for context but not used 
      HttpContext dumbyContext = new HttpContext(dumbyRequest, dumbyResponse); 
      //HttpContextBase dumbyContextBase = new HttpContextWrapper2(dumbyContext); 

      dumbyContext.Server.Execute(pageHolder, output, false); 
      return output.ToString(); 

     } 
    } 
Questions connexes