ASP.Net Web Forms Templates – Part 1
Normal “templates” with ASP.Net web forms uses the App_Themes folder and then places skin and css files so that different templates can be used. The problem with this, as I outlined in the introduction, if you set up a master page, there isn’t much you can do to change the complete layout of the site unless you modify that master page. When you have to maintain a single code base for an entire project, modifying this per customer is virtually impossible.
A solution to this problem is to move that master page out somewhere that a web designer can easily change but not have it affect the main site. For this part, there are a few simple steps:
- Create a folder for the templates
- Create a configuration setting for a current template
- Create a base page that will set the proper template
First we will create a folder to store the templates and move our master page there. Keep in mind that this can be pretty flexible. You can have multiple master pages. To keep this simple, however we are just going to use one.
You cannot use the App_Themes folder as you are not allowed to put a master page in there. Also, my recommendation for these master pages is to not have any code behind files. Keep it simple and only have the page.Master and nothing else. Code can be added to the markup if needed and in the next part of this series, I will outline how to build out a template API so that designers can get to your site functionality pretty easy. For now, just keep in mind that once you get rid of the code behind file, you will need to create a base master page class that these master pages inherit from. See below:
namespace ASPNetTemplates.CMS { public class BaseMasterPage : System.Web.UI.MasterPage { } }
And in the master page:
<%@ Master Language="C#" AutoEventWireup="true" Inherits="ASPNetTemplates.CMS.BaseMasterPage" %>
Once the folder for the templates have been added (I called it Themes), create another folder called Default and move your master page there. This will be our default theme. You should also place any CSS and image files into this default folder. Feel free to organize, but make sure you update the paths to these files. If you happen to need to use any of the images within your main content pages, make sure to set a standard (and document it) path. That way every template has the same path to the image files. It’s also a good idea to standardize the names of these files unless you build out a a full template setup where there is a theme settings file that specifies which image is for what component of the site. Additionally, get rid of any code behind. If you already have a complicated master page that has lots of code behind (like I dealt with) you will need to start on an API to bring the functionality you need back to the page.
Just so that we can see success when we are done, create a second template folder and make a copy of your master, css, and images into this folder. Change the master page in this template so that you will know it’s using a different master page when we switch templates.
For the next step, we need to set up a configuration of some sort so that the template can be switched. Ideally this will include a admin page to change the template. For now, we will just create a simple settings class that will hold our current template setting. Then we have this class save and load our settings. See below:
Settings.cs –
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Xml.Serialization; using System.Xml; namespace ASPNetTemplates.CMS.SiteSettings { public class Settings { public string CurrentTemplate { get; set; } public static Settings CurrentSettings { get { Settings siteSettings = LoadSettings(); if (siteSettings == null) siteSettings = new Settings(); return (siteSettings); } } public static Settings LoadSettings() { Settings siteSettings = null; using (var file = System.IO.File.Open(string.Format("{0}{1}//{2}", HttpContext.Current.Request.PhysicalApplicationPath, "App_Data", "Settings.xml"), System.IO.FileMode.OpenOrCreate)) { var xmlSer = new XmlSerializer(typeof(Settings)); try { siteSettings = (Settings)xmlSer.Deserialize(file); } catch (InvalidOperationException) { // An invalid operation exception is thrown if the settings.xml file doesn't exist or is blank. } } return (siteSettings); } public static void SaveSettings(Settings siteSettings) { using (var file = System.IO.File.Open(string.Format("{0}{1}//{2}", HttpContext.Current.Request.PhysicalApplicationPath, "App_Data", "Settings.xml"), System.IO.FileMode.OpenOrCreate)) { var xmlSer = new XmlSerializer(typeof(Settings)); xmlSer.Serialize(file, siteSettings); } } } }
Settings.xml –
Default
Now the final step is where the magic happens. To make things easy, so we don’t have to add code in each of our pages, we will make a base class that each of our pages will inherit from. This is the place where we will be setting the master page to use. This is pretty easy to do. See below:
namespace ASPNetTemplates.CMS { public class BasePage : System.Web.UI.Page { protected override void OnPreInit(EventArgs e) { base.OnPreInit(e); this.MasterPageFile = string.Format("\\Themes\\{0}\\Site.Master", CMS.SiteSettings.Settings.CurrentSettings.CurrentTemplate); } } }
Then in our pages we just inherit from this class –
namespace ASPNetTemplates { public partial class _Default : CMS.BasePage { protected void Page_Load(object sender, EventArgs e) { } } }
If all goes well you should be able to run the site and be presented with your default template. Now, to see the magic happen, change the settings.xml file to reflect the other template you set up and rerun the application.
Gr1d – Persistent multiplayer online programming game.
Gr1d is a persistent multiplayer online programming game. It takes it’s roots in games like RoboCode but with persistence (your agents run all the time, even while you sleep) and you compete with a ton of other players at the same time.
The code can be written in any .Net compatible language and has a full API.
Go here to check it out! There is a competition starting on the 19th of December, 2010.
Global Application Settings Override (Now with examples!)
My previous posting about TableAdapter connection strings was a pretty big hit with a lot of people. It’s still being referenced on the MSDN forums from time to time when someone needs a solution to the problem of having a connection string for a TableAdapter in the app.config that they need to override at runtime.
Since that post was in VB.Net and over 2 years old now, I’ve decided to resurrect it and work up a quick sample project with both C# and VB.Net code. Someone had request C# and I have no idea why I didn’t include it when I did that post, but better late than never!
Click here for the sample solution. (VS2010) If you don’t have that version of visual studio, you can download the express version from Microsoft, or just open the projects individually in VS2008.
Wrap your ASP.Net session state into a more meaningful object.
I’ve tried to eliminate as much session state as possible as I know that it can cause some issues, especially in a server farm environment. However, there are quite a few times that I just need to have it, and I really doubt this small application is going to be hosted in a farm environment. This application is connected to a WCF service, and I really needed to eliminate round trips without using caching since some of this data is somewhat time sensitive, but it still needs to be available across a few page views. Instead of hitting the WCF service numerous times, I save the data in session state.
The way most people seem to have done session state (at least in just random searches on the internet and in sample code), you see this:
HttpContext.Current.Session["SomeData"] = myData;
There is nothing wrong with this. It works. Is it maintainable? Sort. Is it a potential problem? YES! What happens if you do this somewhere:
myData = HttpContext.Current.Session["SomData"];
Obviously you are going to get a null object back and possibly an error depending on other factors. You will probably find the problem during your automated tests (you do have automated tests right?) or at minimum find it during your own testing. The problem is, sometimes things like this will slip past if they are in infrequently used parts of a system that no one bothers to check for 3 or 4 versions that pass.
So in searching around I found various posts on how to wrap your session state into a more manageable object so that typos don’t creep up and bite you on the backside. Some of the examples went way over what I needed to do and some were way too simple to make sense for me.
So here is what I did (if I happened to have stolen your code, sorry, but it happens in software).
First I created my Current Session class to store everything and I made it static since Session itself is a static object.
public static class CurrentSession { private static HttpSessionState LiveSession { get { return HttpContext.Current.Session; } } }
This simple gives me a class that has access to the current session through a private variable called LiveSession.
Inside this class, I created another class for the CurrentUser since I needed to track the current logged in user (this user has data from the web service, so I’m not using asp.net authentication methods).
public static class CurrentUser { public static bool IsLoggedIn { get { bool? logIn = GetUserItem(UserData.LoginState); if (!logIn.HasValue) logIn = false; return (logIn.Value); } set { SetUserItem (UserData.LoginState, value); } } public static FamilyData CurrentFamily { get { return (GetUserItem (UserData.Family)); } set { SetUserItem (UserData.FamilyInfo, value); } } private static T GetUserItem (UserData key) { T data = (T)LiveSession[Enum.GetName(typeof(UserData), key)]; return (data); } private static void SetUserItem (UserData key, T data) { if (data == null) LiveSession.Remove(Enum.GetName(typeof(UserData), key)); else LiveSession[Enum.GetName(typeof(UserData), key)] = data; } public enum UserData { Family, LoginState } }
Now, some explanation and why I did it the way I did. I could have just made my public variable have the getter and setter access the Session object directly. My typo chances would have been minimized by the fact that it’s only in two spots that I need to type it, but there is still a chance. So I created myself an enum that I can use to specify which sets of data I am saving or retrieving. I use the enum’s string value as the session object name, so all I have to do is make sure my enums never use the same name. In my RLC (Real Life Code) I use much longer enums, like CalendarFilterMonth or CalendarFilterDay, for example. In the above example, I use LoggedInUserFamily in place of just Family. While it may be long, intellisense always gets it right for me with just a few keystrokes.
The GetUserItem and SetUserItem simple save and retrieve from the LiveSession object. I use Type generics to tell the functions what type I am saving and retrieving for ease of use. My type casting never has to be done now. I tell it what I want and that’s what I will get in return.
The best part about this, is to add more, all I need to do is copy and paste a property and change a few of the options, maybe add an enum, and now I’m done. I don’t have to worry about typo’s and I can organize my session object however I want.
To use it, I just do this anywhere in my application:
if (!CurrentSession.CurrentUser.IsLoggedIn) RedirectToLogin(); else if (CurrentSession.CurrentUser.CurrentFamily.NeedsProfileUpdate) RedirectToUpdate();
Since NeedsProfileUpdate is part of my FamilyData class, I can access that directly!