Globalisation and Localisation

Ok, so I said I wasn’t going to do a post on globalisation and localisation because there’s so much out there already. However, I thought I’d put together some of my thoughts and examples I’ve found.

I want to show how to use a separate Resources folder to keep all localisation files instead of using the global or local resource folders that Visual Studio can create. This will allow us to access the resources at any time, including for testing.

I’m using the Contoso University project that you can download here.

So, create a folder called ‘Resources’ and add a couple of .resx files under the folder. One is to be called Student.resx and one to be Student.fr.resx

Next, set the properties on each file to be an Embedded Resource (if not already). Set the ‘Custom Tool’ property to ‘PublicResxFileCodeGenerator’. Set the ‘Custom Tool Namespace’ to be ‘Resources’. This will give us an easy accessor to the resource files. Make sure the project builds at this point.

We can now add some strings to our resx files. Add the following Name Value pairs to the Student.resx file:

and the following pairs to the Student.fr.resx file

We can now configure the Student model to make use of the resources for our validation. In the Student.cs file under the Models folder, add the following attributes:

        [Display(Name = "StudentName", ResourceType = typeof(Resources.Student))]
        [Required(ErrorMessageResourceType = typeof(Resources.Student), ErrorMessageResourceName = "StudentNameRequired")]
        [StringLength(50, ErrorMessageResourceType = typeof(Resources.Student), ErrorMessageResourceName = "StudentNameTooLong")]
public string LastName { get; set; }

The attributes will set our display name, the error message to show it is required and the maximum length of the field. You should see in Visual Studio that we have the intellisense for the Resources object. Unfortunately, it’s not pretty. Phil Haack has some thoughts on this here. It’s a proof of concept that hasn’t been updated but has some great ideas.

 

Further reading:

We can also use fluent validation to create validation rules on the model. This does mean the rules are somewhere else than on the model. The rules can get overlooked. Jerrie Pelser has a good article here.

 

Datepicker Localisation

I won’t go into web site localisation here. There’s enough on the web about that. Take a look at Scott Allen’s blog for some good stuff there. Here I want to localise just the jQuery datepicker. The jQuery documentation has all the info you need to hard code the language into your datepicker but I wanted to know how to get all that good stuff from the client’s browser or current thread in MVC.

So what they are saying is that if you want to show the datepicker to someone using their browser set to French, then load up the French js file something like this:

<script src="datepicker-fr.js"></script>

I understand that it’s great to have these files as small as possible and loaded only when needed but how do you get to load the correct file if you don’t know where the user is coming from?

Well, I guess you can add the language to the html element. You can do it like this:

<html lang="@System.Threading.Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName">

This will give us a nice lang=”en” property of the html element. However, if you take a look at your windows settings you’ll find there’s a lot more to the languages than just “en”. Here in Blighty we’re well aware that British English is quite different to American English. The default “en” is American English and you get all the American date formats etc. Windows shows us there are 16 english language variants.

So, the country is also important. This is why we need to use the language-COUNTRY setting. However, I don’t see most of those in the jQuery ui svn on google. There are many languages but they don’t hold the variants. The good people at w3c internationalization (i18n) show us how to do it.

We can then use the following – CurrentUICulture.Name – to get our <lang>-<COUNTRY> setting to give the end user the experience they deserve.

<html lang="@System.Threading.Thread.CurrentThread.CurrentUICulture.Name">

However, many of these variants don’t seem to be supported by the URLs at the google svn. So, we need a solution with all the possibilities. This might sound like overkill, but there’s nothing more annoying than being presented with a form to fill in with a strange date format, or a decimal separator that you’re not used to.

You can search the nuget packages for ‘jquery-ui-i18n’ and you’ll find a script that contains all the above variants for the datepicker that we need.

There’s some further reading here by Richard Ishida. I worked at Xerox at the same time as Richard and he was the leading light in everything to do with localisation.

Now we have a script full of translations for the datepicker. We just need to know how to use it.

