Introduction
This article describes how to generate a class containing literal strings for the resources defined in a .resx resource file using a simple T4 template.
Background
When applying extra DataAnnotations like [Display] or [Required] to your business entities to add metadata resource texts to properties, a type error is easily made, and there is no way the compiler can help you in defining the problem. You'll only the see an error when running the Silverlight application.
Problem Description
I've created a default Silverlight Business Application using VS2010, which looks like this:
In this project, there is a User.shared.cs file which adds some DataAnnotations to define extra metadata for this entity.
The code looks as follows:
public sealed partial class RegistrationData
{
[Key]
[Required(ErrorMessageResourceName = "ValidationErrorRequiredField",
ErrorMessageResourceType = typeof(ValidationErrorResources))]
[Display(Order = 0, Name = "UserNameLabel",
ResourceType = typeof(RegistrationDataResources))]
[RegularExpression("^[a-zA-Z0-9_]*$",
ErrorMessageResourceName = "ValidationErrorInvalidUserName",
ErrorMessageResourceType = typeof(ValidationErrorResources))]
[StringLength(255, MinimumLength = 4,
ErrorMessageResourceName = "ValidationErrorBadUserNameLength",
ErrorMessageResourceType = typeof(ValidationErrorResources))]
public string UserName { get; set; }
...
When running this Silverlight application, you see the following Register screen:
As you can see, everything looks correct.
But what happens when you make a type mistake (UserNameLabel_
instead of UserNameLabel
)?
The project does compile correct, and no errors or warnings are shown. But when running the application, the Register screen has some errors:
The errors are:
- UserName is displayed last instead of first.
- UserName is not translated correctly anymore to 'User name'.
Solution for This Problem
My solution for this problem is to create a T4 template which will read the .resx file in the MyBusinessApplication.Web project and create a class which defines all the strings defined.
The simple T4 template looks like this:
<#@ template debug="true" hostSpecific="true" #>
<#@ output extension=".cs" encoding="ASCII" #>
<#@ Assembly Name="System.Core.dll" #>
<#@ Assembly Name="System.Xml.dll" #>
<#@ Assembly Name="System.Xml.Linq.dll" #>
<#@ import namespace="System" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Diagnostics" #>
<#@ import namespace="System.Reflection" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Xml.Linq" #>
<#
string resourceFile = "RegistrationDataResources";
string PostFix = ""; // String / Label
#>
//------------------------------------------------------------------------------
// <auto-generated>
// This code was auto-generated at <#= DateTime.Now #>.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace MyBusinessApplication.Web.Resources
{
using System;
/// <summary>
/// A static helper class which exposes all strings as const
/// strings which are present in the resource file.
/// </summary>
[System.CodeDom.Compiler.GeneratedCodeAttribute(
"RegistrationDataResources.shared.tt", "1.0.0.0")]
public static class ResourceLiterals
{
<#
string filename = Path.Combine(Path.GetDirectoryName(this.Host.TemplateFile),
resourceFile + ".resx");
var names = (from e in XElement.Load(filename).Elements("data")
select e.Attribute("name").Value).ToList();
int idx = 0;
string name = names[0];
#>
/// <summary>
/// The string <#=name#>
/// </summary>
public const string <#=name#><#=PostFix#> = "<#=name#>";
<#
for (idx = 1 ; idx < names.Count(); idx++)
{
name = names[idx];
#>
/// <summary>
/// The string <#=name#>
/// </summary>
public const string <#=name#><#=PostFix#> = "<#=name#>";
<#
}
#>
}
}
Create a file called RegistrationDataResources.shared.t and add this to the solution. Copy the contents from above to this .tt file.
The solution looks like this now:
The file RegistrationData.cs can now be updated to use the static class 'ResourceLiterals
':
public sealed partial class RegistrationData
{
[Key]
[Required(ErrorMessageResourceName = "ValidationErrorRequiredField",
ErrorMessageResourceType = typeof(ValidationErrorResources))]
[Display(Order = 0, Name = ResourceLiterals.EmailLabel,
ResourceType = ResourceLiterals.ResourceType)] [RegularExpression("^[a-zA-Z0-9_]*$",
ErrorMessageResourceName = "ValidationErrorInvalidUserName",
ErrorMessageResourceType = typeof(ValidationErrorResources))]
[StringLength(255, MinimumLength = 4,
ErrorMessageResourceName = "ValidationErrorBadUserNameLength",
ErrorMessageResourceType = typeof(ValidationErrorResources))]
public string UserName { get; set; }
...
Note: The file name must be *.shared*, else the Silverlight Presentation project cannot use it. (RIA Services will not generate client-side code.)
Conclusion
Now you have strongly typed resource literals. Use this T4 template as a starting point and adapt it to your own needs.
Source code can be found here.
History
- First version posted on 2010-08-17.