Tuesday, March 30, 2010

WPF Localization

Firstly, let me address a misnomer. A lot of people call the process of adding other languages, internationalisation.  This is inaccurate, firstly you are adding more than languages, your adding culture sensitive spelling for example. The process is called Localisation, as you're localising an application to a specific culture, not necessarily another language.

My key goals of internationalisation are:

  1. Replacement of strings served by the application code onto the UI.
  2. Correct formatting of numbers and dates.
  3. Best practices for UI layout to allow dynamic resizing (text could grow or shrink depending on spelling and language).
  4. Replacement of hard coded strings used in UI code (labels, titles etc).
  5. Possibly full replacement of a UI form if significantly layout changes need to be made to accommodate culture.

This is a good overview of available options and approaches: 

Setup
I have found making en-US the default culture to be the easiest approach.  Only the concrete Application executable is localised (in my opinion anyway), I also believe that exceptions are not translated and are logged only and not presented to the user in their raw form.
Set the default culture at the top of the csproj file as follows:

Set the default culture in AssemblyInfo.cs

   31 [assemblyNeutralResourcesLanguage("en-US"UltimateResourceFallbackLocation.Satellite)]

And finally for testing its best to be specific about the culture you want to use when running on a developer machine. For production remove code lines 9 - 14.  This code needs to be run as soon after the application starts as possible. Preferably in App.Xaml.cs constructor or in the void Main startup.

    9             var culture = new CultureInfo("en-US");
   10             // var culture = new CultureInfo("en-NZ");
   11 
   12             Thread.CurrentThread.CurrentUICulture = culture;
   13             Thread.CurrentThread.CurrentCulture = culture;
   14 
   15             // Ensure the current culture passed into bindings is the OS culture.
   16             // By default, WPF uses en-US as the culture, regardless of the system settings.
   17             FrameworkElement.LanguageProperty.OverrideMetadata(
   18                 typeof(FrameworkElement),
   19                 newFrameworkPropertyMetadata(XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag)));

Using the Vs Command-Line prompt run the following commands:
msbuild /t:updateuid ApplicationProject.csproj

This will generate x:uid attributes in all xaml files automatically.
Check the process was successful by running (or check to see if any new elements have been added without x:uid's)
msbuild /t:checkuid ApplicationProject.csproj

Note: x:uid is a completely different attribute to x:Name. They are used for different purposes. They are not used in code.

Build the application at this point to generate the default culture satellite assembly.  A folder called en-US should appear under bin containing a *.Resources.dll.


1) Replacement Strings Served by the Application Code.
Use a standard Resource String approach for this (.resx file). Best practice is to place them all in the same folder called Resources (not under Properties).  Name them as per diagram.  Also String.resx and Strings.en-US.resx are exactly the same (this probably isn't necessary, but found it to be clear and could allow changing the default culture easily).

Build the application again and a new en-NZ folder should appear under bin containing a new *.Resources.dll.
At this point the satellite assemblies only contain string resources from the resx files.


2) Correct Format of Dates and Numbers
This is simply a best practice step.  All formatting should be done using standard techniques. For example:

dateTime.ToString("d") for short date using current regional format.
dateTime.ToString("D") for long date format.
dateTime.ToString("f") for short date/time pattern.
dateTime.ToString("F") for long date/time patten.
See msdn for more information.

Don't do this:
dateTime.ToString("dd-MM-yy")


3) Xaml Layout Best Practice
Again this is simply a best practice step. Any element that contains text must be capable of growing and shrinking to accommodate more or less text after translation. Try to avoid fixed sizing and positioning.  This is exactly the same as developing in HTML to allow different screen sizes.  Do not put language resources in static images.  It is ok however to embed hard coded strings, field labels and titles into xaml as these will be translated using step 4.  Try to avoid excessive complexity by overusing resource (resx) files.  This can be avoided by hard coding text into the xaml files where it makes sense to do so (ie it doesnt change unless culture changes).  


4) Replacement of Hard Coded Strings in Xaml
Download and compile the LocBaml tool from Microsoft.
(I have a downloaded version of this tool and I have added extra runtime feedback text, see me for more info).

The LocBaml.exe tool must be located in the bin folder of the application with all the other dependencies of the application.  
Run the tool from the bin folder of the application with the following parameters to extract the xaml string resources for localisation:
LOCBAML /parse en-US\ApplicationProject.Resources.dll

