Skip to content

Instantly share code, notes, and snippets.

@kocubinski
Created January 2, 2014 14:58
Show Gist options
  • Save kocubinski/8220449 to your computer and use it in GitHub Desktop.
Save kocubinski/8220449 to your computer and use it in GitHub Desktop.
// ## Theme and UserControl Inversion of Control
//
// BaseClasses or Interfaces which UserControls derive from are bound to an implemenation for a given theme.
// This gives complete flexibility over which programs are using which controls, and
// a container to tie it all together rather than just ConfigKeys
// and 'if..then' statements sprinkled throughout the codebase. Pages query ThemeService (see *Resolve* method)
// for which UserControl to render, and based on the current program a path to the proper UserControl is returned.
//
// In this case ThemeService is our IOC container, and the bindings are defined below in C#.
// Currently there are two themes ("Default" or "Facelift"), and are put in a static hash-table. When *Resolve* is
// called the ConfigurationKey 'Theme' is retrieved, and the proper control returned based on what the ConfigValue is.
//
// This system could (should?) also use a table of the structure:
//
// | Id | ProgramId | Type | Binding |
// |----+-----------+------------------+----------------------|
// | 1 | 2 | LoginControlBase | /UC/LoginThemed.ascx |
// | 2 | 2 | IMenuControl | /UC/NavMenu.ascx |
//
// Or replace ProgramId for ThemeId, etc.
using System;
using System.Linq;
using System.Collections.Generic;
using System.Web.UI;
using DIMC.TotalPro.Business;
public class ThemeControl
{
public string Path { get; private set; }
public Type Type { get; private set; }
public ThemeControl(string path)
{
Path = path;
}
public ThemeControl(Type type)
{
Type = type;
}
}
public class ThemeService
{
// set to false to disable cache.
private static bool UseCache = true;
private static readonly IDictionary<string, IDictionary<Type, ThemeControl>> Bindings;
private static readonly IDictionary<string, ThemeControl> ControlCache = new Dictionary<string, ThemeControl>();
private readonly int _programId;
static ThemeService()
{
Bindings = new Dictionary<string, IDictionary<Type, ThemeControl>>();
// Default Theme:
Bindings["Default"] = new Dictionary<Type, ThemeControl>()
{
{typeof (ILoginControl), new ThemeControl("/UC/Login.ascx")},
{typeof(IMainGreeting), new ThemeControl("/UC/Greeting.ascx")},
{typeof (IFormGreeting), new ThemeControl("/UC/FormGreeting.ascx")},
{typeof (INavigationControl), new ThemeControl("/UC/DefaultNavigation.ascx")},
{typeof (ISiteHeader), new ThemeControl("/UC/Header.ascx")},
{typeof (IAdminHeader), new ThemeControl("/Admin/Header.ascx")},
{typeof (ICreateAccountControl), new ThemeControl("/Login/CreateAccount.ascx")},
{typeof (IForgotPasswordControl), new ThemeControl("/Login/ForgotPassword.ascx")}
};
// "Facelift" Theme:
Bindings["Facelift"] = new Dictionary<Type, ThemeControl>
{
{typeof (ILoginControl), new ThemeControl("/UC/Facelift/Login.ascx")},
{typeof (IMainGreeting), new ThemeControl("/UC/Facelift/Greeting.ascx")},
{typeof (IFormGreeting), new ThemeControl("/UC/Facelift/FormGreeting.ascx")},
{typeof (INavigationControl), new ThemeControl("/UC/Facelift/NavigationMenu.ascx")},
{typeof (IButton), new ThemeControl(typeof(SiteButton))},
{typeof (ISiteHeader), new ThemeControl("/UC/Facelift/HeaderBar.ascx")},
{typeof (IAdminHeader), new ThemeControl("/UC/Facelift/AdminHeader.ascx")},
{typeof (ICreateAccountControl), new ThemeControl("/UC/Facelift/CreateAccount.ascx")},
{typeof (IForgotPasswordControl), new ThemeControl("/UC/Facelift/ForgotPassword.ascx")}
};
// Add additional themes here, using whichever UserControls you desire
}
public ThemeService(int programId)
{
_programId = programId;
}
private static string CheckConfigKeys(SiteConfigurator config, Type type)
{
// give these config keys precedence for backwards compatibility..
if (typeof (IMainGreeting).IsAssignableFrom(type))
{
var val = config.Get(ConfigurationKeys.GreetingControl, false);
if (val != null) return "/UC/" + val;
}
return null;
}
private static ThemeControl ResolveByInterface(Type type, IDictionary<Type, ThemeControl> theme)
{
var themeControl =
type.GetInterfaces()
.Select(i =>
{
ThemeControl res;
return theme.TryGetValue(i, out res) ? res : null;
})
.SingleOrDefault(i => i != null);
return themeControl;
}
private static ThemeControl ResolveBySuperClass(Type type, IDictionary<Type, ThemeControl> theme)
{
ThemeControl themeControl;
var baseType = type.BaseType;
if (baseType == null)
return null;
return !theme.TryGetValue(baseType, out themeControl)
? ResolveBySuperClass(baseType, theme)
: themeControl;
}
public static ThemeControl GetControl(int programId, Type cType)
{
var config = new SiteConfigurator(programId);
// certain controls should always be bound via config keys for backwards compatibility
var configControl = CheckConfigKeys(config, cType);
if (configControl != null)
{
var thControl = new ThemeControl(configControl);
return thControl;
}
// first, try to resolve from cache
string themeName = config.Get(ConfigurationKeys.TotalPro_Theme);
var cacheKey = string.Format("{0}_{1}", themeName, cType.Name);
ThemeControl cached;
if (UseCache && ControlCache.TryGetValue(cacheKey, out cached))
return cached;
// finally we try to resolve the control by the current theme.
var theme = Bindings[themeName];
ThemeControl themeControl = ResolveByInterface(cType, theme) ?? ResolveBySuperClass(cType, theme);
if (UseCache) ControlCache[cacheKey] = themeControl;
return themeControl;
}
}
public class DynamicControl : BaseControl
{
public event Action<Control> ControlCreated;
protected DynamicControl()
{
Init += OnInit;
}
private void OnInit(object sender, EventArgs e)
{
ThemeControl themeControl = ThemeService.GetControl(ProgramID, GetType());
if (themeControl == null)
return;
var control = !string.IsNullOrEmpty(themeControl.Path)
? Page.LoadControl(themeControl.Path)
: (Control) Activator.CreateInstance(themeControl.Type, this);
Controls.Add(control);
if (ControlCreated != null)
ControlCreated(control);
}
}
// below are some interfaces which our dynamic controls implement
public interface INavigationControl { }
public interface ISiteHeader { }
public interface IAdminHeader { }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment