Localizing your Views in ASP MVC

ASP,MVC,C#

Posted by Alex Peta on June 27, 2013 Copyright© from Bing images : Uinta ground squirrels at Tower Fall Campground in Yellowstone National Park, Wyoming (© Shin Yoshino/Minden Pictures)

Today at work I had to do an interesting thing to do : localize all the views in a controller. There is a legacy app that was build in classic ASP with each .aspx file having its own translations as resources and my task here was to migrate this to ASP MVC.

The following pic illustrates my project structure.

 

The particular thing to notice here is that the resource file names are exactly like the view names. So my job here was that each time an Action method was called, I had to create a ResourceManager class that would initialize with the appropriate resource file based on the routed action name, and provide translations.

First I created a ResourceManger singleton:

 

 public sealed class ResourceManagerHelper
  {
    #region Private Members
    private static ResourceManager mInstance = new ResourceManager(Constants.NamespaceEnum.CResourcesDefaultNamespace, typeof(ResourceManagerHelper).Assembly);
    private static readonly object mSyncLock = new object();
    #endregion Private Members

    #region Constructors
    private ResourceManagerHelper()
    {
    }
    static ResourceManagerHelper()
    {
    }
    #endregion Constructors

    #region Public Methods
    public static ResourceManager GetResourceManagerInstance([CallerMemberNameAttribute] string resourceName = null)
    {
      lock (mSyncLock)
      {
        if (!string.IsNullOrWhiteSpace(resourceName))
        {
          mInstance = new ResourceManager(string.Format("{0}{1}",Constants.NamespaceEnum.CResourcesPartialNamespace, resourceName), typeof(ResourceManagerHelper).Assembly);
        }
        else
        {
          mInstance = new ResourceManager(Constants.NamespaceEnum.CResourcesDefaultNamespace, typeof(ResourceManagerHelper).Assembly);
        }

        return mInstance;
      }
    }
    #endregion Public Methods

  }

The interesing bit here is the GetResourceManagerInstance() method that accepts the resource file name passes it to the ResourceManager constructor.

Another trick that is available in .NET 4.5 is the [CallerMemberNameAttribute] parameter attribute that returns the calling method name (I think behind the scenes its done by reflection) – I added this in case I would call this method directly in the ActionResult body and to add more flexibily.

Next I started work on a wrapper class around the default controller class. This is the place that I will all the stuff that my views need to have access to, and this will serve as the base controller for all my pages – the ResourceManager and other stuff

 public class MVCBaseController : Controller
  {
    #region Private Members
    private ResourceManager mResourceManager;
    private IUnitOfWork mUnitOfWork;
    #endregion Private Members
    
    #region Protected Properties
    protected ResourceManager ResourceManager
    {
      get { return mResourceManager; }
      set { mResourceManager = value; }
    }
    protected IUnitOfWork UnitOfWork
    {
      get { return mUnitOfWork; }
      set { mUnitOfWork = value; }
    }
    #endregion Protected Properties

    #region Constructors
    /// 
    /// constructor injection for UnitOfWork implementation
    /// 
    public MVCBaseController()
      : this(new UnitOfWork())
    {
    }
    public MVCBaseController(IUnitOfWork unitOfWork)
    {
      UnitOfWork = unitOfWork;
    }
    #endregion Constructors

    #region Overrides
    protected override void Initialize(System.Web.Routing.RequestContext requestContext)
    {
      base.Initialize(requestContext);
      InitializeResourceManager();
    }
    #endregion Overrides

    #region Private Methods
    /// 
    /// Method that initializes the resource manager based on the RouteData action method
    /// to get the appropriate resource for the currenct action view.
    /// 
    private void InitializeResourceManager()
    {
      string callingMemberName = string.Empty;
      if (RouteData != null && RouteData.Values != null)
      {
        var routeValue = this.RouteData.Values["action"];
        if (routeValue != null)
        {
          callingMemberName = routeValue.ToString();
        }
      }
      ResourceManager = Helpers.ResourceManagerHelper.GetResourceManagerInstance(callingMemberName);
    }
    #endregion Private Methods
  }

The things to highlight here the override Initialize() method. This is the method after which , the "RouteData” property of each controller is initialized, so if I wanted the first place that I would be able to access RouteData for the current request to know which ActionMethod / View was called by user, this is the place to search. So Im overriding the default implementation, and right after the base implementation is called Im calling my InitializeResourceManager method that looks up the “Action” name in the RouteData dictionary and then initializes the ResourceManager instance.

The result is the following :

  • the user navigates to : /home/index
  • Controller base controller is called
  • MVCBaseController is called
  • the derived Initialize method is called , getting the RouteData and initializing the ResourceManager with the correct namespace
  • the public ActionMethod Index() is called now having access to the MVCBaseController class and its ResourceManager with translations