This points the tool at the existing en-US resource dll and extracts the xaml strings into an external file.  This is so the file can be translated and then used to create a new resource dll 

This will create a new CSV file called: ApplicationProject.Resources.csv.
I prefer to rename this file to the intended culture and relocate it to the main project folder.  In this example I would simply name this file en-NZ.csv.


This will be a persistent file and should be version controlled.  It may need to be regenerated and merged with the new one in the future if xaml layout changes.

Edit this file with Excel (or similar) and filter the third column to hide anything classified as a "None". This leaves text contain that needs to be considered for translation.
Column headings are as follows:
Resource File, Resource Key, Localisation Category, Readable, Modifiable, Value.

Build the new satellite assembly as follows from the bin folder of the application (remember the csv file is in the main project folder):
LOCBAML /generate en-US\ApplicationProject.Resources.dll /trans:..\..\en-NZ.csv /cul:en-NZ /out:en-NZ

This will create the new dll containing the translated xaml into the satellite assembly in the en-NZ folder. There is a problem however.  It overwrites the dll currently in that folder meaning all the Resource strings (resx) are now not in the satellite assembly.
This is solved by using a post build task to run a batch file to create the translated xaml resources and link it with the resx resources.

Here's the batch file called CombineResxBaml.cmd
@ECHO OFF
REM                                                        %1            %2    %3              %4            %5
REM USAGE Post Build Task: $(TargetDir)CombineResxBaml.cmd $(ProjectDir) en-NZ $(PlatformName) $(TargetName) $(ConfigurationName)
REM %1 Example C:\Development\ApplicationName\
REM %2 Example en-NZ
REM %3 Example x86
REM %4 Example ApplicationName
REM %5 Example Debug

REM en-US must be the default language in your csproj.

cls
IF %3 == x86 CALL "c:\Program Files\Microsoft Visual Studio 10.0\VC\bin\vcvars32.bat"
IF %3 == x64 CALL "c:\Program Files\Microsoft Visual Studio 10.0\VC\bin\vcvars64.bat"
cd %1

resgen .\Resources\Strings.%2.resx
copy .\Resources\Strings.%2.resources bin\%5\%2
cd .\bin\%5

IF NOT EXIST "%1\bin\%5\%2" MD "%1\bin\%5\%2"

locbaml /generate ..\..\obj\%3\%5\%4.g.en-US.resources /tran:..\..\%2.csv /cul:%2 /out:.\%2
ECHO Baml Generation Complete.
ping 127.0.0.1 -n 3 -w 1000 > nul

cd %2
del %4.Resources.Strings.%2.resources
ren Strings.%2.resources %4.Resources.Strings.%2.resources

del %4.Resources.dll
al /template:"..\%4.exe" /embed:%4.g.%2.resources /embed:%4.Resources.Strings.%2.resources /culture:%2 /out:%4.resources.dll

cd ..\..\..

And the post build task looks like this to create a en-NZ satellite:
$(TargetDir)CombineResxBaml.cmd $(ProjectDir) en-NZ $(PlatformName) $(TargetName) $(ConfigurationName)

If more than one alternate culture is targeted then add more post build tasks:
$(TargetDir)CombineResxBaml.cmd $(ProjectDir) en-NZ $(PlatformName) $(TargetName) $(ConfigurationName)
$(TargetDir)CombineResxBaml.cmd $(ProjectDir) fr-FR $(PlatformName) $(TargetName) $(ConfigurationName)

5) Full Replacement of Xaml Where Necessary
To do...


Maintenance
During the application life cycle is going to evolve.  When it does the culture.csv files will need to be regenerated.  I would suggest a merge based approach to preserve existing translations into newly generated csv files.  It is also advisable to try to reuse x:uid in xaml development to avoid having to merge the culture.csv files altogether.


All in all I think this is a highly flexible localisation approach and certainly better than previous attempts in winforms and asp.net.

Thursday, March 25, 2010

Unit Testing with Private Accessors Part 2

In the previous post, I demo'ed my static helper class method of accessing private members.  In this post I wanted to explore the possibility of creating a T4 code generator that given a type creates a strongly typed wrapper that tests can access simply and with very readable test code.
Lets start at the end and work back to the beginning. Here's the use case...

