eWorld.UI - Matt Hawley

Ramblings of Matt

ASP.NET MVC: Simplified Localization via ViewEngines

October 22, 2008 16:56 by matthaw

Thanks to Brad, he identified a few areas that made things MUCH simpler from my prior implementation. Notably, while you still have your ViewEngine specify "here's your view's path" there's no longer a need for having a new helper, derived classes, etc. This implementation goes back to the simplified HtmlHelper extension in which you can use in any ViewEngine regardless. It also allows you to "replace" the implementation by just changing the namespace, just as you can do with Html and Ajax.

 

Simplified View Engine

As previously stated, you still need a new view engine and view, because this is the only point in which you can truly grab which view is being rendered. What's nice now, is that it has been simplified down to using the base implementation and only setting a ViewData field in which the Resource extension method picks up and uses.

   1:  public class LocalizationWebFormViewEngine : WebFormViewEngine
   2:  {
   3:      protected override IView CreateView(ControllerContext controllerContext, 
   4:                                                  string viewPath, string masterPath)
   5:      {
   6:          return new LocalizationWebFormView(viewPath, masterPath);
   7:      }
   8:   
   9:      protected override IView CreatePartialView(ControllerContext controllerContext, 
  10:                                                                            string partialPath)
  11:      {
  12:          return new LocalizationWebFormView(partialPath, null);
  13:      }
  14:  }
  15:   
  16:  public class LocalizationWebFormView : WebFormView
  17:  {
  18:      internal const string ViewPathKey = "__ViewPath__";
  19:   
  20:      public LocalizationWebFormView(string viewPath) : base(viewPath)
  21:      {
  22:      }
  23:   
  24:      public LocalizationWebFormView(string viewPath, string masterPath) 
  25:                : base(viewPath, masterPath)
  26:      {
  27:      }
  28:   
  29:      public override void Render(ViewContext viewContext, TextWriter writer)
  30:      {
  31:          // there seems to be a bug with RenderPartial tainting the page's view data
  32:          // so we should capture the current view path, and revert back after rendering
  33:          string originalViewPath = (string) viewContext.ViewData[ViewPathKey];
  34:          
  35:          viewContext.ViewData[ViewPathKey] = ViewPath;
  36:          base.Render(viewContext, writer);
  37:          
  38:          viewContext.ViewData[ViewPathKey] = originalViewPath;
  39:      }
  40:  }

As you can see, it's much simpler now. Also, it seems as if there's a bug with the current Beta bits when you have a RenderPartial on your page, there's no isolation and the view's path is not correct later. Simple fix by simply grabbing the original view path, setting it to the new path, rendering, and then reverting back.

 

Getting back to Extension Methods

And now that we have our path again in our ViewData, we can revert back to using extension methods to extract the path when doing both Global and Local resources. As you can see below, should you be using any other ViewEngine it will always work for Global resources.

   1:  public static class ResourceExtensions
   2:  {
   3:      public static string Resource(this Controller controller, string expression, 
   4:                                                    params object[] args)
   5:      {
   6:          ResourceExpressionFields fields = GetResourceFields(expression, "~/");
   7:          return GetGlobalResource(fields, args);
   8:      }
   9:   
  10:      public static string Resource(this HtmlHelper htmlHelper, 
  11:                                                    string expression, params object[] args)
  12:      {
  13:          string path = (string)htmlHelper.ViewData[LocalizationWebFormView.ViewPathKey];
  14:          if (string.IsNullOrEmpty(path))
  15:              path = "~/";
  16:   
  17:          ResourceExpressionFields fields = GetResourceFields(expression, path);
  18:          if (!string.IsNullOrEmpty(fields.ClassKey))
  19:              return GetGlobalResource(fields, args);
  20:   
  21:          return GetLocalResource(path, fields, args);
  22:      }
  23:   
  24:      static string GetLocalResource(string path, ResourceExpressionFields fields, 
  25:                                                       object[] args)
  26:      {
  27:          return string.Format((string)HttpContext.GetLocalResourceObject(path, 
  28:                                 fields.ResourceKey, CultureInfo.CurrentUICulture), args);
  29:      }
  30:   
  31:      static string GetGlobalResource(ResourceExpressionFields fields, object[] args)
  32:      {
  33:          return string.Format((string)HttpContext.GetGlobalResourceObject(
  34:               fields.ClassKey, fields.ResourceKey, CultureInfo.CurrentUICulture), args);
  35:      }
  36:   
  37:      static ResourceExpressionFields GetResourceFields(string expression, 
  38:                                                                string virtualPath)
  39:      {
  40:          var context = new ExpressionBuilderContext(virtualPath);
  41:          var builder = new ResourceExpressionBuilder();
  42:          return (ResourceExpressionFields)builder.ParseExpression(
  43:                                            expression, typeof(string), context);
  44:      }
  45:  }

Again, thanks to Brad, this implementation seems "less dirty". You can download the source for this here.

 

kick it on DotNetKicks.com

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Comments

October 23. 2008 07:06

Hi, matthaw.
Very cool solution Smile I like it.
I think you should contribute it to MvcContrib project. It doesn't have such useful functionality at the moment.
Thank you

zihotki

October 23. 2008 16:05

Indeed it is a really cool solution.
This is actually what i was looking for to localize/globalize a site which uses ASP.NET MVC.
So i am grateful to you for showing me the way.

Tasarım

October 27. 2008 11:45

I think your posts lacks a lot of details. How do I register the view engine as default for one? Called the ViewEngines.Add() in my Global.asax, but it does hit it. Thus I cant use local resources.

Please provide rich examples on how to use the code.

Hans G

November 15. 2008 18:44

Hello,

Can you provide an example on how to use this code?

Thanks in advance.

Rafael Pol

November 19. 2008 14:47

Hi Matt,
I could register the ViewEngine with Beta1 but still didnt manage to use local resources.
Could you please post a web that uses the Viewengine?
Thnx
Mathias

Mathias Fritsch

November 22. 2008 14:54

hmn, could u post a example plz

zeroonea

December 22. 2008 06:52

Hello, I need some example codes, thanks

C.T.

December 28. 2008 21:12

anyone can post an example code about how to use this in the controller?
thanks

C.T.

December 28. 2008 23:30

I have know how to use this code, but I cannot know how to use local resources in the controllers.

C.T.

January 2. 2009 12:03

Good solution for MVC localization.
But i dont know how to register "LocalizationWebFormViewEngine" as :
"ViewEngines.Engines.Add(new LocalizationWebFormViewEngine());"
in "Application_Start" does not work for LocalResource files.

Thnx for some example.

Feryt

January 2. 2009 12:15

I have finally found solution :

ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(LocalizationWebFormViewEngine());
Hope it helps.

Feryt

Add comment


 

  Country flag

[b][/b] - [i][/i] - [u][/u]- [quote][/quote]



Live preview

January 6. 2009 00:20



search

syndication

categories

archive

disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

Copyright © 2000 - 2009 , Excentrics World