Peter Bucher - Mein Experiment, meine Spielereien, meine Welt...   ·   Stefan Falz   ·   Jürgen Gutsch   ·   Golo Roden   ·   ASP.NET Zone   ·   Microsoft ASP.NET
Willkommen bei ASP.NET Zone. Anmelden | Registrieren | Hilfe

Masterpage dynamisch per Code umschalten

Masterpages sind eine wirklich tolle Neuerung von ASP.NET 2.0.
Um so mehr entfaltet sich die Wirkung, wenn die Masterpage dynamisch gewechselt werden kann.

Dabei muss beachtet werden, das die betreffenden Content Controls genau zu den ContentPlaceHolder Controls der Masterpages passen. Sowie das auch zur der Designzeit der Fall sein muss. D.h. die Platzhalter und Platzfüller bei allen Masterpages und Contentseiten übereinstimmen müssen, da es ansonsten zu Fehlern kommt.
Wenn die Masterpage dynamisch zur Laufzeit geändert werden soll, muss das im PreInit Ereignis geschehen.


Zitat aus der MSDN:

Die MasterPageFile-Eigenschaft ist der Name der dieser Seite zugeordneten Masterseitendatei. Die MasterPageFile-Eigenschaft kann nur im PreInit-Ereignis festgelegt werden. Bei einem Versuch, die MasterPageFile-Eigenschaft nach dem PreInit-Ereignis festzulegen, wird eine InvalidOperationException-Ausnahme ausgelöst.

 

Die Lösung ist also relativ simpel.
Um die Masterpage auszuwechseln, überschreiben wir die Methode OnPreInit, die fast am Anfang des Page Zyklus ausgeführt wird.

In diesem Fall wird überprüft ob der GET Parameter "print" den Wert "true" enthält. Falls ja, wird die Druckansicht Masterpage geladen. Andernfalls bleibt die zur Designzeit vergebene Masterpage gesetzt.

protected override void OnPreInit(EventArgs e)
{
    if (this.Request.QueryString["print"] == "true")
        this.MasterPageFile = "~/Masterpages/Print.Master";


    base.OnPreInit(e);
}

 

Alles schön und gut, das ist jetzt noch ein wenig statisch, nicht?

Wenn wir aufgrund von Ereignissen die von Controls ausgelöst werden, die Masterpage umschalten wollten, wirds schon ein wenig schwieriger.

Grundsätzlich haben wir in der OnPreInit Methode Zugriff auf die Request.QueryString und Request.Form Collection.
Was wir aber nicht haben, ist eine erstellte Control Hierarchie. Per Request.Form benötigen wir laut Identifizierung von Controls: Control.ID / .ClientID / .UniqueID die UniqueID des Controls.

Da wir diese aufgrund der fehlenden Controlhierarchie nicht haben, bleibt entweder der Weg des hardcodieren der ID, oder aber einen anderen Weg.
Das Hauptproblem ist ja, das OnPreInit vor allen anderen Ereignissen aufgerufen wird, deshalb können wir auch nicht in einem Button Klick, oder SelectedIndexChanged Ereignishandler, die Masterpage im selben Request umstellen.

Einen möglichen Weg ist mit Sessions und Response.Redirect() zu beschreiten. Wenn die Masterpage in der Session gespeichert ist, bleibt die eingestellte Masterpage für die Länge der Sitzung in diesem Zustand.
Wir können ohne Probleme in einem Button Klick Handler die betreffende Session Variable auf die gewünschte Masterpage setzen und dann ein Response.Redirect("seite.aspx"); aufrufen, um die Seite nochmals zu laden.
Es ist nicht wirklich schön, da wir die Seite mit dieser Methode noch einmal aufrufen müssen.
Der wirklich grosse Nachteil dieser Methode ist aber leider, dass der ViewState wegen Response.Redirect() verloren geht.

Noch einen anderen Weg geht direkt über Request.Form[], da wir den Namen des betroffenen Controls nicht haben (ausser hartcodiert), bleibt uns nur noch den Weg über den Index, was aber auch fehleranfällig ist.

Einen weiteren Weg möglichen Weg wird im Blog von Langleyben Leon beschrieben, er speichert beim ersten Laden der Seite im Page_Load Eventhandler die UniqueID des betreffenden Controls in eine Session.

 

Ich habe überlegt, wie das am besten zu lösen wäre. Einen anderen Weg musste es ja noch geben.
Ein Control dafür entwickeln fällt schon mal aus, da der Lebenszyklus eines Controls erst nach OnPreInit anfängt.
Das naheliegenste ist schlussendlich, das eine Basisklasse für die jeweiligen Content Seiten erstellt wird, auf denen die Masterpages auswechselbar sein sollen.

So haben wir in den überschriebenen Methoden OnPreInit und OnLoad Zugriff auf alles was wir brauchen, um den Wechsel durchzuführen.
Leons Idee (Siehe Artikel oben), die UniqueID des betreffenden Controls beim ersten Seitenaufruf zu speichern, hat mich inspiriert und ich habe ein wenig weitergesponnen.