Here's a sample of how my static helper class would look in a use case:


   24         [Test]
   25         public void TestUsage() {
   26             var target = new PrivateAccessorGeneratorTestClass();
   27 
   28             PrivateAccessor.SetProperty(target"privateInt"1);
   29             PrivateAccessor.SetProperty(target"PrivateObject"new List<string>(new[] { "Ben","Rees" }));
   30             PrivateAccessor.SetProperty(target"privateString""something");
   31             PrivateAccessor.InvokeMethod(target"VoidMethod");
   32 
   33             var methodResult = PrivateAccessor.InvokeMethod<int>(target"IntMethod"newobject[] { 1"2" });
   34             var privateIntResult = (int)PrivateAccessor.GetField(target"privateInt");
   35             var privateObjectResult = (IList<string>)PrivateAccessor.GetProperty(target,"PrivateObject");
   36             var privateStringResult = (string)PrivateAccessor.GetProperty(target,"privateString");
   37 
   38             Assert.AreEqual(1privateIntResult);
   39             Assert.AreEqual(1methodResult);
   40             Assert.AreEqual(2privateObjectResult.Count);
   41             Assert.AreEqual("something"privateStringResult);
   42         }


And here's how the new use case would look using the generated strongly typed wrapper...

    8         [Test]
    9         public void TestUsage() {
   10             var target = new PrivateAccessorGeneratorTestClass();
   11             var accessor = new PrivateAccessorGeneratorTestClass_Accessor(target);
   12 
   13             accessor.privateInt = 1;
   14             accessor.PrivateObject = new List<string>(new[] { "Ben""Rees" });
   15             accessor.privateString = "something";
   16             accessor.VoidMethod();
   17             var result = accessor.IntMethod(1"2");
   18 
   19             Assert.AreEqual(1accessor.privateInt);
   20             Assert.AreEqual(1result);
   21             Assert.AreEqual(2accessor.PrivateObject.Count);
   22             Assert.AreEqual("something"accessor.privateString);
   23         }
The target is the class with non-public members to which we need to access and the accessor is the strongly typed generated wrapper class that allows easy access and takes care of the finding and invoking the members using reflection .

Here is the target class (for clarity):

   26     public class PrivateAccessorGeneratorTestClass {
   27         public PrivateAccessorGeneratorTestClass(string data) {
   28             // omitted for clarity - not important
   29         }
   30 
   31         internal int privateInt { getprivate set; }
   32 
   33         internal List<string> PrivateObject { getprivate set; }
   34 
   35         private string privateString { getset; }
   36 
   37         private void VoidMethod() {
   38             // omitted for clarity - not important
   39         }
   40 
   41         protected int IntMethod(int istring s) {
   42             // omitted for clarity - not important
   43         }
   44     }

The generator produces the PrivateAccessorGeneratorTestClass_Accessor class based on the type it is give, which in this example case is the PrivateAccessorGeneratorTestClass.
I won't go into detail on how the generator works just now, suffice to say it just enumerators through all non-public members and outputs a wrapping method to call the method on the target type using reflection. 

This all looks pretty good from a use case point of view.  However there are a number of down sides:

Pros / Cons:
+ Nice readably use case syntax.
+ Easy to use in testing.
- Requires maintenance of the Code Generator TT file. A list of types must be given to the generator to produce an accessor for each type required in testing.
- The code generator doesn't automatically generate during compile (could be automated with a batch file). [Edit 26-March] Fixed see below.
- Once the code generator has run, it file-locks the assemblies it references, which quite often stops the build process because it needs to rewrite the DLL file.
- Simply renaming or removing a member's name, will still successfully compile, because the code generator must be run manually. [Edit 26-March] fixed see below.

There are some ways around these downsides.  One idea I will pursue is to make a copy of the solution DLL's and the TT generator file can reference those. This copy an take place pre-build, and so could the execution of the code generator.  This might circumvent the locking issue.  (Although it could just defer it to the second build). Then renaming a member name would cause a compile error on the second compile after the change.

More research I think.

[Edit 26-March-2010]
I have explored automating template transform on building the solution (in Vs not CI).  I wondered if this might circumvent the problem with file locking references to class libraries.  This is how I did it:

  1. Add a batch file to your solution with the following text:
    "%CommonProgramFiles%\microsoft shared\TextTemplating\10.0\texttransform.exe" -out "%1.cs" -P "%ProgramFiles%\Reference Assemblies\Microsoft\Framework\v3.5" -P %2 "%1.tt"

    (Without the carriage returns)
  2. Add a Pre-Build task to the project which contains the TT file:
    $(SolutionDir)RunTemplate.bat $(ProjectDir)AccessorGeneratorTestClass.g $(OutDir)

    Replace "AccessGeneratorTest.g" with the name of your template to transform.  Note that the .TT extension is missing.  My actual file is named AccessGeneratorTest.g.tt in this example, and in the above commandline I have dropped the .TT.
  3. Build the solution, this will transform the template first.

