My suggested solution is below. It has several limitations, since all master pages must implement the same set of bookmark controls (not surprisingly). Take a look and let me know what you think.
I set up the folder structure as follows:
Website -> Templates -> TemplateFolder (named the same as the template)
Website β Templates β UserControls (user controls are stored in a folder that does not contain templates)
I defined a simple Template configuration class that we can save / save / load basic template decryption:
public class Template { public string TemplateName { get; set; } public string UserControlName { get; set; } public string MasterPageName { get; set; } public string TemplateFolder { get { return GetTemplateFolder(TemplateName); } } public string TemplateConfigFile { get { return GetTemplateConfigFile(TemplateName); } } private static string GetTemplateFolder(string name) { return HttpContext.Current.Server.MapPath("~/Templates/" + name + "/"); } private static string GetTemplateConfigFile(string name) { return GetTemplateFolder(name) + "/" + name + ".config"; } public Template() { } public void Save() { XmlSerializer xs = new XmlSerializer(typeof(Template)); if (!Directory.Exists(TemplateFolder)) Directory.CreateDirectory(TemplateFolder); using (FileStream fs = File.OpenWrite(TemplateConfigFile)) { xs.Serialize(fs, this); } } public static Template Load(string name) { if(!File.Exists(GetTemplateConfigFile(name))) return null; XmlSerializer xs = new XmlSerializer(typeof(Template)); using (FileStream fs = File.OpenRead(GetTemplateConfigFile(name))) { Template t = (Template)xs.Deserialize(fs); return t; } } }
You can create some kind of bare xml code to get started by running the code below:
Template t1 = new Template() { TemplateName = "Template1", MasterPageName = "Child1.master", UserControlName = "uc1.ascx" }; Template t2 = new Template() { TemplateName = "Template2", MasterPageName = "Child2.master", UserControlName = "uc2.ascx" }; t1.Save(); t2.Save();
I created a basic homepage. This page will probably never be used, except to provide your default page with basic placeholders. All your master pages must have the same set of placeholders as your base so that your pages can use them all interchangeably. Notice that I left the placeholder for our user control.
<%@ Master Language="C#" AutoEventWireup="true" CodeFile="BaseMaster.master.cs" Inherits="Templates_Masters_BaseMaster" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> <asp:ContentPlaceHolder id="head" runat="server"> </asp:ContentPlaceHolder> </head> <body> <form id="form1" runat="server"> <div> <asp:ContentPlaceHolder id="cphHeader" runat="server"> </asp:ContentPlaceHolder> <asp:ContentPlaceHolder id="cpUserControl" runat="server"> </asp:ContentPlaceHolder> <asp:ContentPlaceHolder id="cphFooter" runat="server"> </asp:ContentPlaceHolder> </div> </form> </body> </html>
Now I am creating a basic aspx webpage that uses the above main page.
<%@ Page Title="" Language="C#" MasterPageFile="~/Templates/Masters/BaseMaster.master" AutoEventWireup="true" CodeFile="DefaultTemplated.aspx.cs" Inherits="DefaultTemplated" %> <asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server"> </asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="cphHeader" Runat="Server"> </asp:Content> <asp:Content ID="Content3" ContentPlaceHolderID="cpUserControl" Runat="Server"> </asp:Content> <asp:Content ID="Content4" ContentPlaceHolderID="cphFooter" Runat="Server"> </asp:Content>
In the code we will install the basic patterns. This sets up the master page and adds a predefined user control to the content placeholder for custom controls. If you would like, you could just do a panel or something else and add it to this fixed control, but I thought you could appreciate how to make it work with master pages.
public partial class DefaultTemplated : System.Web.UI.Page { private Template PageTemplate { get { if (_tLoaded == null) { string template = Request.QueryString["template"]; if (string.IsNullOrEmpty(template)) return null; Template t = Template.Load(template); _tLoaded = t; } return _tLoaded; } } private Template _tLoaded = null; protected void Page_Load(object sender, EventArgs e) { if (PageTemplate != null) {
If you have a template named "Template1", you can use it by calling "Default.aspx? Template = Template1". Since you are using URL rewriting, you must use rewriting to pass the template name as a parameter to the page.
Another option that could be combined with the above would be to use Page.ParseControl. Using this, you can save your raw asp.net developer code (designer only) in a database or in a raw text file. Then you can create an instance to load it as follows:
One great thing is that nested controls work great.