I have always had a love/hate relationship with ResXFileCodeGenerator and the StronglyTypedResourceBuilder and the code it generated. Simplified access to string resources and the elimination of string literals in user code is a great thing, but what if I need slightly different generated code? Generated code that you can’t modify is very often a two edged sword.
The only alternative that I have seen offered is to write your own Visual Studio plug in to replace ResXFileCodeGenerator as Dmytro Kryvko has done in his CodeProject article.
I have not had good luck with writing my own VS Pug-ins. All those methods that return object and you have to know what to cast them to, the undocumented property bags, etc. It is just not the kind of framework I like to work with. Moreover, an installed plug-in is harder to use on a distributed team, and what if you want different customizations for different projects?
Stealing shamelessly from the T4MVC template by David Ebbo, my solution to all of this is T4resx.tt. What it does is find all the .resx files in a project and generate output nearly identical to what the Microsoft tools generates. I say nearly identical because, I have of course tweaked it to my liking.
Specific changes I have made:
- I have neglected to include the attributes marking the code and auto-generated and non-user code. (Since the generated code can now become as complex as you like, its good to be able to step into it.)
- The accessor members are public vs. internal to make them easy to reference from MVC Views.
- I have added support for named tokens in the resource strings.
- I have added support for returning HtmlString wrapped strings.
- Defined constants for each resource name (for use as attribute parameters such as are needed to work with the validation attributes in the System.ComponentModel.DataAnnotations namespace). These constants are defined in the “Names” inner class within each resource accessor class.
I will talk more about some of these changes below. Many of them are very similar to what Dmytro did in his plug-in. The real advantage to the T4 approach though is that, being a text template, the code is highly accessible to the developer and easily modified to support generating the code that you want. Moreover, each project gets its own template (or you can share one among all projects in a solution if you like). Your are not locked into the same generated code for each project. Lastly, the template is part of the project source code tree so distributing it and keeping it up to date across a team becomes a simple function of source control.
Token Replacement Coolness
The biggest change to the generated code is the ability to do things like this: Create a resource string named OrderSuccessMessage with a value of “Thank you for your order. Your order confirmation number is {OrderNumber}. You will receive an e-mail shortly with this informaiton.” ResXFileCodeGenerator will create a property like so:
public static string OrderSuccessMessage { get { return ... ; } } |
The T4resx template will generate the following method:
public static string OrderSuccessMessage(string OrderNumber)
{
return ... ;
} |
If the string has no tokens in it, the template will generate a property instead of a method. This is because the DataAnnotation validation attributes when used with resource accessor types use reflection to call the member and they expects the member to be a property. This means you cannot embed tokens in resources that are intended to be used by the DataAnnotations framework. But that is okay. How would the framework know what values to provide in the first place?
Support for HTML strings
If a resource string value is prefixed with the token “HTML:”, it is presumed to contain formatted HTML. In this case, you presumably don’t want the MVC framework to encode it. To fix that problem, if the HTML: tag is found, the template strips it off but returns the string wrapped in an HtmlString so that MVC will not HTML encode it. This is very handy for putting bulk page content into a resource and then emitting it as follows:
<%@ Import Namespace="Localized=MyWebSite.Resources.Views.Home.Index" %>
<div id="mainContent">
<%: Localized.PageConent %>
</div> |
Note that the “Localized” alias for the resource accessor class is not part of the template (yet) but is my convention for making things more readable and reduce typing.
Some Caveats
Of course one disadvantage of T4 templates is that, unlike a VS plug in, they are not language independant. My template generates C# code. It wouldn’t take too much to come up with a VB version but if you needed to maintain VB and C# versions, this could be a headache.
Also, because the template emits classes that are named identically to the ones that ResXFileCodeGenerator creates, it is necessary, after creating each resource file in your project, to go into its properties and remove the reference to ResXFileCodeGenerator by clearing the “Custom Tool” property. Otherwise, the two tools will generated competing class declarations and your build will fail.
I find that getting rid of ResXFileCodeGenerator, because it eliminates the plus signs and child .designer.cs files in the solution explorer has the added aethetic advantage of making the solution tree all that much cleaner.
As with most T4 templates, the generated code now becomes a child file of the template itself. So if you want to find the generated code, look for the T4Resx.cs file as a child of the T4Resx.cs file, wherever you put that.
Lastly, this is my first attempt at writing a T4 template. I am very open to critique. Please leave comments.
Future Plans
Eventually, if people like the “Localized” alias convention, I may set the template up to insert these import statements (or using statements as appropriate) in each class for which there is a matching .resx class.
I would also like to add support for tags of the format {AmountDue=decimal:C} where decimal is the type of the input argument into the method and “C” is the string format to use when converting it to text. I am not settled on the best syntax for these tags though.
Another feature I would like to add is the comparison of the default .resx file and localized versions (.de.resx, etc.) to ensure that any tokens specified in the default version are specified in the localized ones. Right now if a token is missing in the localized version, the value passed to the accessor method is simply ignored. Conversely, if the localized resource file contains tokens not in the default file, they are not processed and remain in the returned string.
I thoroughly tested the code generated against all the scenarios outlined by Alexander Adamyan in his thorough article ASP.NET MVC 2 Localization complete guide to make sure I have all the bases covered. Following on his example of subclassing the DisplayName attribute, I am considering subclassing all the DataAnnotation validation attributes to use a by-convention approach to finding the appropriate resource strings. This doesn’t have so much to do with the T4 template itself but it strikes me as a good next step in reducing the redundant code needed to perform localization.
Download the Template
Download the template here: T4resx.zip