Die Basisklasse für die ContentPage übernimmt die gesamte Arbeit, um die Masterpage zu wechseln.
Die ContentPage muss lediglich von der Basisklasse "PageMasterSwitchable" erben, die Methode OnPreInit überschreiben, und dort die ID eines Controls an die neue Eigenschaft "MasterpageSwitcherID" übergeben das für den Wechsel verantwortlich sein soll.

Leon hat genau das Control abgefangen, das den Postback ausgelöst hat. Ich jedoch lege ein Control über die ID fest und überprüfe, ob sich der Wert geändert hat.
Dabei spielt es keine Rolle, ob das Postback von einem Button oder vom Control selbst ausgelöst wurde.
Die Basisklasse überprüft selbständig, ob der Wert eines Controls geändert wurde. Wenn ja, wird die Session aktualisiert und die Masterpage ausgewechselt.

Im Moment nutze ich eine Hilfsmethode die mir den ControlTree rekursiv durchgeht, um das Control zu finden.
Anders ging es leider nicht. Fall hier jemand eine andere Möglichkeit weiss, bin ich natürlich gerne dafür zu haben.

Edit: Folgend den Quellcode der Basisklasse
/// <summary>
/// Stellt die Funktionalitäten zur Verfügung, die Masterpage anhand des Wertes
/// eines Controls zu wechseln.
/// Die aktuell eingestellte MasterPageFile wird in der Session aufrecht erhalten
/// </summary>
public abstract class PageMasterSwitchableBase : Page
{
    private string _masterPageSwitcherID;

    /// <summary>
    /// Die ID des Controls, das per Postback den neuen MasterPageFile String liefert
    /// </summary>
    public string MasterpageSwitcherID
    {
        get { return this._masterPageSwitcherID; }
        set { this._masterPageSwitcherID = value; }
    }

    /// <summary>
    /// Liest mithilfe des zwischengespeicherten in der Session
    /// den aktuellen Wert des Controls aus und setze die MasterPageFile ggf.
    /// </summary>
    /// <param name="e"></param>
    protected override void OnPreInit(EventArgs e)
    {
        if (this.IsPostBack)
        {
            object controlUniqueID = this.Session["masterPageSwitcher"];
            if (controlUniqueID != null)
            {
                string newMasterpage = Request.Form[controlUniqueID.ToString()];
                if (base.MasterPageFile != newMasterpage)
                    base.MasterPageFile = newMasterpage;
            }
        }

        base.OnPreInit(e);
    }

    /// <summary>
    /// Setzt die Session mit dem UniqueID Namen
    /// </summary>
    /// <param name="e"></param>
    protected override void OnLoad(EventArgs e)
    {
        if (!this.IsPostBack) {
            this.Session["masterPageSwitcher"] = this.FindControlRecursive(
                                                 this, this._masterPageSwitcherID).UniqueID;
        }
        base.OnLoad(e);
    }

    /// <summary>
    /// Durchsucht rekursiv die Control Hierarchie
    /// (Hier gäbe es evt. bessere Möglichkeiten, das zu lösen.)
    /// </summary>
    /// <param name="root">Root Control</param>
    /// <param name="id">ID des zu findenden Controls</param>
    /// <returns></returns>
    private Control FindControlRecursive(Control root, string id) {
        if (root.ID == id) {
            return root;
        }

        foreach (Control c in root.Controls) {
            Control t = FindControlRecursive(c, id);
            if (t != null) {
                return t;
            }
        }

        return null;
    }
}

Links die benutzt wurden:

Im Beispielprojekt enthalten:

  • Basisklasse für die benötigten Funktionalitäten
  • Masterpage zwischen "Normal-" und "Druckansicht" per QueryString umschalten.
  • Masterpage durch verschiedene Controls umschalten lassen, RadioButtonList, TextBox, ...
  • Minimalbeispiel zum Einsatz der Basisklasse (SimpleExample.aspx)
  • Verschachtelte Masterpages

Download des Beispielprojekts:

Veröffentlicht Sonntag, 30. September 2007 23:29 von Peter Bucher
Abgelegt unter: , , ,

Kommentare

# Die alten Bekannten - Response.Write() und Request.Form[]

Im Bezug meine Äusserungen bspw. in diesem Thread möchte ich hier meine Meinung zu den alten Bekannten

Montag, 4. Februar 2008 21:02 by Peter Bucher

# re: Masterpage dynamisch per Code umschalten

huch, den Artikel sehe ich ja jetzt erst *schäm*

Gute Idee, ich denke, das kann ich gerade gut gebrauchen...

Dienstag, 5. Februar 2008 08:25 by Jürgen Gutsch

# FindControl mal anders: iterativ, rekursiv, generisch mit Bedingungen!

In ASP.NET müssen ab und zu Controls gefunden werden, meistens ist das der Fall wenn sie dynamisch hinzugefügt

Dienstag, 20. Januar 2009 22:58 by Peter Bucher
Anonyme Kommentare sind nicht zugelassen