Changing Your Website Theme based on the Users Language or Culture

Friday, November 4, 2011

Currently I am working on pretty interesting project where if the user is not from the United States but speaks English, I need to present to them an entirely different themed experienced while still  providing the same website functionality.

ASP.Net MVC does a very nice thing by encapsulating the view engine code so that is pluggable. As a matter of fact, you can actually use more than one view engine for your website if you wanted, as seen in Scott Hanselman’s post.

In my case, I still want to use the Razor View Engine but I just want to add functionality to dynamically change the paths to the views so that when user hits the web site and their current thread’s UI Culture is say United Kingdom English, I want to point them to an entirely different view engine location where the markup is very different but uses the same model.

Overriding the Razor View Engine

The way to do this is to override the RazorViewEngine class modifying its functions to dynamically change the path based on the current culture.

 

 1: using System.Web.Mvc;
 2: using Infrastructure.Http;
 3: using Infrastructure.Theming.Context;
 4: using StructureMap;
 5:  
 6: namespace Infrastructure.Theming.ViewEngines
 7: {
 8:     public class ThemingRazorViewEngine : RazorViewEngine
 9:     {
 10:         private readonly IThemingContext _themingContext;
 11:         public ThemingRazorViewEngine()
 12:         {
 13:             _themingContext = ObjectFactory.GetInstance<IThemingContext>();
 14:             LoadFormats();
 15:         }
 16:  
 17:         public ThemingRazorViewEngine(IThemingContext themingContext)
 18:         {
 19:             _themingContext = themingContext;
 20:             LoadFormats();
 21:         }
 22:  
 23:         private void LoadFormats()
 24:         {
 25:             AreaViewLocationFormats =
 26:                 new[]
 27:                     {
 28:                         "~/Areas/{2}/Views/%G/{1}/{0}.cshtml",
 29:                         "~/Areas/{2}/Views/%G/{1}/{0}.vbhtml",
 30:                         "~/Areas/{2}/Views/%G/Shared/{0}.cshtml",
 31:                         "~/Areas/{2}/Views/%G/Shared/{0}.vbhtml"
 32:                     };
 33:             AreaMasterLocationFormats =
 34:                 new[]
 35:                     {
 36:                         "~/Views/%G/{1}/{0}.cshtml",
 37:                         "~/Views/%G/Shared/{0}.cshtml",
 38:                         "~/Areas/{2}/Views/%G/{1}/{0}.cshtml",
 39:                         "~/Areas/{2}/Views/%G/Shared/{0}.cshtml"
 40:                     };
 41:  
 42:             AreaPartialViewLocationFormats =
 43:                 new[]
 44:                     {
 45:                         "~/Areas/{2}/Views/%G/{1}/{0}.cshtml",
 46:                         "~/Areas/{2}/Views/%G/Shared/{0}.cshtml"
 47:                     };
 48:  
 49:             ViewLocationFormats =
 50:                 new[]
 51:                     {
 52:                         "~/Views/%G/{1}/{0}.cshtml",
 53:                         "~/Views/%G/Shared/{0}.cshtml"
 54:                     };
 55:  
 56:             MasterLocationFormats =
 57:                 new[]
 58:                     {
 59:                         "~/Views/%G/{1}/{0}.cshtml",
 60:                         "~/Views/%G/Shared/{0}.cshtml"
 61:                     };
 62:  
 63:             PartialViewLocationFormats =
 64:                 new[]
 65:                     {
 66:                         "~/Views/%G/{1}/{0}.cshtml",
 67:                         "~/Views/%G/Shared/{0}.cshtml"
 68:                     };
 69:         }
 70:  
 71:         
 72:  
 73:         protected override IView CreatePartialView
 74: (ControllerContext controllerContext, string partialPath)
 75:         {
 76:             var compCulture = GetCompCulture();
 77:             string replacedPath = partialPath.Replace("%G", compCulture);
 78:             return base.CreatePartialView(controllerContext, replacedPath);
 79:         }
 80:  
 81:         protected override IView CreateView
 82: (ControllerContext controllerContext, string viewPath, string masterPath)
 83:         {
 84:             var compCulture = GetCompCulture();
 85:             string replacedMasterPath = masterPath.Replace("%G", compCulture);
 86:             string replaceViewPath = viewPath.Replace("%G", compCulture);
 87:             return base.CreateView(controllerContext, replaceViewPath, replacedMasterPath);
 88:         }
 89:  
 90:         protected override bool FileExists
 91: (ControllerContext controllerContext, string virtualPath)
 92:         {
 93:             var compCulture = GetCompCulture();
 94:             string replaceVirtualPath = virtualPath.Replace("%G", compCulture);
 95:             return base.FileExists(controllerContext, replaceVirtualPath);
 96:         }       
 97:  
 98:         private string GetCompCulture()
 99:         {
 100:             string folder = _themingContext.GetFolderName();
 101:             if (string.IsNullOrEmpty(folder))
 102:             {
 103:                 folder ="enUS";
 104:             }
 105:  
 106:             return folder;
 107:         }
 108:  
 109:  
 110:     }
 111: }

On every request this request for the view path will first to check to see if the file exists using the FileExists functionion and then if does Exists depending on what type of view request it is, the CreateView, or CreatePartialView will be called. As you can see, I have added functionality to replace the “%G” in the path with what ever the culture is returned from the GetCompCulture function.

All my GetCompCulture function is doing is injection a business object that is getting the users current culture from current thread and then stripping out the “-“ so that “en-US” is “enUS”. It is also defaulting any language culture that we have not set up to also be “enUS”.

Once I have a custom view engine, I just need to tell the MVC framework to only use it instead of the default Razor view engine. I can do that in the application startup event in the Global.cs file.

 1: ViewEngines.Engines.Clear();
 2: ViewEngines.Engines.Add(new ThemingRazorViewEngine());

The Folder Structure

Once my view engine is setup, I then need to setup my folder structure to match. Note, that if you are using a _viewStart.cshtml file, you will need to change the path of the _Layout file in that file.

 1: @{
 2:     Layout = "~/Views/enUS/Shared/_Layout.cshtml";
 3: }

The folder structure looks something like this.

internationaltheming

And that’s it. Your views can now be totally separate and totally different markup based on your language criteria, or any criteria you want to provide for that matter.

Hope that helps.

comments powered by Disqus