We can use our immediately called function in contoso.js (see previous post) to grab the language:

    // Grab the language of the html element.
    var lang = $('html')[0].lang;

We can set the datepicker defaults (from the i18n js file) by calling the setDefaults method of the datepicker:

    // default format for the current culture on date pickers
    $.datepicker.setDefaults($.datepicker.regional[lang]);

But, even though we have a better set of localisations in the i18n file, we still have neutral language variants instead of the culture provided by .net

For example, we don’t have fr-FR because French for France is the neutral variant. So, if we can’t find the language-COUNTRY variant in the file, we need to drop back to the neutral one.

    // try to get the full language-COUNTRY variant of the user's settings.
    if ($.datepicker.regional[lang] == undefined) {
        // Change lang to neutral language variant. e.g. instead of fr-FR, use fr.
        lang = lang.substr(0, 2);
    }

Also, we can’t use “en” because it’s the default language. We have to send through an empty string.

    // default format for the current culture on date pickers
    $.datepicker.setDefaults($.datepicker.regional[(lang === 'en' ? '' : lang)]);

If we do all this before we turn the text boxes into datepickers:

    // Turn them all into date pickers.
    $(":input[data-datepicker=true]").datepicker();

Then, we will have the correct localisation for our datepicker.

 

In Internet Explorer, with the language priority setting given to fr-FR:

We get:

 

Localisation is a massive task when creating multi-language web sites. It’s best thought out at the start of the project because it’s a huge effort to come and do it at the end.

As a final note, if you’re using Chrome as the browser you may need to include this fix for it’s validation of localised dates.

Scott Hanselman has some similar thoughts on his blog over here.

 

Use a Custom Template in MVC to Automatically Create Datepicker Text Boxes

I started out on this challenge trying to find a way to automatically setup a jQuery datepicker on each rendered text box that had or needed to contain a date. I wanted to keep the standard Html.EditorFor in the MVC View without having to add special instructions to render the required text box that turned it into a datepicker. This is where the MVC Custom Templates come in.

The standard template for a datetime object is the String template. I wanted to create a DateTime template that would create all the necessary information for the jQuery datepicker.

In the Views, Shard folder, create an EditorTemplates folder.

In the EditorTemplates folder, Create a razor page called DateTime.cshtml

I’m using the ContosoUniversity project for the demo as shown below.

DateTime.cshtml

DateTime.cshtml

Replace the contents of the file with the following code:

@Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue,
                    new { @class = "text-box single-line", 
                            data_datepicker = "true" })

This will create our TextBox with a data- attribute we can use to hook up the datepicker.

Now, when we load an editor for a model with a DateTime object, MVC will find the DateTime custom template and use that instead of the default String template.

Before it magically appears on every page, we need to get jQuery to attach the datepicker to the new text boxes. We need to include a piece of javascript to do that. I added this line to the contoso.js file that I have in the standard bundle loaded on every page.

$(":input[data-datepicker=true]").datepicker();

An example of editing a student record with an enrollment date is shown below. We haven’t had to modify anything in the view to get this functionality.

Date Picker
Date Picker

The view still looks like this:

        <div class="editor-label">
            @Html.LabelFor(model => model.EnrollmentDate)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.EnrollmentDate)
            @Html.ValidationMessageFor(model => model.EnrollmentDate)
        </div>

The work here did lead me on to another post which I should write up now while I’m thinking about it. That is the use of the datepicker when used with localisation.

Northwind Repository Pattern

I like to use Entity Framework and the Repository pattern so here’s a simple example of setting one up. I’d also like to use the UnitOfWork pattern and I’ll do a post on that later.

We need to make sure we have separation of concerns as part of our SOLID principles.

Make sure you have the Northwind database installed in your local instance of SQL. You can grab it from here… http://northwinddatabase.codeplex.com/ and do database, Restore Database or follow the instructions from somewhere like here… http://www.codeproject.com/Articles/42837/HowTo-Install-the-Northwind-and-Pubs-Sample-Data

Start out with a new Visual Studio project… a Test project.

New Test Project

New Test Project

Then we’ll add a new project of class library to the solution. Give it a Data.Repository name.

Repository Class
Repository Class Library

Then we’ll need another new project of class library to the solution. Give it a Data name.

Data Class
Data Class

Then we’ll need to add an ADO.NET Entity Data Model to the Repository project.

ADO.NET Entity Data Model
ADO.NET Entity Data Model

When clicking add, we’ll be asked for the type of model. Select Generate from Database.

Generate from database
Generate from database

Click next and setup the database connection. Call it NorthwindEntityContext.

Database connection
Database connection

Then choose the database objects we need. Just tables. Give it the repository namespace.

Database Objects
Database Objects

… and click Finish.

We should then have the edmx file which is an xml representation of the Northwind data model.

edmx file
edmx file

Just to tidy things up, we can add a solution folder called Data and drag and drop our Data and Data.Repository projects into it.

Data Solution Folder
Data Solution Folder
Screenshot
Drag drop Data and Data.Repository

Now we have all of our entities in our Repository project. We don’t really want them there, so let’s create another new project four our entities (another Class Library) and call it ClearOak.Northwind.Entity

Add another solution folder and call it Domain.  Add our new project to the Domain folder.

Domain folder
Domain folder

Then add a text template to the Entity project. This will generate our entity classes for us from the edmx data model file.

T4 Template
T4 Template

Copy the contents of the NorthwindDataModel.tt file to the Entity.tt file.

Copy contents to new template
Copy contents to new template

Save it. Click ok to any questions about running templates.

It will probably complain about not finding the edmx file. Go to the top of the file and modify the reference from

const string inputFile = @"NorthwindDataModel.edmx";

to

const string inputFile = @"..\ClearOak.Northwind.Data.Repository\NorthwindDataModel.edmx";

and hit save. Again, click ok to any questions about running templates. This time the error should disappear. We should now have our entities in the Domain Entity project.

Domain Entity project
Domain Entity project

Then delete the NorthwindDataModel.tt file from the Repository project. We don’t need that anymore.

Add references in the Repository project to the ClearOak.Northwind.Data and ClearOak.Northwind.Entity projects.

Add a using statement to the Repository NorthwindDataModel.Context.tt file to reference the ClearOak.Northwind.Entity library.

using statement
using statement

Save and build the solution. It should build at this point. If it doesn’t, review some of the steps above.

Now move over to our unit testing project. Rename UnitTest1.cs to NorthwindTests.cs    Also update the name of the class in the code to NorthwindTests.

Update the TestMethod to give it an accurate name for the test being performed. Let’s call it Query_GetAllCustomers()

Start typing to get the list of Customers using the following code:

var list = _repository.All<Customer>

In order for this to compile we need a reference to the Entity project. Resharper will add this for you with its hints. This will give us the Customer object. We will create the _repository in a second.

Resharper
Resharper

Complete the line of code like this:

var list = _repository.All<Customer>();

Resharper will then allow us to extract the field name.

Extract Field
Extract Field

Then we have this:

private object _repository;

We want to create an interface to the repository so we change its type from object to IRepository.

private IRepository _repository;

Again, Resharper allows us to create the interface ‘IRepository’.

    internal interface IRepository
    {
    }

Resharper allows us to move this to its own file.

namespace ClearOak.Northwind.Data.Test
{
    internal interface IRepository
    {
    }
}

We are going to copy this file to our data project. Also delete it from the test project. Update the namespace and change from internal to public. Like this:

IRepository
IRepository

So, now we’ve got a test that doesn’t care where our data is. It only knows there’s an interface to a repository where it can get stuff.

Add references in our Test project to the Data and Entity projects.

Back in our Test Method:

[TestMethod]
        public void Query_GetAllCustomers()
        {
            var list = _repository.All<Customer>().ToList();
        }

Click on the red ‘All’ and use Resharper to add the method to our interface.

namespace ClearOak.Northwind.Data
{
    public interface IRepository
    {
        void All<T>();
    }
}

We also want to turn that from void into a generic IQueryable like this:

IQueryable<T> All<T>();

Back in our Test Method we can now iterate over a list of Customer objects returned from our repository.

        [TestMethod]
        public void Query_GetAllCustomers()
        {
 
            var list = _repository.All<Customer>().ToList();
 
            foreach (var item in list)
            {
                Trace.TraceInformation("Company Name : {0}",item.CompanyName);
            }
        }

Our _repository is still null, so we need to set it to a NorthwindRepository()

Create a TestInitialize method

        [TestInitialize]
        public void Initialise()
        {
            _repository = new NorthwindRepository();
        }

Use Resharper to extract a type for NorthwindRepository. Create as a class. Move to an external file and then copy it to the Repository project. Delete the NorthwindRepository.cs file in the Tests project.

In the newly copied file, use Resharper to implement the missing members.

Implement Members
Implement Members

We then get

using System.Linq;
 
namespace ClearOak.Northwind.Data.Repository
{
    public class NorthwindRepository : IRepository
    {
        public IQueryable<T> All<T>()
        {
            throw new System.NotImplementedException();
        }
    }
}

If we hit CTRL-R+T on our Test Method (to debug the current test) then we see that the Exception is hit in the Repository.

Exception
Exception

We are missing the NorthwindEntityContext. This is the piece that joins up our database to our Entity model. We need to create an instance of the Context.

Use ctor <tab><tab> to create the constructor. Then replace the exception and return something from the Context. We will see the list of objects from the database in our Context.

Context
Context

We need to return a generic type and not just Orders so we need to code for the generic result set.

The generic type is a Set of T. This complains it’s a reference type.

return _context.Set<T>();

So we have to add the class constraint to our method. Resharper can do this for us. We get this:

        public IQueryable<T> All<T>() where T : class
        {
            return _context.Set<T>();
        }

This then complains about the Type parameter T in All<T>. We will need to update the IRepository to have the same class constraint.

namespace ClearOak.Northwind.Data
{
    public interface IRepository
    {
        IQueryable<T> All<T>() where T : class;
    }
}

If we run our test now, we get a different exception where it can’t find a connection string. Copy the Repository app.config to the Test app.config. Now re-run the test and see we have the 91 Customers and all their associated data.

Customers
Customers

Phew! We’ve implemented an abstract generic repository. We can also make the Repository inherit from IDisposable so we can clean up after ourselves.

Now instead of getting all the data we might want to be a bit more selective. Let’s say we wanted to only get the Customers and their Orders. We can create a generic method like the All() method to return just what we need.

Start out with a new Test Method.

        [TestMethod]
        public void Query_GetAllCustomersIncludingOrders()
        {
 
            var list = _repository.AllIncluding<Customer>().ToList();

Use Resharper on AllIncluding to add the method to the Repository.

We have to turn it into an IQueryable and also add the class constraint. Also we need to tell it to expect a delegate – the Func in our method which is basically a pointer to a method.

    public interface IRepository: IDisposable
    {
        IQueryable<T> All<T>() where T : class;
        IQueryable<T> AllIncluding<T>(Func<T,object> include ) where T : class;
    }

Our AllIncluding in the NorthwindRepository starts out like this:

public IQueryable<T> AllIncluding<T>(Func<T, object> include) where T : class
        {
            var retVal = _context.Set<T>();
 
            return retVal;
        }

We need to get our generic data (Customer in our Test Method) but then tell it what to include.

We continue like this, telling it to expect an array of params but the Include statement wants a string. Item is not a string.

        public IQueryable<T> AllIncluding<T>(params Func<T, object>[] include) where T : class
        {
            var retVal = _context.Set<T>();
 
            foreach (var item   in include)
            {
                retVal = retVal.Include(item);
            }
            return retVal;
        }

We need to wrap the Func in an Expression. Func<T> denotes a delegate which is pretty much a pointer to a method and Expression<Func<T>> denotes a tree data structure for a lambda expression. For more see this stackoverflow question.

It also has issues converting a DbSet into an IQueryable so we need to define our retVal a bit more clearly.

        public IQueryable<T> AllIncluding<T>(params Expression<Func<T, object>>[] include) where T : class
        {
            IQueryable<T> retVal = _context.Set<T>();
 
            foreach (var item   in include)
            {
                retVal = retVal.Include(item);
            }
            return retVal;
        }

Then update our IRepository to have the same signature with the Expression.

So with a bit of a leap we can have many Test Methods using the Repository:

[TestClass]
    public class NorthwindTests
    {
        private IRepository _repository;
 
        [TestInitialize]
        public void Initialise()
        {
            _repository = new NorthwindRepository();
        }
 
        [TestMethod]
        public void Query_GetAllCustomers()
        {
 
            var list = _repository.All<Customer>().ToList();
 
            foreach (var item in list)
            {
                Trace.TraceInformation("Company Name : {0}",item.CompanyName);
            }
        }
 
        [TestMethod]
        public void Query_GetAllCustomersIncludingOrders()
        {
            // Use a Lambda expression to return the Customer's Orders 
            var list = _repository.AllIncluding<Customer>(p=>p.Orders).ToList();
 
            foreach (var item in list)
            {
                Trace.TraceInformation("Company Name : {0}, Orders Count : {1}", item.CompanyName, item.Orders.Count);
            }
        }
 
        [TestMethod]
        public void Query_GetAllCustomerNamesIncludingOrderCount()
        {
            // Use a Lambda expression to return the Customer's Name and Order count (but no more).
            // new gives us an anonymous object
            var list = _repository.AllIncluding<Customer>().Select(s=> new {s.CompanyName, s.Orders.Count}).ToList();
 
            foreach (var item in list)
            {
                Trace.TraceInformation("Company Name : {0}, Orders Count : {1}", item.CompanyName, item.Count);
            }
        }
 
 
        [TestMethod]
        public void Query_GetAllCustomerNamesIncludingOrderCountWithMoreThanThreeOrders()
        {
            // Use a Lambda expression to return the Customer's Name and Order count (but no more) who have more than 3 orders.
            // new gives us an anonymous object
            var list = _repository.AllIncluding<Customer>().Where(p=>p.Orders.Count>3).Select(s => new { s.CompanyName, s.Orders.Count }).ToList();
 
            foreach (var item in list)
            {
                Trace.TraceInformation("Company Name : {0}, Orders Count : {1}", item.CompanyName, item.Count);
            }
        }

These Test Methods can be seen converted to SQL in SQL Profiler.

 

 

 

 

Battery Life

Updated Battery Life to have a battery like power bar appear to the right of the screen where a scrollbar might be. The app was written to help get users of the device to notice how much battery life was left and prompt them to plug it in before they lose all their work. Well, it didn’t seem to make any difference to some people (you know who you are) so I needed something to appear on the screen every couple of minutes and keep reminding them of the battery life remaining. It’s only a small improvement but I think it’s quite handy when you’re on battery power and just need that little extra reminder.

JSONP or CORS – cross domain requests

Just been reading up about JSONP (JSON with Padding) and how you can use it to get cross domain requests to work. This is normally not allowed in browserland because of XSS (Cross Site Scripting) attacks, so I found it interesting that JSONP has managed to work some magic around this. It’s not a magic bullet because it has its own security implications – mainly because it executes a function on your client with the data that comes back from the remote server. That may not be a good idea if you don’t totally trust that server or it has been compromised. That’s where CORS comes in. CORS (Cross Origin Resource Sharing) allows other HTTPRequest types too, like PUT and DELETE so it’s a bit more flexible than the GET of JSONP. One to remember next time you need to do some cross domain access.

Links:

http://en.wikipedia.org/wiki/JSONP

http://en.wikipedia.org/wiki/Cross-origin_resource_sharing

https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS