I do something similar with some software I wrote that I host on a shared server. Depending on the domain coming in, I end up with a different skin and some configuration options.
At the root is the "Site" class that has the site title, description, and some other basic information. Then it also keeps a list of attributes.
The tables look like this:
Create Table [Site](
[Id] uniqueidentifier default newSequentialId(),
[Name] nvarchar(128),
[Description] nvarchar(512),
Constraint pk_Site Primary Key (Id)
)
Create Table [Attribute](
[Id] int identity(1,1),
[Key] varchar(128),
[Type] varchar(16),
[OwnerMask] int,
Constraint pk_AttributeId Primary Key (Id),
Constraint uq_AttributeKey Unique ([Key])
)
Create Table [AttributeValue](
[OwnerId] uniqueidentifier,
[AttributeId] int,
[Value] nvarchar(512),
Constraint pk_Owner_Attrib Primary Key (OwnerId, AttributeId)
)
I'm using Guids because I wanted to be able to attach the attributes to different objects, and be able to look them up by the object id. The OwnerMask is to determine which objects the different attributes are valid for. This would be optional for you.
Every class that can have Attributes implements this interface:
public interface IPropertyContainer
{
void AddProperty(Property property);
string FindPropertyValue(string name);
Property FindProperty(string name);
void SetPropertyValue(string name, string value);
}
I regret the name difference (Attribute vs. Property) but it became annoying to deal with the fact that Attribute is also a predefined class in the .NET Framework. The Site class implements this interface.
I then have a service that checks the domain the request is coming in on and checks the database to see which Site Id that maps too. I then load the site object into the HttpContext using an HttpModule. I'll include the code below, but it's maybe not clear out of context:
private static void AuthenticateRequestHandler(object sender, EventArgs e)
{
HttpApplication app = sender as HttpApplication;
if(app == null) return;
Uri url = app.Request.Url;
string domain = url.Host;
string path = url.AbsolutePath.ToLower().Replace("default.aspx", "");
IInjectionWrapper wrapper = new WindsorWrapper();
ISiteProvider provider = wrapper.Resolve<ISiteProvider>();
Site site = provider.FindSiteByDomain(domain);
if (site == null)
return;
ActiveContext.Current.Site = site;
}
The attribute system allows you to attach any arbitrary data to your site, which is nice, but it does rely on "Magic Strings" which some may not like, but in this case it doesn't bother me too much.
An example of how I use this, is in a base class for each Page object, I set the theme like so:
protected override void OnPreInit(EventArgs e)
{
Page.Theme = ActiveContext.Current.Site.Theme;
base.OnPreInit(e);
}
The Theme
property is wrapping a call to one of the methods on the IPropertyContainer
interface.
public string Theme
{
get { return FindPropertyValue("Theme") ?? "default"; }
}
Hopefully this long winded example gives you a jumping off point.
UPDATE
I realize you might not be familiar with the HttpContext
or what I'm doing with the ActiveContext
class. Basically I'm using the ActiveContext
class to provide a strongly typed wrapper around the HttpContext.Items
and HttpContext.Sessio
n collections.
Session data is persisted from one call to the next, but can cause problems in load balanced situations. HttpContext.Items
only exist for the duration of the current request. Here is the code for my ActiveContext
class which would be available from any aspx page by calling the static method ActiveContext.Current
public class ActiveContext : IActiveContext
{
private Site _site;
public static IActiveContext Current
{
get
{
IActiveContext activeContext;
activeContext = Context.Items["ActiveContext"] as ActiveContext;
if (activeContext == null)
{
activeContext = new ActiveContext();
Context.Items["ActiveContext"] = activeContext;
}
return activeContext;
}
}
public Person AuthenticatedUser
{
get
{
Person person = Context.Session["AuthenticatedUser"] as Person;
return person;
}
set { Context.Session["AuthenticatedUser"] = value; }
}
public Site Site
{
get { return Context.Items["CurrentSite"] as Site; }
set { Context.Items["CurrentSite"] = value; }
}
private static HttpContext Context
{
get
{
HttpContext context = HttpContext.Current;
if (context == null)
throw new Exception("HttpContext is null");
return context;
}
}
}