Crea una pagina Aspx di istanza del controllo Ascx in una classe back-end senza caricare FilePath

StackOverflow https://stackoverflow.com/questions/1325101

  •  19-09-2019
  •  | 
  •  

Domanda

Domanda: È possibile nel codice back-end (non nel code-behind ma in una vera classe back-end) caricare ed eseguire il rendering di una pagina o di un controllo definito in un .aspx o .ascx senza dover utilizzare Load(path) e invece semplicemente creare un'istanza della classe page/control?

Voglio essere in grado di farlo (da una classe back-end NON da un codice dietro):

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

Invece di questo

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

Dettagli:In una WebApp Asp.net occasionalmente ho bisogno di eseguire il rendering di un controllo utente (.ascx) o di una pagina (.aspx) come stringa HTML.Quando una pagina o un controllo eredita da un code-behind, la sua classe viene visualizzata in Intellisense nel mio codice back-end e posso creare un'istanza e impostare proprietà senza ottenere errori in fase di compilazione o di esecuzione.Tuttavia, quando provo a eseguire il rendering della pagina o del controllo ottengo sempre una stringa vuota e dopo l'ispezione la pagina o il controllo mostra errori di rendering interni soppressi a meno che non carico la pagina o il controllo utilizzando il percorso fisico del file.

Penso che la questione chiave riguardi quando e come i file .aspx/.ascx vengono compilati in fase di esecuzione.Non voglio creare una libreria di classi precompilata di controlli utente perché ciò renderebbe il processo di progettazione scomodo e mi piacciono molto le funzionalità di progettazione offerte dalle pagine .aspx / .ascx e quindi mi piacerebbe trovare un modo per fare in modo che le pagine vengano compilate nella soluzione in modo che siano utilizzabili come qualsiasi altra classe back-end ma possano comunque essere create utilizzando la finestra di progettazione.Voglio che il meglio di entrambi i mondi (1) sia in grado di modificare pagine e controlli nel designer e (2) creare istanze e impostarne le proprietà utilizzando le classi back-end.

È stato utile?

Soluzione

Ecco un approccio che può aiutare in situazioni come questa.

Il codice di "back-end" può non sapere dove si trova il controllo utente, ma il controllo utente non sapere dove si trova.

Quindi, nel controllo utente, aggiungere un metodo statico come questo:

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

(La CustomDto è incluso per illustrare come dati iniziali possono essere passato al controllo utente. Se non c'è bisogno di farlo, tirarla fuori!)

Con questo, il codice che carica il controllo utente fa non ha bisogno di sapere il percorso in cui si trova fisicamente il controllo utente. Se quella posizione cambia mai, quindi aggiornare questa posizione. Tutti gli altri codice che utilizza questo UserControl è invariata.

Nel codice di back-end, o in qualsiasi altro luogo, si può fare qualcosa in questo modo:

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

Altri suggerimenti

In generale: no.

Per quanto ne so, ASP.NET eredita dalle classi di combinare l'/ .ascx modello di aspx con il codice. Questo è il motivo per cui i controlli appaiono vuote: il codice di combinare il modello con il codice è mancante. Questo di solito è fatto da ASP.NET la prima volta che si accede a una pagina o un utente di controllo (è proprio per questo il primo colpo è un po 'lento: in realtà è la generazione e la compilazione del collegamento-code).

Per i siti web precompilati ASP.NET genera questo codice come parte della DLL del tuo sito web precompilato in anticipo, che è il motivo per cui questi siti si caricano più velocemente. Tuttavia, IIRC avrai ancora bisogno di creare un'istanza di generato classi piuttosto che le vostre classi originali.

E 'una richiesta abbastanza comune, ma finora MS non ha fornito gli strumenti per farlo.

Modifica:. Anche se non riesco a capire perché ci si vuole rendere un controllo su una stringa in memoria, potrei avere una soluzione ai problemi di compilazione

Se si bastone per non compilato file (utilizzando il modello di sito web, piuttosto che il modello di applicazione web) ascx, si può effettivamente sviluppare separatamente mettendo fisicamente nella sottocartella del progetto principale, e trattarli come file di contenuto solo. Quindi, si può fare un progetto separato con questa sottocartella come cartella principale. Hai solo bisogno di trattare i file in questa sottocartella come file del sito web, il progetto principale può essere ancora un'applicazione web. (In realtà consigliato, perche' non si desidera che i file Csproj compresi nel progetto principale.)

Tuttavia, codice condiviso (cioè, condiviso tra il progetto di controllo e il progetto principale) dovrebbe essere messo in un progetto di libreria separata, in modo da poter compilare ogni progetto separatamente senza interdipendenze.

Utilizzando LoadControl nell'ambito del progetto principale li compila al volo (code è dietro possibile); se è necessario impostare le proprietà, è necessario però definire le interfacce nel progetto condiviso, implementarli sui controlli utente appropriati e lanciare il controllo creato da LoadControl all'interfaccia appropriata.

Ho sviluppato una soluzione che risolve il mio problema in VS 2008:

  1. Creare la soluzione del sito principale: Creare una soluzione per il sito Web MVC 1 in VS 2008
  2. Crea libreria di classi modello: Aggiungi una libreria di classi per il codice modello
  3. Crea codice di visualizzazione: Aggiungi un "Sito Web vuoto" per contenere le pagine .ascx e aggiungi un riferimento alla libreria del modello
  4. Crea sito di distribuzione: Aggiungi un progetto di distribuzione che compila il "Sito Web vuoto", vai alla "pagina delle proprietà" e Controllo:"Unisci tutti gli output in un unico assieme" e "Considera come componente di libreria" e assicurati di farlo Deseleziona:"Consenti l'aggiornamento di questo sito precompilato"
  5. Output della distribuzione di riferimento: Nel progetto principale aggiungi un riferimento all'output del sito di distribuzione.
  6. ASP.- Controlli compilati: I controlli vengono visualizzati sotto l'ASP.namespace e sono denominati in due modi Un.se la pagina .ascx/aspx non ha dichiarato un "ClassName", vengono denominati utilizzando la cartella e il nome del file con caratteri di sottolineatura, ad es.<%@ Control Language="C#" ClassName="Admin_Index" %> B.se hanno dichiarato il nome di una classe, quello sarà il loro nome

  7. Voce dell'elenco

Utilizzo: Il codice di esempio è riportato di seguito

Ecco un esempio di utilizzo

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

        }
    }

I è venuta in mente una soluzione più semplice lungo le linee di consiglio di Ruben. Ha funzionato senza problemi per circa un mese:

//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();

        }
    }
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top