However it doesn't prevent the T4 engine from file locking the referenced Dll's.  This really limits the ease of use of these templates.

I still need to explore copying the DLL's on a Pre-Build task. Although the chances for that to work are slim.

[Edit 1-April-2010]
A colleague of mine (thanks Simon) found an excellent toolkit for T4 called funnily enough T4 Toolkit.  Its written by Oleg Sych a very respectable developer and authority on all things T4.

Inside this toolkit there is a alternative preprocessor to reference an assembly and it copies the assembly instead of referencing it in place. And this gets around the reference locking issue.  Wow, thanks Oleg.  To use Oleg's toolkit you MUST install it with his MSI you cannot simply reference the dll.

So now the Pros vs Cons of using a generated accessor the Pros far out way the Cons.

Here's a use case template of the T4 using Oleg's preprocessor.

// <autogenerated/>
// Last generated <#= DateTime.Now #>
<#@ template language="C#" hostspecific="true"#>

<#@ assembly name="System" #>

<#@ VolatileAssembly processor="T4Toolbox.VolatileAssemblyProcessor" name="bin\debug\FrameworkWpf.dll" #>
<#@ VolatileAssembly processor="T4Toolbox.VolatileAssemblyProcessor" name="bin\debug\FrameworkTestToolkit.dll" #>
<#@ VolatileAssembly processor="T4Toolbox.VolatileAssemblyProcessor" name="bin\debug\WpfAppTemplate.exe" #>

<#@ output extension=".cs" #>

<#@ import namespace="System" #>
<#@ import namespace="FrameworkTestToolkit" #>

namespace WpfAppTemplateTest {
using System;
using System.Reflection;
<# 
    // Add new types into the below array:
    Type[] types = new Type[] { 
typeof(FrameworkWpf.SafeEvent),
typeof(FrameworkWpf.Mvvm.ControllerBase),
typeof(FrameworkTestToolkit.PrivateAccessorGeneratorTestClass),
typeof(WpfAppTemplate.PostController),
typeof(WpfAppTemplate.ShellController),
};


// Do not modify this code
foreach (Type type in types) {
PrivateAccessorGenerator builder = new PrivateAccessorGenerator(type, WriteLine, Error, Warning);
builder.Generate();
}
#>
}

Happy generations.
[Edit] See this post for more information: http://blog.rees.biz/2010/03/private-accessor-t4-generation-for.html

Saturday, March 20, 2010

Unit Testing with Private Accessors Part 1

20-March-2010
Recently I have rewritten a class I use for accessing non-public fields and properties during unit testing and simplified it a little.

The problem:
Unit tests and testing code is always kept in a separate assembly.  To effectively unit test a class you will need to be able to set properties and fields that are not public to give that class under test its dependencies.  The first mistake some people make is to make properties public or create more constructors. This is a very smelly idea. It makes the production code more difficult to use as it appears consumers have access to more members on a class than they need. Inevitably, they will use some inappropriate members and it will blow up.

My Previous Solutions:
One solution is to use an assembly based attribute that extends trust from one assembly to another and allows the other assembly access to its internal members. See an earlier blog post for more information. This is still not ideal as it is polluting a production library and still only means you can access a class's internals when you need to access its privates too. (No pun intended).  

Tim Stall gives a good overview of some more tried and true techniques here. These however, seem to require polluting actual code with test harnesses etc, not ideal.
I have also seen a extension method based technique to create a utility style class to wrap some of the more complex reflection nastiness.
MSTest also has a real nice feature for auto-generating private accessors, see msdn for more information. I personally don't like mstest, see pros and cons here and here. (Basically, NUnit has better mocking support, easier integration into CI, and runs faster and more predictably with multi-threading).

I have tried using a hybrid of MsTest and NUnit, and although this allows use of MsTest's private accessor generator, it also means both libraries must be referenced. That is a bad idea as it is too easy for a developer to mistakenly use the wrong test attributes and those tests are not run during the CI process.

A "Not-Bad" Solution:
Using a "private accessor" it is possible to reflect across a type and invoke any public member as long as you can spell the name of the member as a string.  (I have seen some interesting cases of developer spelling, for example Organator meant to be Originator, Igor, tsk tsk).  
My current solution is to use a helper style static class called PrivateAccessor that has easier to use methods than using reflection directly.

Pros / Cons:
+ Enables access to any member or constructor on a type.
- It uses reflection, which always seems to smell funny. (But hey this is test code only).
- If a member is renamed the test will not give a compile time warning. Meaning the offending name changer will probably not fix the test.
+ Test will fail if member is renamed.
- Syntax is a little lengthy and wordy. (But again test code needn't be supermodel status).

Usage:
Consider this class in a production library.
   31     public class Class1 {
   32         internal Class1(string something) {
   33         }
   34 
   35         public void DoSomething() {
   36             var existingInstance = new Widget();
   37             ObjectFactory.Initialize(init => {
   38                 init.For<ILogWriter>().Use<FakeLogWriter>();
   39                 init.For<IService>().Singleton().Use<MyService>();
   40                 init.For<IWidget>().Use(existingInstance);
   41                 init.For<IComplex>().Use(() => new ComplexToBuildObject());
   42             });
   43 
   44             var service = ObjectFactory.Container.GetInstance<Service.IService>();
   45         }
   46     }
The class has a non-public accessor, so ordinarily it cannot be created outside its own assembly. We need to create a new instance to test it, without relying on any other peice of code. We only want to target this class for testing.

A test to create and test the constructor.
   96         [Test]
   97         public void GetObject() {
   98             var target = PrivateAccessor.PrivateConstructor<Class1>(() => "something");
   99             Assert.IsNotNull(target);
  100         }
The Private Accessor wraps the nasty reflection code and makes it easier to use and read. Lambda is used to pass in strongly typed arguments for the constructor.

The private accessor code:
   10     public static class PrivateAccessor {
   11         public static T PrivateConstructor<T>() where T : class {
   12             var type = typeof(T);
   13             var constructor = type.GetConstructor(new Type[] { });
   14             return constructor.Invoke(new object[] { }) as T;
   15         }
   16 
   17         public static TClass PrivateConstructor<TClass>(params Func<object>[] getArguments)where TClass : class {
   18             var type = typeof(TClass);
   19             var args = (from getter in getArguments
   20                         select getter())
   21                             .ToArray();
   22             var argTypes = (from arg in args
   23                             select arg.GetType())
   24                             .ToArray();
   25             var ctor = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance |BindingFlags.PublicnullargTypesnull);
   26             return ctor.Invoke(argsas TClass;
   27         }
   28     }

In exactly the same way properties can be "getted" and "setted"
   29         public static object GetProperty(object instancestring propertyName) {
   30             if (instance == null) {
   31                 throw new ArgumentNullException("instance");
   32             }
   33 
   34             if (string.IsNullOrEmpty(propertyName)) {
   35                 throw new ArgumentNullException("propertyName");
   36             }
   37 
   38             var info = instance.GetType().GetProperty(propertyNameBindingFlags.Instance |BindingFlags.NonPublic | BindingFlags.Public);
   39             return info.GetValue(instancenew object[] { });
   40         }
   41 
   42         public static void SetProperty(object instancestring propertyNameobject value) {
   43             if (instance == null) {
   44                 throw new ArgumentNullException("instance");
   45             }
   46 
   47             if (string.IsNullOrEmpty(propertyName)) {
   48                 throw new ArgumentNullException("propertyName");
   49             }
   50 
   51             var info = instance.GetType().GetProperty(propertyNameBindingFlags.Instance |BindingFlags.NonPublic | BindingFlags.Public);
   52             info.SetValue(instancevaluenew object[] { });
   53         }

And of course methods, constants and fields can be access in the same way.

The only thing I don't like about it is having to use magic strings that will not give me compile time errors if things are not right.
For example:

  122         [Test]
  123         public void GetObject() {
  124             var target = PrivateAccessor.PrivateConstructor<Class1>(() => "something");
  125             Assert.IsNotNull(target);
  126 
  127             var propertyValue = PrivateAccessor.GetProperty(target"State"as string;
  128             Assert.AreEqual("OK"propertyValue);
  129         }

Now if the State property changes its name or is removed, the test will compile, but fortunately will not pass, not really ideal, because the offending developer has already checked in the code with running the tests and has broken the build, discovering the problem a little too late.

In Part 2 I'll attempt to write a T4 Code Generator that will allow strongly typed access to non-public members.