Click here to Skip to main content
Click here to Skip to main content
Technical Blog

Extending the ASP.NET MVC ViewEngine to support localization

, 12 May 2011 CPOL
Rate this:
Please Sign up or sign in to vote.
Adding localization support to the ASP.NET MVC ViewEngine.

I’ve been using Scott Hanselman’s CustomMobileViewEngine from his post: A Better ASP.NET MVC Mobile Device Capabilities ViewEngine along with jQuery Mobile (for mobile templates) and 51degrees.mobi (for accurate mobile browser detection) to build ASP.NET MVC sites that output nice mobile-friendly templates. The techniques that Scott talks about in his post have been working really well.

So recently, when I had to solve a similar problem when trying to render out localized templates, I took a deeper look into Scott’s approach to see if I could ‘tweak’ it to do what I wanted, which, in Visual Studio looks like:

This allows my site to serve the same URLs for multiple languages. For example, the URL /Home/Index uses the same controllers, models, etc., but will call different views for English and Spanish users based on the current uiCulture.

Here’s how I did it. I took Scott’s classes and refactored them just a bit so that as I extended this functionality, there weren’t bits of code getting duplicated (see DRY – Don’t repeat yourself).

First, I took the existing CustomMobileViewEngine class and renamed it CustomViewEngine as this engine will no longer be Mobile only. Other than thatm no changes were necessary.

public class CustomViewEngine : IViewEngine
{
    public IViewEngine BaseViewEngine { get; private set; }
    public Func<ControllerContext, bool> IsTheRightDevice { get; private set; }
    public string PathToSearch { get; private set; }

    public CustomViewEngine(Func<ControllerContext, bool> isTheRightDevice, 
           string pathToSearch, IViewEngine baseViewEngine)
    {
        BaseViewEngine = baseViewEngine;
        IsTheRightDevice = isTheRightDevice;
        PathToSearch = pathToSearch;
    }

    public ViewEngineResult FindPartialView(ControllerContext context, 
           string viewName, bool useCache)
    {
        if (IsTheRightDevice(context))
        {
            return BaseViewEngine.FindPartialView(context, PathToSearch + 
                   "/" + viewName, useCache);
        }
        return new ViewEngineResult(new string[] { });
        //we found nothing and we pretend we looked nowhere
    }

    public ViewEngineResult FindView(ControllerContext context, 
                            string viewName, string masterName, bool useCache)
    {
        if (IsTheRightDevice(context))
        {
            return BaseViewEngine.FindView(context, PathToSearch + 
                   "/" + viewName, masterName, useCache);
        }
        return new ViewEngineResult(new string[] { });
        //we found nothing and we pretend we looked nowhere
    }

    public void ReleaseView(ControllerContext controllerContext, IView view)
    {
        throw new NotImplementedException();
    }
}

Next, I took the most generic AddMobile<T> extension method, renamed it AddCustomView<T>, and put it in its own ViewHelper class.

public static class ViewHelper
{
    public static void AddCustomView<T>(this ViewEngineCollection ves, 
           Func<ControllerContext, bool> isTheRightDevice, string pathToSearch)
           where T : IViewEngine, new()
    {
        ves.Add(new CustomViewEngine(isTheRightDevice, pathToSearch, new T()));
    }
}

Additionally, I refactored the existing MobileHelpers class to call the newly refactored AddCustomView<T> to prevent further duplication of code.

public static class MobileHelpers
{
    public static bool UserAgentContains(this ControllerContext c, string agentToFind)
    {
        return (c.HttpContext.Request.UserAgent.IndexOf(agentToFind, 
                StringComparison.OrdinalIgnoreCase) >= 0);
    }

    public static bool IsMobileDevice(this ControllerContext c)
    {
        return c.HttpContext.Request.Browser.IsMobileDevice;
    }

    public static void AddMobile<T>(this ViewEngineCollection ves, 
           string userAgentSubstring, string pathToSearch)
           where T : IViewEngine, new()
    {
        ves.AddCustomView<T>(c => c.UserAgentContains(userAgentSubstring), pathToSearch);
    }

    public static void AddIPhone<T>(this ViewEngineCollection ves) //specific example helper
        where T : IViewEngine, new()
    {
        ves.AddCustomView<T>(c => c.UserAgentContains("iPhone"), "Mobile/iPhone");
    }

    public static void AddGenericMobile<T>(this ViewEngineCollection ves)
        where T : IViewEngine, new()
    {
        ves.AddCustomView<T>(c => c.IsMobileDevice(), "Mobile");
    }
}

Finally, I created some AddLanguage<T> extension methods in their own LocalizationHelpers class along with the UICulture detection routing.

public static class LocalizationHelpers
{
    public static bool UICultureEquals(this ControllerContext c, string stringToFind)
    {
        var culture = CultureInfo.CurrentUICulture;
        var cultureName = culture != null ? culture.Name : string.Empty;

        return (cultureName.IndexOf(stringToFind, 
                StringComparison.OrdinalIgnoreCase) >= 0);
    }

    public static void AddLanguage<T>(this ViewEngineCollection ves, 
           string cultureName, string pathToSearch)
           where T : IViewEngine, new()
    {
        ves.AddCustomView<T>(c => c.UICultureEquals(cultureName), pathToSearch);
    }

    public static void AddLanguage<T>(this ViewEngineCollection ves, string cultureName)
        where T : IViewEngine, new()
    {
        ves.AddCustomView<T>(c => c.UICultureEquals(cultureName), cultureName);
    }
}

Using the Localized views in your project is as simple as registering the view in the Application_Start() method.

ViewEngines.Engines.Clear();
ViewEngines.Engines.AddLanguage<WebFormViewEngine>("es-ES");
ViewEngines.Engines.AddGenericMobile<WebFormViewEngine>();
ViewEngines.Engines.AddCustomView<WebFormViewEngine>(c => c.IsMobileDevice() 
    && c.UICultureEquals("es-ES"), "Mobile/es-ES");
ViewEngines.Engines.Add(new WebFormViewEngine());

Lastly, my sample project has these classes in their own class library because it’s my hope to be able to provide this functionality as a NuGet package soon.

I’ve included my sample project here for reference.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Pete Mourfield
Software Developer (Senior)
United States United States
Software Developer
Follow on   Twitter   Google+

Comments and Discussions

 
QuestionMissing link to your sample PinmemberChris H Developer20-Jun-11 13:15 
Generalcombine them PinmemberDaniel Gidman20-May-11 6:42 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141223.1 | Last Updated 12 May 2011
Article Copyright 2011 by Pete Mourfield
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid