web 2.0

Lambda Expressions


A lambda expression is an anonymous function (ie a function without a name). It is an inline expression or statement that can be used wherever a delegate type is accepted. There are two types of lambdas: Expression lambdas and statement lambdas.

An expression lambda has the following form:

The => is called the lambda operator and is read as "goes to". The left side specifies the input parameters (if any), and the right side holds the expression or statements (in case of a statement lambda).

For example

 (a, b) => a + b
 

The previous lambda accepts two input parameters (a and b) and sums the two of them (a + b).

The following lambda squares an integer

 a => a * a
 

When there is only one input parameter, you can omit the parenthesis. Also, a lambda expression can invoke another method:

() => callMethod()
 

The previous lambda takes no input and invokes the method callMethod.

The input parameters data type can be inferred from the context. However, sometimes it is hard to guess the right data type so in that case you must specify it

 (char c, string s) => s[0] == c;
 

The lambda above tests if a string starts with a specified character. The return value from the right side part (expression) is obviously Boolean.

The second type of lamda is called a statement lambda. A statement lambda can have statements in its body.

x => { return 2 * x; }         
 

In fact, this lambda returns double of the input x, and it is equivalent to the following lambda

 x => 2 * x          
 

All the previous examples can be done using regular C# methods, so what are Lambda expressions for?

A lambda expression is a convenient way to initialize a delegate. Recall that a delegate is a data type that stores an address to a method. It is like a function pointer in C++, but a delegate is type safe. You may have heard of events in Windows Forms or ASP.NET WebForms. In fact, an Event is a special type of delegate. For example, the Button class offers a Click event, you would define a handler method that is invoked whenever this event is fired.

A delegate can also be used to pass methods as arguments to other methods. For example, LINQ methods are a place where you would find lambda expressions very convenient.

  var numbers = Enumerable.Range(1, 20); // Generate integers from 1 to 20
  
  int[] evens = numbers.Where(n => n % 2 == 0).ToArray(); // Only get even integers only         
 

The lambda expression n => n % 2 == 0 takes an integer as input and returns true if the number is even and false if the number is odd.

The following lambda expression doubles a set of numbers

 int[] doubles = numbers.Select(n => n * 2).ToArray();
 

Func and Action delegates

If you hover the mouse over the Select method above, you will see the dollowing delegate as the input type

  Func<int, int> selector
  

Instead of creating your own custom delegates with different input parameters and return data type, the .NET Framework provides for you a flexible set of generic delegates, and these are Func and Action. The Action delegate references a method that has no return value (void), whereas Func references a method that has a return value.

These generic delegates can take up to 16 parameters of different data types. In the case of Func, the last parameter is always the return data type and all the previous ones are considered input parameters.

The following table helps you understand how they work

Delegate Description
    Func<int>
  

Takes no input

Returns an integer

  Func <bool, string>
  

Takes a bool value

Returns a string

  Func <int, int, int>
  

Takes two integers

Returns an integer

  Func <string, string, bool>
  

Takes two strings

Returns a bool

     Action
  

Takes no input

No return value

     Action<int>
  

Takes an inetger

No return value

     Action<int, bool>
  

Takes an integer and bool

No return value

     Action<int, int, string>
  

Takes two integers and a string

No return value

The following code takes advantage of one of these generic delegates

        static void Main(string[] args)
        {
            int[] arr = Enumerable.Range(1, 10).ToArray();

            var doubles = Process(arr, n => 2 * n); // 2, 4, 6, 8 ...
            var triples = Process(arr, n => 3 * n); // 3, 6, 9, 12, ...
            var squared = Process(arr, n => n * n); // 1, 4, 9, 16, ...


        }

        static int[] Process(int[] arr, Func<int, int> processor)
        {
            int[] result = new int[arr.Length];
            for (int i = 0; i < arr.Length; i++)
                result[i] = processor(arr[i]); 

            return result;
        }

 

The method Process accepts a delegate which takes an integer and returns an integer. We used three different lambda expressions.

Tags:

Paging, sorting, and searching using EF Code first and MVC 3


Introduction

In this blog post, I am going to show you how to search, paginate and sort information retrieved from a real database using Entity Framework Code First approach.

We are going to use SQL Server and the AdventureWorks Light database. AdeventureWorksLT is a good example. It is a stripped down version that contains only 12 tables compared to the 70 tables in the AdventureWorks full database. You can download it from http://msftdbprodsamples.codeplex.com/ . Alternatively, I generated a Transact-SQL script file which contains the whole database.

Installing AdventureWorksLT database

To run the script file, first extract the AdventureWorks.sql to your hard disk. If you have SQL Server Management Studio, you can open the script file and run it by hitting F5. Make sure you change the files path as highlighted below:

If you don't have Management Studio, click Start, click Run, and type sqlcmd –S (local) –i “%temp%\AdventureWorks.sql”

In my case, I extracted the file to the temporary folder on my hard disk, so you might need to adjust the file path.

Creating the MVC 3 web application

Select File, New Project, and choose ASP.NET MVC 3 Web Application after clicking the Web item on the left. Type AdventureWorksLT as the project name, and click OK.

Now choose the Internet Application template and make sure Razor is selected as the View engine.

Package Manager Console

We'll need to install Package Manager Console in case you don’t have it already installed.

Select Tools, and click Extension Manager. Choose Online Galery from the left pane. Type NuGet in the search box and wait a few seconds. Select NuGet Package Manager. Click Download. If you see a green check mark on the right side, this means the item has already been installed. You might need to restart Visual Studio after installation.

Entity Framework

We are going to use Entity Framework 4.2 as it is the latest release at the time of writing this post. If you don't have it already installed, open Package Manager Console

and type the following command:

Install-Package EntityFramework

And this installs the latest version for the current project.

There is one more thing we need to install. Select Tools, and click Extension Manager. Choose Online Galery from the left pane. Type DbContext in the search box and wait a few seconds. Select ADO.NET C# DbContext Generator. Click Download. If you see a green check mark on the right side, this means the item has already been installed.

Generating classes for our model

Usually, you'll use Code-First with databases that already exist. In this case, we have AdventureWorksLT.

Right click on the Models folder under Solution Explorer pane. Click Add, choose New Item.

Select Data from the Installed Templates pane. Click on ADO.NET Data Model, and type AdventureWorksLTModel.edmx in the name box. Click Add.

A new dialog appears, choose Generate from database item.

Click Next.

Choose New Connection from the new dialog. Configure the new dialog like below:

Your configuration depends on the edition of SQL Server that you have installed on your machine, so your configuration may not be exactly the same as above.

Click Test Connection to make sure everything is OK. You should see Test connection succeeded. Otherwise, make sure your server name and database name are both correct. When you click the down arrow in the textbox field, you'll see some options from which you can choose.

Click OK.

Click Next.

Just select Tables and click Finish.

You'll see that EF has generated entities from our database. Right click anywhere in the whitespace and choose Add Code Generation Item

Select Code from the Installed Templates pane. Click on ADO.NET DbContext Generator, and type AdventureWorksLTModel.tt in the name box. Click Add.

This will generate a DbContext class for our project, and a new class for each table that exists in the database.

Preparing the Controller

Now we need to retrieve information from the database. Open HomeController.cs and add the following lines of code.

We also need to add a new action to retrieve the products from our database. Add the following new action.

The Products action is restricted to the HTTP GET request. It is a good practice to use GET for actions that do not modify any data.

We are fetching all the products that exist in our database, and we are executing the query immediately by calling the ToList() method. We need to create a new view that will display the products to the end user.

Compile the project first and then right click within the Products action. Choose Add View. A new dialog appears. Check Create a strongly typed view, and choose Product (AdventureWorksLT.Models) from the Model class combo box. Also make sure that all the other options are like below:

Click Add.

We created a strongly-typed view. We want this view to show products information in a table. Modify the new generated view so that it looks like below:

Note that we re-defined the model to match our real model which is a list of products, not a single product.

We also defined an HTML table. We started by creating a header row that will hold the column titles. For every product in our database, we are interested only in a certain subset of its columns, not all of them. We are also showing a different background color for alternate rows using the modulus (%) operator.

Run the application now and type http://localhost:4843/home/products in your browser’s address bar. The port number might be different on your machine, so make sure you type the correct value.

If your table doesn't look like this, then you need to copy the styles from the CSS file in the attached project.

As you can see, the table is showing all the products. It would be much better in terms of performance and usability to show only a few products at a time, and allow the user to navigate through these sets using hyperlinks. We call this pagination. Let's make our website support pagination.

Modify the action Products in HomeController.cs like below:

Now if you compile the application and refresh your browser, you'll see only the first 10 products.

We defined a new variable called pageSize, which represents the maximum number of products we want to show at a time.

The OrderBy() method sorts the products by a specified property (ProductID in this case). We'll allow the user eventually to order products by any column they want. Anyway, The Skip() method skips a specified number of products. The expression (page -1) * pageSize skips all the products before the specified page. The Take() method only selects a certain number of products, which is pageSize.

We also defined the page parameter in the action Products, so that our users can select which page (ie subset of products) they want. We also made this parameter optional, so if the user does not supply a value, it will hold a default value of 1.

Currently, the only way to navigate through our products is to specify the page number in the browser's address bar directly like this http://localhost:4843/home/products?page=2. This is not a good approach and do not expect the end user to do it. We have to make things as easy as possible for our users.

We will show page links so that users can navigate easily through our products. First, we have to think of what things we need in order to create page links. We basically need three things:

  • Number of pages (we need the total number of products for this)
  • Page size (we already have it)
  • Current page number (we already have it)

We add these new variables in the Products action like this:

Some developers like to wrap these in a view Model instead of passing the values through the ViewBag property.

Now we need to show page links in our view Products.cshtml

The current page link is highlighted, so the user knows exactly which page is currently shown. The link for each page simply points to the same action Products but with a different value for the parameter page.

If you compile and refresh your browser, you'll see the following:

Oops!! Too many page links!

The links work fine. However, they are too many which makes our gridview look ugly, let's show a few page links at a time.

Modify the view Products.cshtml so that it looks like below


@model IList<AdventureWorksLT.Models.Product>

@{
    ViewBag.Title = "Products";
}


@helper buildLinks(int start, int end, string innerContent)
{
     for (int i = start; i <= end; i++)
     {
         <a class="@(i == ViewBag.CurrentPage ? "current" : "")" href="@Url.Action("products", "home", new { page = i })">@(innerContent ?? i.ToString())</a>  
     }
    
}


@helper pageLinks()
{
    const int maxPages = 11;
    
    if (ViewBag.TotalPages <= maxPages)
    {
        @buildLinks(1, (int)ViewBag.TotalPages, null)
        return;
    }

    int pagesAfter = ViewBag.TotalPages - ViewBag.CurrentPage; // Number of pages after current
    int pagesBefore = ViewBag.CurrentPage - 1; // Number of pages before current

    if (pagesAfter <= 4)
    {
        @buildLinks(1, 1, null) // Show 1st page

        int pageSubset = ViewBag.TotalPages - maxPages - 1 > 1 ? ViewBag.TotalPages - maxPages - 1 : 2;
        @buildLinks(pageSubset, pageSubset, "...") // Show page subset (...)

        @buildLinks(ViewBag.TotalPages - maxPages + 3, ViewBag.TotalPages, null) // Show last pages

        return; // Exit
    }

    if (pagesBefore <= 4)
    {
       @buildLinks(1, maxPages - 2, null) // Show 1st pages


       int pageSubset = maxPages + 2 < ViewBag.TotalPages ? maxPages + 2 : ViewBag.TotalPages - 1;
       @buildLinks(pageSubset, pageSubset, "...") // Show page subset (...)

       @buildLinks(ViewBag.TotalPages, ViewBag.TotalPages, null) // Show last page

        return; // Exit

    }

    if (pagesAfter > 4)
    {
        @buildLinks(1, 1, null) // Show 1st pages

        int pageSubset1 = ViewBag.CurrentPage - 7 > 1 ? ViewBag.CurrentPage - 7 : 2;
        int pageSubset2 = ViewBag.CurrentPage + 7 < ViewBag.TotalPages ? ViewBag.CurrentPage + 7 : ViewBag.TotalPages - 1;

        @buildLinks(pageSubset1, pageSubset1, pageSubset1 == ViewBag.CurrentPage - 4 ? null : "...") // Show 1st page subset (...)

        @buildLinks(ViewBag.CurrentPage - 3, ViewBag.CurrentPage + 3, null) // Show middle pages

        // Show 2nd page subset (...)
        // only show ... if page is contigous to the previous one.
        @buildLinks(pageSubset2, pageSubset2, pageSubset2 ==  ViewBag.CurrentPage + 4 ? null : "...")
        @buildLinks(ViewBag.TotalPages, ViewBag.TotalPages, null) // Show last page

        return; // Exit

    }    
}
<h2 class="center">Products</h2>

<table class="products">
 @* header *@
 <tr>
    <th>ID</th>
    <th>Name</th>
    <th>Number</th>
    <th>Color</th>
    <th>Standard Cost</th>
    <th>List Price</th>
    <th>Size</th>
    <th>Weight</th>
 </tr>

 
@{int i = 1;}
@foreach (var p in Model)
{    
    <tr class="@(i++ % 2 == 0 ? "highlighted" : "")">
        <td>@p.ProductID</td>
        <td>@p.Name</td>
        <td>@p.ProductNumber</td>
        <td>@p.Color</td>
        <td>@p.StandardCost.ToString("C")</td>
        <td>@p.ListPrice.ToString("C")</td>
        <td>@p.Size</td>
        <td>@p.Weight</td>       
    </tr>
}

</table>

<div class="pagination">
Page: 
@pageLinks()       
</div>

Don't worry if you do not understand completely the second helper as it is a little awkward.

We simply added two inline helpers. The first one, buildLinks, generates hyperlinks for pages with numbers between start and end. The parameter innerContent sets the inner html content for the hyper link. If this value if null, then we use the page number. We only used it for the page subset symbol ...

The second helper, pageLinks, which is very smart, depends on buildLinks to generate the required hyper links for pagination and shows only 11 links as a maximum.

Now refresh your browser and you should see a professional pagination tool:

Sorting

Now that we have enabled pagination, we also would like to allow the user to sort the products. Sorting is usually done in the database, and not in C#.

If you notice in the Products action, we already performed sorting on the column ProductID using the OrderBy method. How can the user tell us which column they want to sort? There are different ways to do this. Turning the table column headers into hyper links is pretty straightforward and widely used in many web applications.

Before we start, we want to modify our action so that it accepts the sorting column sent from the user's browser. Modify the Products action like below:


        [HttpGet]
        public ActionResult Products(int page = 1, int sortBy = 1, bool isAsc = true)
        {
            IEnumerable<Product> products;

            #region Sorting
            switch (sortBy)
            {
                case 1:
                    products = isAsc ? db.Products.OrderBy(p => p.ProductID) : db.Products.OrderByDescending(p => p.ProductID);
                    break;

                case 2:
                    products = isAsc ? db.Products.OrderBy(p => p.Name) : db.Products.OrderByDescending(p => p.Name);
                    break;

                case 3:
                    products = isAsc ? db.Products.OrderBy(p => p.ProductNumber) : db.Products.OrderByDescending(p => p.ProductNumber);
                    break;

                case 4:
                    products = isAsc ? db.Products.OrderBy(p => p.Color) : db.Products.OrderByDescending(p => p.Color);
                    break;

                case 5:
                    products = isAsc ? db.Products.OrderBy(p => p.StandardCost) : db.Products.OrderByDescending(p => p.StandardCost);
                    break;

                case 6:
                    products = isAsc ? db.Products.OrderBy(p => p.ListPrice) : db.Products.OrderByDescending(p => p.ListPrice);
                    break;

                case 7:
                    products = isAsc ? db.Products.OrderBy(p => p.Size) : db.Products.OrderByDescending(p => p.Size);
                    break;

                default:
                    products = isAsc ? db.Products.OrderBy(p => p.Weight) : db.Products.OrderByDescending(p => p.Weight);
                    break;
            }
            #endregion

            products = products             
                .Skip((page - 1) * pageSize)
                .Take(pageSize)                
                .ToList();
            
            ViewBag.CurrentPage = page;
            ViewBag.PageSize = pageSize;
            ViewBag.TotalPages = (int) Math.Ceiling((double)db.Products.Count() / pageSize);

            ViewBag.SortBy = sortBy;
            ViewBag.IsAsc = isAsc;

            return View(products);
        }

We added two new parameters to the action method, sortBy and isAsc. The sortBy parameter specifies which column to sort by, and the isAsc specifies the direction of sort ascending or descending. Note that we changed the data type of the products variable, so that it matches the return data type of the LINQ methods. We needed this because we deferred the LINQ queries. This means that the results are evaluated only when we call the ToList method. We also used a switch statement to determine the column of sorting. We took advantage of the ternary operator (?:) to specify the sorting direction. Note that we need to access two things in our view in order to provide sorting: sortBy and isAsc.

Now we have to give the end user the ability to sort. Modify the view like below:


@helper sortLink(string name, int id)
{
   <a href="@Url.Action("products", "home", new { sortby = id, isasc = (id == ViewBag.sortBy ? !@ViewBag.isAsc : @ViewBag.isAsc).ToString().ToLower() })">@name</a> 
}
<h2 class="center">Products</h2>

<table class="products">
 @* header *@
 <tr>
    <th>@sortLink("ID", 1)</th>
    <th>@sortLink("Name", 2)</th>
    <th>@sortLink("Number", 3)</th>
    <th>@sortLink("Color", 4)</th>
    <th>@sortLink("Standard Cost", 5)</th>
    <th>@sortLink("List Price", 6)</th>
    <th>@sortLink("Size", 7)</th>
    <th>@sortLink("Weight", 8)</th>
 </tr>

Here helpers become really handy. We defined a new helper that will generate the hyper link that will trigger the action with the sorting parameters set. The first parameter name specifies the name of the column header, and id is the number of the column that we used for sorting in the action. The helper sortLink is smart enough to flip the sorting direction when the user clicks twice on the same column.

Let's make it even nicer. We want to add an arrow to indicate the sort direction. Add the following code to the helper we just created.


@helper sortLink(string name, int id)
{
   <a href="@Url.Action("products", "home", new { sortby = id, isasc = (id == ViewBag.sortBy ? (!@ViewBag.isAsc).ToString().ToLower() : true) })">@name</a> 
    if (id == ViewBag.sortBy){
        <span class="arrow @(ViewBag.isAsc ? "up" : "down" )"></span>
    }
}

The previous code simply adds an arrow to the currently sorted column.

There is still one problem. When we click on any page link, the sorting is switched back to the column ProductID. Page links currently do not consider any sorting, so we have to make sure they reflect the current sort column and type.

Modify the buildLinks helper like below:


@helper buildLinks(int start, int end, string innerContent)
{
     for (int i = start; i <= end; i++)
     {
         <a class="@(i == ViewBag.CurrentPage ? "current" : "")" href="@Url.Action("products", "home", new { page = i, sortBy = ViewBag.sortBy, isAsc = ViewBag.isAsc })">@(innerContent ?? i.ToString())</a>      
     }
    
}

Now the page links reflect the sorting direction and column.

Searching

The only thing left to polish our website is searching. We want our users to be able to search the products that we have.

We need to accept a search string from the user. Modify the action like below:


        [HttpGet]
        public ActionResult Products(int page = 1, int sortBy = 1, bool isAsc = true, string search = null)
        {
            IEnumerable<Product> products = db.Products.Where(
                    p => search == null
                    || p.Name.Contains(search)
                    || p.ProductNumber.Contains(search)
                    || p.Color.Contains(search));

            #region Sorting
            switch (sortBy)
            {
                case 1:
                    products = isAsc ? products.OrderBy(p => p.ProductID) : products.OrderByDescending(p => p.ProductID);
                    break;

                case 2:
                    products = isAsc ? products.OrderBy(p => p.Name) : products.OrderByDescending(p => p.Name);
                    break;

                case 3:
                    products = isAsc ? products.OrderBy(p => p.ProductNumber) : products.OrderByDescending(p => p.ProductNumber);
                    break;

                case 4:
                    products = isAsc ? products.OrderBy(p => p.Color) : products.OrderByDescending(p => p.Color);
                    break;

                case 5:
                    products = isAsc ? products.OrderBy(p => p.StandardCost) : products.OrderByDescending(p => p.StandardCost);
                    break;

                case 6:
                    products = isAsc ? products.OrderBy(p => p.ListPrice) : products.OrderByDescending(p => p.ListPrice);
                    break;

                case 7:
                    products = isAsc ? products.OrderBy(p => p.Size) : products.OrderByDescending(p => p.Size);
                    break;

                default:
                    products = isAsc ? products.OrderBy(p => p.Weight) : products.OrderByDescending(p => p.Weight);
                    break;
            }
            #endregion
            ViewBag.TotalPages = (int)Math.Ceiling((double)products.Count() / pageSize);

            products = products     
                .Skip((page - 1) * pageSize)
                .Take(pageSize)                
                .ToList();
            
            ViewBag.CurrentPage = page;
            ViewBag.PageSize = pageSize;            
            ViewBag.Search = search;

            ViewBag.SortBy = sortBy;
            ViewBag.IsAsc = isAsc;

            return View(products);
        }

We made decent changes to our previous code. The Where method is used for filtering, and we used it to retrieve the products that match some pattern. The first condition search == null returns true when the user has not entered any search criteria, and thus returns all products.

Clearly, the number of products will change depending on the search criteria, so we adjusted ViewBag.TotalPages. Now the products number is updated after search.

Every time you add some features to a controller that takes input from the user, you have to tweak your views to reflect the changes.

We'll add the search form first. Add the following DIV panel under the title.


/
<div class="product-search">

<form action="@Url.Action("products", "home")" method="get">
    Search <input id="search" name="search" type="text" value="@ViewBag.Search" />
    <input type="submit" value="Search" />
</form> 

</div>

Now we need to fix the page links again to reflect the search:


@helper buildLinks(int start, int end, string innerContent)
{
     for (int i = start; i <= end; i++)
     {
         <a class="@(i == ViewBag.CurrentPage ? "current" : "")" 
         href="@Url.Action("products", "home", new { page = i, sortBy = ViewBag.sortBy, isAsc = ViewBag.isAsc, search = ViewBag.Search })">@(innerContent ?? i.ToString())</a>      
     }
    
}

Now our gridview looks professional!

We tackled the problems one by one. As soon as we finished implementing a feature in our controller, we moved to the view to reflect our changes.

Another nice feature that you can do is allowing the user to adjust the page size.

I hope this helps !!

Tags: , , , , , , , , , , , ,

ASP.NET | Entity Framework | MVC | SQL Server

ASP.NET Razor Syntax Explained


Introduction

In this blog post, I am going to talk about Razor syntax that you might find useful. If you already know Razor well, this will be a refresher.

Razor syntax is simple and easy to understand. It requires a minimum amount of keystrokes, unlike WebForms. The @ character marks the beginning of code. This code can be an expression or a code block.

An expression does not end with a semicolon, and it returns a value after its evaluation. For example,

Each of the expressions in the previous lines returns some value. We didn’t need to add a semicolon at the end. Razor is smart enough to know where the expressions ended, so we didn’t have to close the @ tag because it ended with an html markup. However, sometimes Razor cannot guess where the expression ends, and in this case we need to use parenthesis.

Code blocks can be defined using @{}. Code blocks do not return any value. They just contain some code that will be executed. It is useful for converting or declaring variables that will be used in the view.

Sometimes we want to set a different color for alternating rows in a table. Below is an example:

If the row is even, we give it a different background color that is defined in the CSS class 'highlighted'.

It is worth saying that you cannot define a function inside a block, but you can define helpers and delegates.

Compare the following code snippets. The last one won’t compile:

Enumerable.Range generates a sequence of integers for you. We are storing the numbers from 1 to 10 in an array. In code blocks, Razor expects to find code unless we tell it this is text. However, sometimes like in the first foreach block, Razor can recognize that <p> is an html markup, so it compiles fine. In the last foreach code block, Razor cannot guess that "current.." is some text, so we need to tell Razor this is a text and not code. We have a few options:

The <text></text> block tells Razor this is markup, not code. Likewise, @: also is followed by markup; however, you cannot use multiple lines in it like in <text>.

By default, Razor html encodes all strings. For example,

Outputs the following:

We can use Html.Raw for this purpose:

Which does what we need:

Comments in Razor can be inserted using @* *@. Compare the following:

The difference between a Razor and HTML comment is that the Razor comments are never sent to the client, just like (server-side code) C# comments. If you view the source of the rendered HTML document, you’ll only see the HTML comment:

As you can see, the C# and Razor comments are never sent to the client. Server side comments are intended for development purpose only.

ViewBag is a new dynamic property that gives us a more convenient syntax than ViewData. In fact, ViewBag is a wrapper for ViewData. To prove this:

This dynamic feature comes from C# 4.0, not from ASP.NET. It's worth mentioning that property names for ViewBag are like C# variables.This means that ViewBag.My Number is not valid, whereas ViewData["My number"] is acceptable.


Razor Declarative Helpers

It is often necessary to generate the same HTML markup to be used in different places in views. Repeating the same HTML code is not efficient. If you need to make a small change, you have to modify all the repeated blocks of HTML which is tedious and prone to error. HTML helpers allows to us to overcome this problem by using the same HTML markup.

There are two ways to reuse HTML markup: Razor declarative helpers and extension methods. I'll cover extension methods in a different post.

If you are going to use a helper in a single view, it makes sense to define it in that view file. Inline helpers are declared using the keyword @helper

Which renders the following

If you plan to reuse this HTML code in a different view, you can move this helper definition to a global file in App_Code folder. Add a new folder and call it "App_Code". In this folder, add a new Razor view and give an appropriate name such as MyHelpers.cshtml

Now you call the helper method with the view file name like below:

Declarative Helpers Limitations

Declarative helpers are sweet. However, I noticed there are some limitations. If you define your helpers in App_Code, you won’t be able to use Html helpers like Html.Encode or Html.Label. Nor can you use Url.Content or Ajax helpers.

I hope this helps !!

Tags: , , , , ,

ASP.NET | MVC | Website Optimization

ASP.NET MVC 3 Internationalization - Part 2 (NerdDinner)


In my previous blog post, ASP.NET MVC 3 Internationalization, I talked in detail about creating a multilingual ASP.NET MVC 3 website and showed the different techniques that can be used to globalize an MVC 3 website. Perhaps the best way to learn about internationalization is by applying it to a real-world application. In this post, I am going to show you how to globalize the popular web application, NerdDinner. We’ll tackle the obstacles one by one and from server-side to client-side. We will add the Spanish language to the website, so we can have Spanish nerds joining us.

Introduction

If a website targets users from different parts of the world, these users might like to see the website content in their own language. Creating a multilingual website is not an easy task, but it will certainly allow a web site to reach more audience. Fortunately, the .NET Framework already has components that support different languages and cultures.

We will revamp the NerdDinner web application, so that:

  • It can display contents in different languages.
  • It autodetects the language from the user's browser.
  • It allows the user to override the language of their browser.

Internationalization, Globalization and Localization. What confusion!

It is actually confusion. The terms defined by Microsoft are different from the rest of the industry.

In Microsoft terms, Internationalization involves Globalization and Localization. Globalization is the process of designing applications that support different cultures. Localization is the process of customizing an application for a given culture. Other industries, however, transpose the meanings of Internationalization and Globalization. You can read more about this here.

Internationalization or Globalization?

In this post, it makes sense to use Microsoft’s terminology since both .NET and ASP.NET MVC 3 are Microsoft’s products.

Anyway, Internationalization is often abbreviated to "I18N". The abbreviation takes the first and last letters and the number of letters between them, so 18 stands for the number of letters between the first "I" and the last "N". The same applies to Globalization (G11N), and Localization (L10N).

A culture is a language and, optionally, a region. The format for the culture name is "<languagecode2>-<country/regioncode2>", where <languagecode2> is the language code and <country/regioncode2> is the subculture code. Examples include "es-CL" for Spanish (Chile) and "en-US" for English (United States). A complete list can be found here. When a culture has a specified language, but not a region, we call it a neutral culture. A neutral culture is not specific to any region. For example, "en" represents English, but not English of the United States.

Now, let’s review the terms used so far:

  • Globalization (G11N): The process of making an application support different languages and regions.
  • Localization (L10N): The process of customizing an application for a given language and region.
  • Internationalization (I18N): Describes both globalization and localization.
  • Culture: It is a language and, optionally, a region.
  • Locale: A locale is the same as a culture.
  • Neutral culture: A culture that has a specified language, but not a region. (e.g. "en", "es")
  • Specific culture: A culture that has a specified language and region. (e.g. "en-US", "en-GB", "es-CL")

Why do we need a region? Isn’t a language alone enough?

You might not need a region at all. It is true that English in the United States is not the same as English in the United Kingdom but if your application just shows English text readable to people from these English-speaking countries, you will not need a region. The problem arises when you need to deal with numbers, dates, and currencies. For example, compare the following output for two different Spanish-speaking regions (Chile, Mexico):

int value = 5600;
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("es-CL");

Console.WriteLine(DateTime.Now.ToShortDateString());
Console.WriteLine(value.ToString("c"));

Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("es-MX");

Console.WriteLine(DateTime.Now.ToShortDateString());
Console.WriteLine(value.ToString("c"));

// Output
26-07-2011 // Date in es-CL, Spanish (Chile)
$ 5.600,00 // Currency in es-CL, Spanish (Chile)

26/07/2011 // Date in es-MX, Spanish (Mexico)
$5,600.00 // Currency in es-MX, Spanish (Mexico)

You can notice the difference in date and currency format. The decimal separator in each region is different and can confuse people in the other region. If a Mexican user types one thousand in their culture "1,000", it will be interpreted as 1 (one) in a Chilean culture website. We mainly need regions for this type of reasons and not much for the language itself.

Culture and UICulture

ASP.NET keeps track of two culture values, the Culture and UICulture. The culture value determines the results of culture-dependent functions, such as the date, number, and currency formatting. The UICulture determines which resources are to be loaded for the page by the ResourceManager. The ResourceManager simply looks up culture-specific resources that is determined by CurrentUICulture. Every thread in .NET has CurrentCulture and CurrentUICulture objects. So ASP.NET inspects these values when rendering culture-dependent functions. For example, if current thread's culture (CurrentCulture) is set to "en-US" (English, United States), DateTime.Now.ToLongDateString() shows "Saturday, January 08, 2011", but if CurrentCulture is set to "es-CL" (Spanish, Chile) the result will be "sábado, 08 de enero de 2011".

How can ASP.NET guess the user’s language?

On each HTTP request, there is a header field called Accept-Language which determines which languages the user’s browser supports:

Accept-Language: en-us,en;q=0.5

This means that my browser prefers English (United States), but it can accept other types of English. The "q" parameter indicates an estimate of the user’s preference for that language. You can control the list of languages using your web browser.


Internet Explorer


Firefox

How to Support Different Cultures in ASP.NET MVC 3

There are two ways to incorporate different cultures in ASP.NET MVC 3:

  • By using resource strings in all our site views.
  • By using different set of views for every culture.
  • By mixing between 1 and 2

Which one is the best?

It is a matter of convenience. Some people prefer to use a single view for all languages because it is more maintainable. While others think replacing views content with code like "@Resources.Something" might clutter the views and will become unreadable. Some project requirements force developers to implement different views per language. But sometimes you have no choice where layout has to be different like right-to-left languages. Even if you set dir="rtl", this won’t be enough in real applications unless the project’s UI layout is really simple. Perhaps, a mix of the two is the best. Anyway, for this example, it makes sense to use resources since we won’t have any issue with the layout for the Spanish and English languages that we will use.

Internationalizing NerdDinner

Download the NerdDinner project from CodePlex site and extract the VS2010-MVC3-Razor folder. We will do the following steps in order to convert NerdDinner into a multilingual website:

  1. Resources: We’ll create resources files for each culture.
  2. Views: All text in the views must be extracted and added in the resource files instead.
  3. Source code: All text in C# code files must be extracted and added in the resource files, too.
  4. Model and Validation Rules: Validation rules messages have to be moved to Resources.
  5. Create a Culture Helper and a Base Controller.
  6. Client-Side: All client side code must be localized including dates, numbers, etc.

Resource files

We will store resource files in a separate assembly, so we can reference them in other project types in the future.

Right click on the Solution and then choose the "Add->New Project" context menu command. Choose "Class Library" project type and name it "Resources".

Now right click on "Resources" project and then choose "Add->New Item" context menu command. Choose "Resource File" and name it "Resources.resx". This will be our default culture (en-US) since it has no special endings. A default culture means that when the user asks for a language that we did not implement, they will see the English version.

We want to enable Spanish, but which Spanish? Or rather, which region?

We certainly want to have nerds from all Spanish-speaking countries including Argentina and Peru, and not only from a specific country. In this case, we care about the language, not the region, so we don’t care about the country in the translation strings. We don’t need to create a resource file for every culture "es-CL", "es-MX", etc. We just need one common file.

Now create a new resource file and name it "Resources.es.resx" for the Spanish version of the site.

Before we move one, make sure to mark the resource's access modifier property to "public" in both files, so it will be accessible from other projects.

Views

We need to extract the English text from all the views and move it to the resource files. This is going to take a while as we have to do this for every single view. You can download the whole NerdDinner project internationalized from the link at the top of this page.

Now we need to reference "Resources" project from our web application, so that we can read the resource strings right from our web site. Right click on "References" under our web project "NerdDinner", and choose the "Resources" project from Projects tab.

Here is a trick, instead of typing the namespace name each time (e.g. "@Resources. Resources.LogOn "), we can add this namespace to the views Web.config and type "@Resources.LogOn" instead. Open the Web.config file under the views folder.

You also get IntelliSense when accessing these resource strings since Visual Studio generates a class behind the scenes that contains a typed property for each key. Now we need to populate the resource files. Visual Studio has a nice resource editor that makes it easy to manage our translated strings.

Let "_LoginStatus.cshtml" be our first view. We need to extract "Log On", "Log Off", and "Welcome". That’s all what we need from this view.


English


Spanish

We have to repeat these steps for all other views in the project. Remember that link names must be translated like in this case. Everything that appears to the end user must be translated, but element names, ids should never be translated. In this project, I made the effort to do the translation myself, but normally you as a developer will receive all the text already translated.

Note: There is no need to show all views in this post since they are done in a very similar way.

Validation Rules Internationalization

It is not surprising that controllers and other source code files contain text that needs to be translated, such as error messages sent to the end user. In order to globalize validation messages, we need to specify a few extra parameters. The "ErrorMessageResourceType" indicates the type of resource to look up the error message. "ErrorMessageResourceName" indicates the resource name to lookup the error message. Resource manager will pick the correct resource file based on the current culture.

namespace NerdDinner.Models
{
    public class LogOnModel
    {
        [Required(ErrorMessageResourceName = "YouMustSpecifyUsername", ErrorMessageResourceType = typeof(Resources.Resources))]
        [Display(Name = "ModelUsername", ResourceType=typeof(Resources.Resources)) ]        
        public string UserName { get; set; }

        [Required(ErrorMessageResourceName = "YouMustSpecifyPassword", ErrorMessageResourceType = typeof(Resources.Resources))]
        [DataType(DataType.Password)]
        [Display(Name = "ModelPassword", ResourceType = typeof(Resources.Resources))]
        public string Password { get; set; }

        [Display(Name = "ModelRememberMe", ResourceType = typeof(Resources.Resources))]
        public bool RememberMe { get; set; }
    }

    public class RegisterModel
    {
        [Required(ErrorMessageResourceName = "YouMustSpecifyUsername", ErrorMessageResourceType = typeof(Resources.Resources))]
        [Display(Name = "ModelUsername", ResourceType = typeof(Resources.Resources))]   
        public string UserName { get; set; }

        [Required(ErrorMessageResourceName = "YouMustSpecifyEmailAddress", ErrorMessageResourceType = typeof(Resources.Resources))]
        [DataType(DataType.EmailAddress)]
        [Display(Name = "ModelEmail", ResourceType=typeof(Resources.Resources)) ]                
        public string Email { get; set; }

        [Required(ErrorMessageResourceName = "YouMustSpecifyPassword", ErrorMessageResourceType = typeof(Resources.Resources))]
        [StringLength(100, ErrorMessageResourceName = "PasswordTooShort", ErrorMessageResourceType = typeof(Resources.Resources), MinimumLength = 6)]
        [DataType(DataType.Password)]
        [Display(Name = "ModelPassword", ResourceType = typeof(Resources.Resources))]
        public string Password { get; set; }

        [DataType(DataType.Password)]
        [Display(Name = "ModelConfirmPassword", ResourceType = typeof(Resources.Resources))]
        [Compare("Password", ErrorMessageResourceName = "NewPasswordAndConfirmationMismatch", ErrorMessageResourceType = typeof(Resources.Resources))]
        public string ConfirmPassword { get; set; }
    }

}

Culture Helper and Base Controller

Before we move to Client-side, we need to figure out how to retrieve the culture from the user’s browser. I already mentioned there is a header field called "Accept-Language" that the browser sends on every request. This field contains a list of culture names (language-country) that the user has configured in their browser. The problem is that this culture may not reflect the real user's preferred language, such as a computer in a cybercafé. We should allow the user to choose a language explicitly and allow them to change it. In order to do this sort of things, we need to store the user's preferred language in a store, which can be perfectly a cookie.

We will create a base controller that inspects the cookie contents first, if there is no cookie, we will use the "Accept-Language" field sent by their browser. Create a controller and name it "BaseController" like below:

namespace NerdDinner.Controllers
{
    public class BaseController : Controller
    {
        protected override void ExecuteCore()
        {
            string cultureName = null;
            // Attempt to read the culture cookie from Request
            HttpCookie cultureCookie = Request.Cookies["_culture"];
            if (cultureCookie != null)
                cultureName = cultureCookie.Value;
            else
                cultureName = Request.UserLanguages[0]; // obtain it from HTTP header AcceptLanguages

            // Validate culture name
            cultureName = CultureHelper.GetImplementedCulture(cultureName); // This is safe


            // Modify current thread's cultures            
            Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(cultureName);
            Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;

            base.ExecuteCore();
        }

    }
}

Make sure all controllers in this project inherit from this BaseController.

The base controller checks if the cookie exists, and sets the current thread cultures to that cookie value. Of course, because cookie content can be manipulated on the client side, we should always validate its value using a helper class called "CultureHelper". If the culture name is not valid, the helper class returns the default culture.

CultureHelper Class

The CultureHelper is basically a utility that allows us to store culture names we are implementing in the web site:

namespace NerdDinner.Helpers
{
    public static class CultureHelper
    {
        // Valid cultures
        private static readonly IList<string> _validCultures = new List<string> {"af", "af-ZA", "sq", "sq-AL", "gsw-FR", "am-ET", "ar", "ar-DZ", "ar-BH", "ar-EG", "ar-IQ", "ar-JO", "ar-KW", "ar-LB", "ar-LY", "ar-MA", "ar-OM", "ar-QA", "ar-SA", "ar-SY", "ar-TN", "ar-AE", "ar-YE", "hy", "hy-AM", "as-IN", "az", "az-Cyrl-AZ", "az-Latn-AZ", "ba-RU", "eu", "eu-ES", "be", "be-BY", "bn-BD", "bn-IN", "bs-Cyrl-BA", "bs-Latn-BA", "br-FR", "bg", "bg-BG", "ca", "ca-ES", "zh-HK", "zh-MO", "zh-CN", "zh-Hans", "zh-SG", "zh-TW", "zh-Hant", "co-FR", "hr", "hr-HR", "hr-BA", "cs", "cs-CZ", "da", "da-DK", "prs-AF", "div", "div-MV", "nl", "nl-BE", "nl-NL", "en", "en-AU", "en-BZ", "en-CA", "en-029", "en-IN", "en-IE", "en-JM", "en-MY", "en-NZ", "en-PH", "en-SG", "en-ZA", "en-TT", "en-GB", "en-US", "en-ZW", "et", "et-EE", "fo", "fo-FO", "fil-PH", "fi", "fi-FI", "fr", "fr-BE", "fr-CA", "fr-FR", "fr-LU", "fr-MC", "fr-CH", "fy-NL", "gl", "gl-ES", "ka", "ka-GE", "de", "de-AT", "de-DE", "de-LI", "de-LU", "de-CH", "el", "el-GR", "kl-GL", "gu", "gu-IN", "ha-Latn-NG", "he", "he-IL", "hi", "hi-IN", "hu", "hu-HU", "is", "is-IS", "ig-NG", "id", "id-ID", "iu-Latn-CA", "iu-Cans-CA", "ga-IE", "xh-ZA", "zu-ZA", "it", "it-IT", "it-CH", "ja", "ja-JP", "kn", "kn-IN", "kk", "kk-KZ", "km-KH", "qut-GT", "rw-RW", "sw", "sw-KE", "kok", "kok-IN", "ko", "ko-KR", "ky", "ky-KG", "lo-LA", "lv", "lv-LV", "lt", "lt-LT", "wee-DE", "lb-LU", "mk", "mk-MK", "ms", "ms-BN", "ms-MY", "ml-IN", "mt-MT", "mi-NZ", "arn-CL", "mr", "mr-IN", "moh-CA", "mn", "mn-MN", "mn-Mong-CN", "ne-NP", "no", "nb-NO", "nn-NO", "oc-FR", "or-IN", "ps-AF", "fa", "fa-IR", "pl", "pl-PL", "pt", "pt-BR", "pt-PT", "pa", "pa-IN", "quz-BO", "quz-EC", "quz-PE", "ro", "ro-RO", "rm-CH", "ru", "ru-RU", "smn-FI", "smj-NO", "smj-SE", "se-FI", "se-NO", "se-SE", "sms-FI", "sma-NO", "sma-SE", "sa", "sa-IN", "sr", "sr-Cyrl-BA", "sr-Cyrl-SP", "sr-Latn-BA", "sr-Latn-SP", "nso-ZA", "tn-ZA", "si-LK", "sk", "sk-SK", "sl", "sl-SI", "es", "es-AR", "es-BO", "es-CL", "es-CO", "es-CR", "es-DO", "es-EC", "es-SV", "es-GT", "es-HN", "es-MX", "es-NI", "es-PA", "es-PY", "es-PE", "es-PR", "es-ES", "es-US", "es-UY", "es-VE", "sv", "sv-FI", "sv-SE", "syr", "syr-SY", "tg-Cyrl-TJ", "tzm-Latn-DZ", "ta", "ta-IN", "tt", "tt-RU", "te", "te-IN", "th", "th-TH", "bo-CN", "tr", "tr-TR", "tk-TM", "ug-CN", "uk", "uk-UA", "wen-DE", "ur", "ur-PK", "uz", "uz-Cyrl-UZ", "uz-Latn-UZ", "vi", "vi-VN", "cy-GB", "wo-SN", "sah-RU", "ii-CN", "yo-NG" };

        // Include ONLY cultures you are implementing
        private static readonly IList<string> _cultures = new List<string> {
            "en-US",  // first culture is the DEFAULT
            "es", // Spanish NEUTRAL culture
            "es-AR", "es-BO", "es-CL", "es-CO", "es-CR", "es-DO", "es-EC", "es-SV", "es-GT", "es-HN", "es-MX", "es-NI", "es-PA", "es-PY", "es-PE", "es-PR", "es-ES", "es-US", "es-UY", "es-VE" // Specific cultures
           
        };



        /// <summary>
        /// Returns a valid culture name based on "name" parameter. If "name" is not valid, it returns the default culture "en-US"
        /// </summary>
        /// <param name="name">Culture's name (e.g. en-US)</param>
        public static string GetImplementedCulture(string name)
        {
            // make sure it's not null
            if (string.IsNullOrEmpty(name))
                return GetDefaultCulture(); // return Default culture

            // make sure it is a valid culture first
            if(_validCultures.Where( c => c.Equals(name, StringComparison.InvariantCultureIgnoreCase)).Count() == 0)
                return GetDefaultCulture(); // return Default culture if it is invalid

               
            // if it is implemented, accept it
            if (_cultures.Where( c => c.Equals(name, StringComparison.InvariantCultureIgnoreCase)).Count() > 0)
                return name; // accept it

           

            // Find a close match. For example, if you have "en-US" defined and the user requests "en-GB", 
            // the function will return closes match that is "en-US" because at least the language is the same (ie English)  
            var n = GetNeutralCulture(name);
            foreach (var c in _cultures)
                if (c.StartsWith(n))
                        return c; 
                


            // else 
            // It is not implemented
            return GetDefaultCulture(); // return Default culture as no match found
        }


        /// <summary>
        /// Returns default culture name which is the first name decalared (e.g. en-US)
        /// </summary>
        /// <returns></returns>
        public static string GetDefaultCulture()
        {
            return _cultures[0]; // return Default culture

        }

        public static string GetCurrentCulture()
        {
            return Thread.CurrentThread.CurrentCulture.Name;
        }

        public static string GetCurrentNeutralCulture()
        {
            return GetNeutralCulture(Thread.CurrentThread.CurrentCulture.Name);
        }
          

        public static string GetNeutralCulture(string name)
        {
            if (name.Length < 2)
                return name;

            return name.Substring(0, 2); // Read first two chars only. E.g. "en", "es"
        }

 
    


    }
}

We should populate "_cultures" manually. The "_cultures" dictionary stores the list of culture names our site supports. The nice part of this utility class is that it serves similar cultures. For example, if a user is visiting our site from the United Kingdom (en-GB), a culture which is not implemented in our site, he or she will see our site in English using "en-US" (English, United States). This way, you don't have to implement all cultures unless you really care about currency, date format, etc. One important thing to mention is that we added all specific cultures for the Spanish language. For example, we are implementing "es" Spanish in general without any region where a date looks like this "30/07/2011". Now suppose a nerd is coming from Chile (es-CL), it would be very nice display dates in their culture format (30-07-2011) instead of the neutral one. It does not matter if we don’t have a resource file for any of these cultures. ResourceManager can pick a neutral culture when it cannot find a specific culture file, this automatic mechanism is called fallback.

Output Caching

You might run into a problem with Output Caching as content varies by culture. If you think that VaryByHeader="Accept-Language" could do it, then this is not always the case because our culture is stored in a cookie, not in the header. If the cookie is absent, this approach will be OK. We overcome this by applying custom caching.

   /* NerdDinner i18n Custom caching */
   public override string GetVaryByCustomString(HttpContext context, string arg)
        {
            // It seems this executes multiple times and early, so we need to extract language again from cookie.
            if (arg == "culture") // culture name (e.g. "en-US") is what should vary caching
            {
                string cultureName = null;
                // Attempt to read the culture cookie from Request
                HttpCookie cultureCookie = Request.Cookies["_culture"];
                if (cultureCookie != null)
                    cultureName = cultureCookie.Value;
                else
                    cultureName = Request.UserLanguages[0]; // obtain it from HTTP header AcceptLanguages

                // Validate culture name
                cultureName = CultureHelper.GetImplementedCulture(cultureName); // This is safe

                return cultureName.ToLower();// use culture name as cache key, "es", "en-us", "es-cl", etc.
            }
            
            return base.GetVaryByCustomString(context, arg);
        }

And it can be used this way:

[OutputCache(Duration=60, VaryByCustom="culture")]

Client Side

This is the last step in the process of i18n. Client side internationalization can be easy or challenging depending on the way you created your code and the tools you are using. The first thing to do is to replace all the strings inside javascript files with the translated versions. Then we need to figure out how to parse and format numbers, dates, and currency if any. We also need to localize the calendar used to set event dates.

Before we begin with the strings replacement, it is worth it to mention JQuery Global plugin. This jQuery plugin supports i18n for hundreds of cultures. It makes it easy to parse and format numbers, dates, and currencies. We will use this plugin in the NerdDinner project. Download the plugin and extract the folder "lib" contents in the "Scripts" folder of our project.

Add a reference to the "globalize.js" file in the "_Layout.cshtml". The jQuery Global plugin allows us to store culture specific values, so this can be the perfect place to store the translated strings. Of course, there are other ways to store localized strings inside javascript files. For example, we can create a separate javascript file for each culture, so that each file contains localized strings for that culture. However, it is simpler to store strings using this plugin.

Let's begin with NerdDinner.js file. Look at the following code snippet:

This method is fragile. It is appending an "s" to the end of the string. For English, this is fine. However, this won’t work in many other cultures, so we need two separate words "RSVP" and "RSVPs" stored separately. We can store these words in resource files and fetch them from there with this trick. There is also a confirmation message we need to replace. Here is how the code will look like:

                Globalize.addCultureInfo("@NerdDinner.Helpers.CultureHelper.GetCurrentCulture()" /*es-CL*/, {
                    messages: {
                        "RSVP": "@Resources.RSVP",
                        "RSVPs": "@Resources.RSVPs",
                        "BingMapsReturnedAddress": "@Html.Raw(Resources.BingMapsReturnedAddress)",
                        "MonthDayYear": "@Html.Raw(Resources.MonthDayYear)",
                        "MonthDay": "@Html.Raw(Resources.MMMdd)",
                        "with":"@Resources.with"
                    }
                });

And now NerdDinner.js should look like below:

function _getRSVPMessage(RSVPCount)  {
   var rsvpMessage = "" + RSVPCount + " " + (RSVPCount > 1 ? Globalize.localize("RSVPs") : Globalize.localize("RSVP"));
   return rsvpMessage;
   }

And here is how you would replace the confirmation:

var answer  =  confirm(htmlDecode(Globalize.localize("BingMapsReturnedAddress").replace("{0}",  locations[0].Name).replace("{1}",  currentAddress.val())));

Now let’s move on to dates. Currently, the NerdDinner project is using a different jQuery plugin for date formatting. We need to replace it with the Global plugin.

Look at the following function:

        function _getDinnerDate(dinner,  formatStr)  {
   return '<strong>' + _dateDeserialize(dinner.EventDate).format(formatStr) + '</strong>';
   }

Here is how it should be after using Global plugin:

        function _getDinnerDate(dinner,  formatStr)  {
   return '<strong>' + Globalize.format(_dateDeserialize(dinner.EventDate), formatStr);
   + '</strong>';
   }

The Calendar

There are two components that need to be localized: DatePicker and TimePicker. Luckily, both support i18n. DatePicker ships with javascript localization files, so we just need to reference the file of the implemented culture. The following references the right file from Microsoft CDN:

<script src="http://ajax.aspnetcdn.com/ajax/jquery.ui/1.8.11/i18n/jquery.ui.datepicker-@(NerdDinner.Helpers.CultureHelper.GetCurrentNeutralCulture()).js" type="text/javascript"></script>

It is important to mention that the date picker plugin only supports neutral cultures (e.g. "en", "es"). This means we need to tweak its formatting.

The time picker plugin localization is done in the same way, but it doesn’t ship with its own localization file, so we need to do the process manually. Here is the complete js code for both components:

    @if (NerdDinner.Helpers.CultureHelper.GetCurrentCulture() != "en" && NerdDinner.Helpers.CultureHelper.GetCurrentCulture() != "en-US")
    {
        // load necessary localization files for Global and DatePicker
         <script src="http://ajax.aspnetcdn.com/ajax/jquery.ui/1.8.11/i18n/jquery.ui.datepicker-@(NerdDinner.Helpers.CultureHelper.GetCurrentNeutralCulture()).js" type="text/javascript"></script>
        
         <script type="text/javascript">
             $(function () {
                 
                 @* Unfortunately, the datepicker only supports Neutral cultures, so we need to adjust date and time format to the specific culture *@
                 $("#EventDate").change(function(){
                     $(this).val(Globalize.format($(this).datetimepicker('getDate'), Globalize.culture().calendar.patterns.d + " " + Globalize.culture().calendar.patterns.t)); /*d t*/
                });
                
                @* Timepicker i18n *@
                $.timepicker.regional['@NerdDinner.Helpers.CultureHelper.GetCurrentCulture()'] = {
                timeOnlyTitle: '@Resources.timeOnlyTitle',
                timeText: '@Resources.timeText',
                hourText: '@Resources.hourText',
                minuteText: '@Resources.minuteText',
                secondText: '@Resources.secondText',
                currentText: '@Resources.currentText',
                closeText: '@Resources.closeText',
                ampm: '@Resources.ampm'
            };

            $.timepicker.setDefaults($.timepicker.regional['@NerdDinner.Helpers.CultureHelper.GetCurrentCulture()']);

             });
         </script>
    }

DatePicker doesn’t support specific cultures like "es-CL". This will be an issue for ASP.NET. For example, the date value "30/07/2011" is not a valid date in the Chilean culture, which uses "-" (i.e. "30-07-2011"). ASP.NET will throw an exception when parsing a date in an incorrect format. To solve this issue, each time the user modifies the event date, we need to re-format it using Globalize.format method.

For TimePicker, we are providing the plugin the translated strings from the resource files. We are telling the plugin to use the Spanish culture like this:

$.timepicker.setDefaults($.timepicker.regional['@NerdDinner.Helpers.CultureHelper.GetCurrentCulture()']);


Now you can see the calendar in Spanish

Now that dates and time appear in correct format to the end user, let’s move to numbers parsing and formatting.

NOTE It seems that DefaultModelBinder is not culture-ware. It does not always use the current culture for type conversion. If the data value is coming as part of the URL, the InvariantCulture takes place. Otherwise, the current culture is used. This might be a big issue. Luckily, it’s not a problem in this case since we are not using any values as part of a URL. Anyway, the invariant culture is culture-insensitive, and it is similar to English but is not related to any region. Suppose a culture on the server is set to "es-CL" which expects dates in this format "30-07-2011" and an incoming querystring has the value "01-08-2011". An exception is thrown since DefaultModelBinder used the InvariantCulture which can parse dates in this format "mm/dd/yyyy" or "yyyy-mm-dd".

Numbers

Although the end user is not dealing with any numbers directly, there are two hidden input fields that actually store the Bing Maps pushpin coordinates. Since these values are of type float, they contain a decimal point which poses a problem across different cultures as explained earlier. To overcome this issue, each time these values are read or saved, we need to parse or format them. Otherwise, we’ll get a server-side error from ASP.NET. Anyway, we can use Globalize.parseFloat and Globalize.format for this purpose. Below is some code snippet from NerdDinner.js

 if (NerdDinner._points.length  ===  1)  {
   $("#Latitude").val(Globalize.format(NerdDinner._points[0].Latitude, "n14"));
   $("#Longitude").val(Globalize.format(NerdDinner._points[0].Longitude, "n14"));
   }

Unobtrusive Client-Side Validation

Client side validation is important in nowadays web applications because users expect immediate response. It’s not a good thing to make the a user wait for the server load response just to tell them if "10" is a valid number. A lot of things can be done on the client side which can also boost performance by reducing the number of sever requests. Unobtrusive validation does not add any javascript in the views. Instead, the validation process is done separately. However, validation rules are described as attributes that are added to the HTML elements to be validated. These attributes are processed by some MVC client side library which configures jQuery Validation tool that does all the job behind the scenes.

Anyway, NerdDinner is using unobtrusive client-side validation, and jQuery validation by default does not consider i18n. We need to override some of the validation methods in order to have i18n. In our NerdDinner case, the only problem occurs when we try to place an event since the Latitude and Longitude input values can be misinterpreted in different cultures.

We can either turn off client-side validation for these inputs, or we can tell jQuery validation library to parse them correctly. We’ll choose the latter since in other applications, it may not be pleasant to turn off validation.

// Override default jQuery Validation number method
$.validator.methods.number = function(value, element) {
   return this.optional(element) || !isNaN(Globalize.parseFloat(value));
   }

In this method, the return value is either true or false. We are checking if the result of Globalize.parseFloat is a valid number.

Language picker

Finally, we need to allow the user to choose their preferred language. We need a language selector. I already created a one for this purpose. Below is the code needed to add the language picker to the _Layout.cshtml view:

            <div class="lang-picker-wrapper">
            <span class="lang-picker">
                 <a class="en-us" href="javascript:void(0);">English</a>
                 <a class="@(NerdDinner.Helpers.CultureHelper.GetNeutralCulture(Request.UserLanguages[0].ToLower()) == "es" ? Request.UserLanguages[0].ToLower() : "es")" href="javascript:void(0);">Español</a>
            </span>
            </div>

We used the AcceptLanguage header field here on purpose. Suppose a nerd is visiting our site from Spain, instead of using the neutral culture "es", we want to use the user’s specific culture "es-ES" instead. However, if the user’s browser does not prefer Spanish and they prefer to see our site in Spanish, the neutral culture is applied.

                $(".lang-picker a." + Globalize.culture().name.toLowerCase()).prependTo($(".lang-picker")); @* Select current culture*@

                @* culture change *@
                $(".lang-picker a").click(function(){

                if ($(this).hasClass(Globalize.culture().name.toLowerCase()))
                    return false; // do nothing

                $.cookie("_culture", $(this).attr("class") , {expires : 365, path: '/'});
                window.location.reload(); // reload 

                });

And the javascript needed for this to work:

In the code above, we attach a click handler to each link, so the user can click on them to change their language. The culture name is stored in the "class" attribute of the link. When the language link is clicked, the cookie’s value is updated, and the page is reloaded to reflect the new culture.

Try It Out

Let’s see how NerdDinner looks like now:

Everything looks fine except for one thing, the image next to the search text box is still in English because it is not text. We have two options: Either we convert this image into text, or we create a different image file per language. I have already prepared a Spanish version of this image. It doesn’t look exactly the same, but it’s still good. We have to reference the right image file in CSS. However, we can’t use C# directly in CSS files, so we need to move the style into our view.

    <style type="text/css">
        #hm-masthead
        {
            background: transparent url('Content/img/hm-masthead-@(NerdDinner.Helpers.CultureHelper.GetCurrentNeutralCulture()).png') no-repeat 0px 45px;
        }
    
    </style>

We used the neutral version of the culture because we only need one image per language, not per region.

Enjoy!!

Now we can have Spanish nerds joining us!!

Summary

Building a multilingual web application is not an easy task, but it's worth it especially for web applications targeting users from all over the world, something which many sites do. It is true that i18n is not the first priority in site development process; however, it should be well planned early in the stage of development, so it can be easily implemented in the future. Luckily, ASP.NET supports i18n and there are plenty of .NET classes that are handy.

Client-side localization was more painful than server-side since there were many components involved, and we had to localize them one by one. Anyway, if i18n is planned from the beginning, it can mitigate the pain to convert the web project into a multilingual website.

I hope his helps!

Any questions or comments are welcome!

Tags:

ASP.NET | MVC

ASP.NET MVC 3 Internationalization


ASP.NET MVC 3 Internationalization

Introduction

If your website targets users from different parts of the world, these users might like to see your website content in their own language. Creating a multilingual website is not an easy task, but it will certainly allow your site to reach more audience. Fortunately, the .NET Framework already has components that support different languages and cultures.

We will build an ASP.NET MVC 3 web application that contains the following features:

  • It can display contents in different languages.
  • It autodetects the language from the user's browser.
  • It allows the user to override the language of their browser.

Globalization and Localization in ASP.NET

Internationalization involves Globalization and Localization. Globalization is the process of designing applications that support different cultures. Localization is the process of customizing an application for a given culture.

The format for the culture name is "<languagecode2>-<country/regioncode2>", where <languagecode2> is the language code and <country/regioncode2> is the subculture code. Examples include es-CL for Spanish (Chile) and en-US for English (United States).

ASP.NET keeps track of two culture values, the Culture and UICulture. The culture value determines the results of culture-dependent functions, such as the date, number, and currency formatting. The UICulture determines which resources are to be loaded for the page by the ResourceManager. The ResourceManager simply looks up culture-specific resources that is determined by CurrentUICulture. Every thread in .NET has CurrentCulture and CurrentUICulture objects. So ASP.NET inspects these values when rendering culture-dependent functions. For example, if current thread's culture (CurrentCulture) is set to "en-US" (English, United States), DateTime.Now.ToLongDateString() shows "Saturday, January 08, 2011", but if CurrentCulture is set to "es-CL" (Spanish, Chile) the result will be "sábado, 08 de enero de 2011".

How to Support Different Languages in ASP.NET MVC 3

There are two ways to incorporate different languages and cultures in ASP.NET MVC 3:

  1. By using resource strings in all our site views. (See part 2)
  2. By using different set of views for every language and locale.
  3. By mixing between 1 and 2

Which one is the best?
It depends on you. It is a matter of convenience. Some people prefer to use a single view for all languages because it is more maintainable. While others think replacing views content with code like "@Resources.Something" might clutter the views and will become unreadable. Recall that views have to be as simple as possible. If your views look fine with a lot of inline code, it's fine. But sometimes you have no choice in languages where layout has to be different like right-to-left languages. Perhaps, a mix of the two is the best. Anyway, in this example we will use the 2nd approach just to show a different way than the usual resource strings.

Views Naming Conventions

In order to create different views for every culture, we will append the culture name to the name of the view. For example, Index.cshtml (the default view), Index.es-CL.cshtml (Spanish, Chile), Index.ar-JO.cshtml (Arabic, Jordan). The view name that has no ending is considered the default culture. A default culture view will be rendered if the requested culture name is not implemented explicitly.

Globalizing our Web Site

We will create a new ASP.NET MVC 3 web application and globalize it step by step.

Click "File->New Project" menu command within Visual Studio to create a new ASP.NET MVC 3 Project. We'll create a new project using the "Internet Application" template.

Creating the Model

We'll need a model to create our web application. Add a class named "User" to the "Models" folder:

Internationalizing Validation Messages

Our model presented above contains no validation logic, and this is not the case in normal applications nowadays. We can use data annotation attributes to add some validation logic to our model. However, in order to globalize validation messages, we need to specify a few extra parameters. The "ErrorMessageResourceType" indicates the type of resource to look up the error message. "ErrorMessageResourceName" indicates the resource name to lookup the error message. Resource manager will pick the correct resource file based on the current culture.

Now modify the "Person" class and add the following attributes:

namespace MvcInternationalization.Models
{
    public class Person
    {
        [Required(ErrorMessageResourceType=typeof(MyResources.Resources), 
                  ErrorMessageResourceName="FirstNameRequired")]
        [StringLength(50, ErrorMessageResourceType = typeof(MyResources.Resources), 
                          ErrorMessageResourceName = "FirstNameLong")]       
        public string FirstName { get; set; }

        [Required(ErrorMessageResourceType = typeof(MyResources.Resources), 
            ErrorMessageResourceName = "LastNameRequired")]
        [StringLength(50, ErrorMessageResourceType = typeof(MyResources.Resources), 
            ErrorMessageResourceName = "LastNameLong")]
        public string LastName { get; set; }

        [Required(ErrorMessageResourceType = typeof(MyResources.Resources), 
            ErrorMessageResourceName = "AgeRequired")]        
        [Range(0, 130, ErrorMessageResourceType = typeof(MyResources.Resources), 
            ErrorMessageResourceName = "AgeRange")]                
        public int Age { get; set; }

        [Required(ErrorMessageResourceType = typeof(MyResources.Resources), 
            ErrorMessageResourceName = "EmailRequired")]
        [RegularExpression(".+@.+\\..+", ErrorMessageResourceType = typeof(MyResources.Resources), 
            ErrorMessageResourceName = "EmailInvalid")]        
        public string Email { get; set; }

        public string Biography { get; set; }
    }
}

Localizing Data Annotations Validation Messages

Because we need to perform data validation on our model using Data Annotations, we will have to add translated resource strings for every culture our site will support. In this case, English, Spanish, and Arabic.

We will store resource files in a separate assembly, so we can reference them in other project types in the future.

Right click on the Solution and then choose the "Add->New Project" context menu command. Choose "Class Library" project type and name it "MyResources".

Now right click on "MyResources" project and then choose "Add->New Item" context menu command. Choose "Resource File" and name it "Resources.resx". This will be our default culture (en-US) since it has no special endings. Add the following names and values to the file like below:

Remember to mark the resource's access modifier property to "public", so it will be accessible from other projects.

Now create a new resource file and name it "Resources.es-CL.resx" and add the following names and values like below:

Now, do the same for the Arabic version. You may not be able to enter the correct strings by keyboard because your OS may not be configured to accept Arabic. However, you can download the files from the link at the top. Anyway, the resource file is included for reference:

We need to reference "MyResources" project from our web application, so that we can read the resource strings right from our web site. Right click on "References" under our web project "MvcInternationalization", and choose the "MyResources" project from Projects tab.

Determining Culture

How do we determine which version of a view to return to the end user?
How do we know which culture does the user want?

There is a header field called "Accept-Language" that the browser sends on every request. This field contains a list of culture names (language-country) that the user has configured in their browser. The problem is that this culture may not reflect the real user's preferred language, such as a computer in a cybercafé. We should allow the user to choose a language explicitly and allow them even to change it. In order to do this sort of things, we need to store the user's preferred language in a store, which can be perfectly a cookie.

We will create a base controller that inspects the cookie contents first, if there is no cookie, we will use the "Accept-Language" field sent by their browser. Create a controller and name it "BaseController" like below:

namespace MvcInternationalization.Controllers
{
    public class BaseController : Controller
    {
        
        protected override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            // Is it View ?
            ViewResultBase view = filterContext.Result as ViewResultBase;
            if (view == null) // if not exit
                return;

            string cultureName = Thread.CurrentThread.CurrentCulture.Name; // e.g. "en-US" // filterContext.HttpContext.Request.UserLanguages[0]; // needs validation return "en-us" as default            

            // Is it default culture? exit
            if (cultureName == CultureHelper.GetDefaultCulture())
                return;

            
            // Are views implemented separately for this culture?  if not exit
            bool viewImplemented = CultureHelper.IsViewSeparate(cultureName);
            if (viewImplemented == false)
                return;
            
            string viewName = view.ViewName;

            int i = 0;

            if (string.IsNullOrEmpty(viewName))
                viewName = filterContext.RouteData.Values["action"] + "." + cultureName; // Index.en-US
            else if ((i = viewName.IndexOf('.')) > 0)
            {
                // contains . like "Index.cshtml"                
                viewName = viewName.Substring(0, i + 1) + cultureName + viewName.Substring(i);
            }
            else
                viewName += "." + cultureName; // e.g. "Index" ==> "Index.en-Us"

            view.ViewName = viewName;

            filterContext.Controller.ViewBag._culture = "." + cultureName;

            base.OnActionExecuted(filterContext);
        }


        protected override void ExecuteCore()
        {
            string cultureName = null;
            // Attempt to read the culture cookie from Request
            HttpCookie cultureCookie = Request.Cookies["_culture"];
            if (cultureCookie != null)
                cultureName = cultureCookie.Value;
            else
                cultureName = Request.UserLanguages[0]; // obtain it from HTTP header AcceptLanguages

            // Validate culture name
            cultureName = CultureHelper.GetValidCulture(cultureName); // This is safe



            // Modify current thread's culture            
            Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureName);
            Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(cultureName);
            
            
            base.ExecuteCore();
        }



    }
}

The base controller checks if the cookie exists, and sets the current thread culture to that cookie value. Of course, because cookie content can be manipulated on the client side, we should always validate its value using a helper class called "CultureHelper". If the culture name is not valid, the helper class returns the default culture. After that, when we call "Return" in controller action methods, the view name is modified by the base controller behind the scenes to reflect the correct culture in cookie or header field. This way we make sure that the user gets the right content based on their culture.

CultureHelper Class

The CultureHelper is basically a utility that allows us to store culture names we are implementing in our site:

namespace MvcInternationalization.Utility
{
    public static class CultureHelper
    {
        // Include ONLY cultures you are implementing as views
        private static readonly  Dictionary<String, bool> _cultures  = new Dictionary<string,bool> {
            {"en-US", true},  // first culture is the DEFAULT
            {"es-CL", true},
            {"ar-JO", true}
        };


        /// <summary>
        /// Returns a valid culture name based on "name" parameter. If "name" is not valid, it returns the default culture "en-US"
        /// </summary>
        /// <param name="name">Culture's name (e.g. en-US)</param>
        public static string GetValidCulture(string name)
        {
            if (string.IsNullOrEmpty(name))
                return GetDefaultCulture(); // return Default culture

            if (_cultures.ContainsKey(name))
                return name;

            // Find a close match. For example, if you have "en-US" defined and the user requests "en-GB", 
            // the function will return closes match that is "en-US" because at least the language is the same (ie English)            
            foreach(var c in _cultures.Keys)
                if (c.StartsWith(name.Substring(0, 2)))
                    return c;

            
            // else             
            return GetDefaultCulture(); // return Default culture as no match found
        }


        /// <summary>
        /// Returns default culture name which is the first name decalared (e.g. en-US)
        /// </summary>
        /// <returns></returns>
        public static string GetDefaultCulture()
        {
            return _cultures.Keys.ElementAt(0); // return Default culture

        }


        /// <summary>
        ///  Returns "true" if view is implemented separatley, and "false" if not.
        ///  For example, if "es-CL" is true, then separate views must exist e.g. Index.es-cl.cshtml, About.es-cl.cshtml
        /// </summary>
        /// <param name="name">Culture's name</param>
        /// <returns></returns>
        public static bool IsViewSeparate(string name)
        {
            return _cultures[name];
        }

    }
}

We should populate "_cultures" manually. The "_cultures" dictionary stores the list of culture names our site supports. The first parameter indicates culture name (e.g. en-US), the second parameter indicates whether we are implementing separate views for that culture. If the second parameter is false, the default view is used (ie the one that has no special ending).

The nice part of this utility class is that it serves similar languages. For example, if a user is visiting our site from Argentina (es-ar), a culture which is not implemented in our site, he or she will see our site in Spanish using "es-cl" (Spanish, Chile) instead of English language. This way, you don't have to implement all cultures unless you really care about currency, date format, etc.

Controllers

Visual Studio has created a controller named "HomeCotnroller" for us, so we'll use it for simplicity. Modify the "HomeController.cs" so that it looks like below:

namespace MvcInternationalization.Controllers
{
    public class HomeController : BaseController
    {
        [HttpGet]
        public ActionResult Index()
        {
          
            return View();
        }

        [HttpPost]
        public ActionResult Index(Person per)
        {
                 return View();
        }

        public ActionResult SetCulture(string culture)
        {
            // Validate input
            culture = CultureHelper.GetValidCulture(culture);

            // Save culture in a cookie
            HttpCookie cookie = Request.Cookies["_culture"];
            if (cookie != null)
                cookie.Value = culture;   // update cookie value
            else
            {

                cookie = new HttpCookie("_culture");
                cookie.HttpOnly = false; // Not accessible by JS.
                cookie.Value = culture;
                cookie.Expires = DateTime.Now.AddYears(1);
            }
            Response.Cookies.Add(cookie);

            return RedirectToAction("Index");
        }

        public ActionResult About()
        {
            
           return View();
        }

        



    }
}

The "SetCulture" action allows the user to change their current culture and stores it in a cookie called "_culture". We are not restricted to cookies, we could instead save the culture name in Session or elsewhere, but cookies are really lightweight since they do not take any type of space on server side.

Creating a View Template

Now we will implement the View associated with the HomeController's Index action. First delete the existing Index.cshtml file under "Views/Home" folder. Now to implement the view right-click within the "HomeController.Index()" method and select the "Add View" command to create a view template for our home page:

We will need to modify some of the settings above. Choose the option "Create a strongly-typed view" and choose the "Person" class we created before. Also, choose the "Create" menu item from the "Scaffold template" drop-down box.

When we click the "Add" button, a view template of our "Create" view (which renders the form) is created. Modify it so it looks like below:

The javascript code simply post back the form to set the culture. The "selected" helper is used to mark the appropriate culture radio button as checked.

Now make two copies of "Index.cshtml" and rename them to "Index.es-CL.cshtml" and "Index.ar-JO.cshtml". These latter views represent the localized versions of Index.cshtml for two different cultures, so we can add whatever is necessary inside them. Make them look like below:


Spanish view


Arabic view

Of course, for simple partial views like "_LogOnPartial.cshtml" and which are not referenced by controllers, we can use resource strings perfectly.


Arabic view

Try It Out

Run the website now. Notice that client side validation is working nicely. Click on radio buttons to switch between cultures, and notice how right-to-left language is showing correctly. Using separate views allowed us to control how to position elements, and have made our views clean and readable.


English


Spanish


Arabic

Client-Side localization

What about client-side scripts?

For client-side, we should worry mainly about numbers, date and time, and messages since these change from culture to culture. There are many ways to implement client-side localization. But here are two common options:

  1. Creating standalone localized javascript files for every culture and language.
  2. Creating a standard common javascript file for all cultures by sticking to Microsoft Ajax Library.

For (1), we follow the same convention for views and resource files. For example, for the file "myscript.js", you need to create "myscript.es-CL.js", "myscript.ar-JO.js", etc. We can reference the javascript files easily from our views by appending culture name to the javascript file :

<script src="@Url.Content("~/Scripts/myscript" + ViewBag._culture + ".js")" type="text/javascript"></script>

The variable "_culture" is already defined in the base controller and works nicely by ignoring default culture (returns null in this case).

Even if you want to use Microsoft Ajax Library, you may still need separate javascript files that define text messages to the end user. You can define a literal object that contains the list of messages, or if you are using separate views for every culture, you can use inline javascript instead of separate javascript files.

Summary

Building a multilingual web application is not an easy task. but it's worth it especially for web applications targeting users from all over the world, something which many sites do. It is true that globalization is not the first priority in site development process, however, it should be well planned early in the stage of development so it can be easily implemented in the future. Luckily, ASP.NET supports globalization and there are plenty of .NET classes that are handy. We have seen how to create an ASP.NET MVC 3 application that supports 3 different languages, including a right-to-left one, which requires a different UI layout. Anyway, here is a summary of how to globalize a site in ASP.NET MVC 3:

  1. Add a base controller from which all controllers inherit. This controller will intercept the view names returned and will adjust them depending on the current culture set.
  2. Add a helper class that stores the list of culture names that the site will support.
  3. Create a single view or set of views for every culture and language.
  4. Create resource files that contain translation of all string messages. (e.g. Resources.resx, Resources.es-CL.resx, Resources.ar-JO.resx, etc )
  5. Localize javascript files.

I hope this help!
Any questions or comments are welcome!

Tags: ,

ASP.NET | MVC | Website Optimization

Create a Wizard in ASP.NET MVC 3


Introduction

Wizards can improve web sites usability by breaking an overwhelming list of questions into multistep simpler forms. A wizard collects information and submits all of it at the end, the user can go back and modify their answers when they want. Because ASP.NET MVC is so flexible, there are many ways to create wizards in MVC. In this post, I will show you a nice technique to create a wizard with the aid of jQuery.

The intrinsic idea is to divide a single form by wrapping each set of questions in one container (e.g. <DIV>) , and all the rest of the work including navigation is done in javascript.

Creating the Wizard

Let's see how we can create this wizard using ASP.NET MVC 3 and Razor. Although you are not restricted to Razor, it's the recommended view engine by many professional web developers because of its fluid, concise, and robust nature.

Click "File->New Project" menu command within Visual Studio to create a new ASP.NET MVC 3 Project. We'll create a new project using the "Internet Application" template.

Creating the Model

We'll need a model to create our Wizard. Add a class named "User" to the "Models" folder

Note that we added some validation using DataAnnotations.

Creating a WizardController

Now right click on the controllers folder and then choose the "Add->Controller" context menu command. This will bring the "Add Controller" dialog:

We'll name the new controller "WizardController". When we click the "Add" button, Visual Studio will add a default "Index" action:

Creating a View Template

Now we will implement the View associated with the WizardController's Index action. Now to implement the view right-click within the "WizardController.Index()" method and select the "Add View" command to create a view template for our home page:

We will need to modify some of the settings above. Choose the option "Create a strongly-typed view" and choose the User class we created before. Also, choose the "Create" menu item from the "Scaffold template" drop-down box.

When we click the "Add" button, a view template of our "Create" view (which renders the form) looks like the one below:

Our Wizard will have 3 steps. The first ask for Username and email address, the second step asks for First and Last name, and the third asks for Age and Biography. Now we need to wrap every step into a <div> container like the one below:

We'll need to make these containers use class "wizard-step", this will mark these containers so we can track them using javascript later. Also we'll need to hide the steps when the view renders to the end user, so define the following CSS class in the same Index.cshtml template, or you can do it in a .CSS file:

We have to add navigation buttons to the template. Add the following two buttons to the end of the template:

Adding javascript logic

We will need to handle the button clicks on the client side. The "Next" button takes the user to the next step, while the "Back" button takes them to the previous step. The button behind the scenes uses javascript to hide the current step panel (ie <div>) and show the next one, or the previous one (in case of back button).

We will need to add the following code to the .cshtml template:

<script type="text/javascript">
    $(function ()
    {

        $(".wizard-step:first").fadeIn(); // show first step


        // attach backStep button handler
        // hide on first step
        $("#back-step").hide().click(function ()
        {
            var $step = $(".wizard-step:visible"); // get current step
            if ($step.prev().hasClass("wizard-step")) { // is there any previous step?
                $step.hide().prev().fadeIn();  // show it and hide current step

                // disable backstep button?
                if (!$step.prev().prev().hasClass("wizard-step")) {
                    $("#back-step").hide();
                }
            }
        });


        // attach nextStep button handler       
        $("#next-step").click(function ()
        {

            var $step = $(".wizard-step:visible"); // get current step
                        
            var validator = $("form").validate(); // obtain validator
            var anyError = false;
            $step.find("input").each(function ()
            {
                if (!validator.element(this)) { // validate every input element inside this step
                    anyError = true;
                }

            });

            if (anyError)
                return false; // exit if any error found
             
            

            
            if ($step.next().hasClass("confirm")) { // is it confirmation?
                // show confirmation asynchronously
                $.post("/wizard/confirm", $("form").serialize(), function (r)
                {
                    // inject response in confirmation step
                    $(".wizard-step.confirm").html(r);
                });

            }
                        
            if ($step.next().hasClass("wizard-step")) { // is there any next step?
                $step.hide().next().fadeIn();  // show it and hide current step
                $("#back-step").show();   // recall to show backStep button
            }
            
            else { // this is last step, submit form
                $("form").submit();
            }


        });

    });

</script>

Creating a POST Action

Whenever the user clicks on the submit button, the controller must handle the request. We will need an action that handles the wizard data. Add a new action like below:

If all data is valid, then we will show the "Complete" template. We will add the "Complete" template by right-clicking within the new POST action, and then choosing "Add View":

In the "Complete.cshtml" template, we will just show the following message to the end user:

The wizard is ready now. Let's launch the site and try it. Navigate to "/wizard":

Notice how client-side validation is still working nicely. We did not need to enable client-side validation manually in the template as before, this is because ASP.NET MVC 3 already has it enabled by default. We did also hide the "Back" button on first step using javascript. We cannot submit the form until all information is correct. Even if a user bypasses javascript validation (which can be easily done), the form will be re-displayed again.

Further Enhancements

Prevent User from Navigating to Next Step when errors found

You may have noticed that the user can navigate to the next step even if the previous step contains errors. Let's prevent the user from navigating to the next step by applying some logic into our javascript code.

Since client-side validation uses jQuery validation behind the scenes, we obtained a validator's instance and applied validation to every input element on the current step. This will cause alert messages to appear like below:

Now we cannot move forward to step 2 until we enter a valid email address. This won't change the final result as the form won't post back until all fields are OK, however, it will improve user's experience.

Show a Confirmation Dialog Before Submission

Wizards normally show users a summary of all the details they enter before completion. This will give the user a chance to modify any mistake they made when filling out the form:

If any of the above information is incorrect, we can go back and modify it. This can be handy for large forms, as the user sometimes forgets to fill out some answers.

First, let's add the controller action that we will call to return the template above. In the Wizard controller, add the following action:

Now let's add the "Confirm.cshtml" template that is show in the screenshot above. Right click within the newly added action method, and choose "Add View":

This time we created a partial view since we will use call the action asynchronously (ie using AJAX). This is a limitation because if we do not use AJAX, all the form data (input fields) will be lost due to the stateless HTTP (There are workaround though). Anyway, let's modify the rendered template so it looks like below:

We added the "ValidationSummary" to show errors that cannot be detected on the client-side like duplicate usernames, or bypassed validation rules. If any errors found, the template will show them.

If we will use AJAX to load that template, we will need to inject it somewhere in the main template (Index.cshtml). Let's add it after the final step in the wizard, we will also mark it with both the CSS classes "wizard-step" and "confirm"

The HTML code returned from the "Confirm" action will be injected in that container. The only missing thing now is the javascript code to load the template from the server. Modify the "next step" button by adding the following code:

* You can download all the code by clicking the link "Download code" at the top.

Summary

Wizards improve web sites usability by breaking a long list of questions into multistep simple forms. If your site contains such a long form, it is recommended that you use a wizard, especially if it's the registration form. Users will become reluctant if they see a huge list of questions on one page. We learnt how to create a wizard with the aid of javascript. You can do further enhancements to this wizard by adding more navigation links, etc.

Tags:

ASP.NET | jQuery | MVC | Website Optimization

jQuery templates vs. MVC Partial Views

Introduction

Now that jQuery templates are becoming popular, many ASP.NET developers are using them on their sites. We know that both jQuery templates and partial views can achieve same result. There are even many blog posts that cover the details of jQuery templates, so I am not going to explain them here. Instead, I am going to show you when it is convenient to use jQuery templates and/ or partial views.

Client-side templates vs. partial views

As you might know, jQuery templates can be thought as client-side view engine. Sometimes jQuery templates can complicate your code and may not bring you any benefit at all. They can be handy though if you know when to use them. Here are some guidelines that will help you decide:

  • Use partial views if data is not to be manipulated on the client. For example, if you are loading a list of items (eg books) and need to show them in table rows or whatever. Why would you need jQuery templtes?!
    Using jQuery templates will impose extra javascript code with no benefit.
  • Use jQuery templates if you are going to modify or refine the information returned from server before rendering to HTML. For example, if you have a list of items (eg books), you might need to modify their order or adjust time to browser’s local time before showing them, you will need to parse them on the client using some tool like jQuey templates.
  • Use jQuery templates if you need to show same information in more than one part of the DOM even if you are not planning to modify this information. For example, To render a list of books as table rows (<tr>) and as list items (<li>) at the same time, you will normally return a JSON containing an array of the books, and then parse it using two different views and show the lists in their corresponding parts in the DOM. However, partial views cannot do the same thing at the same time, it would require another round trip to the server which is not efficient.
  • Use jQuery templates when you want to pull data from 3rd party servers like twitter. For example, you can load tweets on the client using JSON format and then parse them using jQuery templates. You are unlikely to send this information (tweets) back to your server for the sake of rendering to HTML. It would be superfluous. However, if your server is acting as a proxy, a one that fetches tweets directly from twitter, it makes sense to use partial views.
  • Use jQuery templates if your site is build entirely using javascript and Web Services. This makes more sense in ASP.NET Web Forms (Classic ASP.NET), because ASP.NET Web Forms has no idea of partial views. So jQuery templates act as a view-engine on the client. It would an efficient approach, but very tedious!!
  • Use partial views when javascript is disabled on the client. Remember if javascript is disabled, jQuery templates won’t be available at all.
  • Use jQuery templates if you want to return different pieces of information from the server. For example, you can fetch a list of books, a list of authors, and a list of movies all at the same time in only one roundtrip to your server. You can do this using JSON format containing arrays for each one. With partial views, you need to perform multiple requests to fetch each array independently as ready HTML. Note you can also do this by returning direct javascript code that injects HTML elements into DOM. This approach is not recommended as it complicates your serverside code with javascript, which is likely to change over time.

Summary

Recall that jQuery templates are much more flexible than partial views, but also more tedious. Sometimes they are not necessary and sometimes they are really handy. It is a trade-off between flexibility and complexibility. But you can also mingle between partial views and jQuery templates in your website.

Tags: , , , ,

ASP.NET | jQuery | MVC

How to Return Random Rows Efficiently in SQL Server

How to Return Random Rows Efficiently in SQL Server

Introduction

When building an application, sometimes you need some random rows from a database table whether it is for testing purpose or some other. There are different ways to select random rows from a table in SQL Server. For example, consider the following SQL statement which returns 20 random orders from the Northwind database

select  top(20) * from Orders order by newid()

Because the previous query scans the whole table, this solution is perfect for small tables. However, for large tables that contain hundred of thousands or even millions of rows, this query will be rather slow.

TABLESAMPLE Clause

Fortunately, SQL Server 2005 and later include a feature you may have never heard of that fits for this purpose. This unknown feature is a clause called TABLESAMPLE that you specify after table name in a FROM clause. The TABLESAMPLE clause has the following syntax:

TABLESAMPLE [SYSTEM] (sample_number [ PERCENT | ROWS ] )
[ REPEATABLE (repeat_seed) ]

Here is an example that returns random rows from the Orders table using TABLESAMPLE:

Select * from Orders TABLESAMPLE(20 rows)

Note that you might not get exactly 20 rows. Also note that for small tables you probably won’t get any results at all. We will see why and how to overcome this problem.

SYSTEM specifies an ANSI SQL implementation-dependent sampling method. Specifying SYSTEM is optional, but this option is the only sampling method available in SQL Server and is applied by default.

You can use either ROWS or PERCENT to specify how many rows you want back in the results. SQL Server generates a random value for each physical page in that table. Based on that value, the page is either included or excluded. When a page is included, all rows in that page are included. For example, if you choose to select only 5 percent, then all rows from approximately 5 percent of the data pages are included in the result. When you choose the number of rows explicitly (use ROWS option) as in the previous example, this number is actually converted into a percentage of the total number of rows in that table. Because page size can vary, you might not get the exact number of rows you requested. Rather, you will get a result set size close to the number you requested.

To make it more likely you will get the exact number of rows you requested, you should specify a greater number of rows than what you need in the TABLESAMPLE clause and use TOP to limit the result to the actual number of rows you need. For example, if you need 500 rows:

Select top(500) * from Orders TABLESAMPLE(1000 rows)

You may still get a fewer number of rows, but you will never get more. The larger the number of rows you specify in the TABLESAMPLE clause, the more likelihood to get the exact number of rows you really want.

Using the REPEATABLE Option

If you need to get repeatable results, use the REPEATABLE option. The REPEATABLE option causes a selected sample to be returned again. When REPEATABLE is specified with the same repeat_seed value, SQL Server returns the same subset of rows, as long as no changes have been made to the table. When REPEATABLE is specified with a different repeat_seed value, SQL Server will typically return a different sample of the rows in the table. For example, the following code returns the same set of results even if you run it multiple times:

Select * from Orders TABLESAMPLE(30 rows) repeatable(55)

When to use TABLESAMPLE

Use TABLESAMPLE on large tables and when the resulting rows do not have to be truly random at the level of individual rows. However, TABLESAMPLE cannot be applied to derived tables, tables from linked servers, and tables derived from table-valued functions, rowset functions, or OPENXML. TABLESAMPLE cannot be specified in the definition of a view or an inline table-valued function.

I am also using twitter for posting useful tips, you can follow me at http://twitter.com/NadeemAfana

Tags:

SQL Server | SQL | CodeProject

Session-less Controllers and TempData in ASP.NET MVC


Introduction

Microsoft has released ASP.NET MVC 3 RC with a dozen of exciting features. One of these features is Sessionless controllers, meaning you can specify the type of session state behavior that is required in order to support HTTP requests to a controller.

Sessionless Controller

If you are not using Session State in your web site, you can gain slight performance improvement by disabling session state globally. However, if you use session state in some controllers, you can keep it in those controllers, and disable it in those that are not using it.

The new ControllerSessionStateAttribute gives you more control over session-state behavior for controllers by specifying a System.Web.SessionState.SessionStateBehavior enumeration value. The following example shows how to turn off session state for all requests to a controller.

[ControllerSessionState(SessionStateBehavior.Disabled)]
public class HomeController : Controller {
  public ActionResult Index() {
    
  
  }
}

The SessionStateBehavior Enumeration Specifies the type of session support which is one of the following values:

  • Disabled: Session state is disabled for the processing request.
  • ReadOnly: Read-only session state is enabled for the request. This means that session state cannot be updated.
  • Required: Full read-write session state behavior is enabled for the request
  • Default: The default ASP.NET logic is used to determine the session state behavior for the request.

Recall that TempData uses Session State behind the scenes, and using it in any controller that has session state disabled will throw an exception. However, it is still possible to use TempData in that case by configuring TempData to use browser cookies instead of Session State. Below is revamped code to use a browser cookie as a storage location instead of Session State.

First, you create a base class controller from which your controllers derive:

 public class ControllerBase : Controller
    {
        // Store TempData in browser's cookie instead
        protected override ITempDataProvider CreateTempDataProvider()
        {
            return new CookieTempDataProvider(HttpContext);
        }
        
    
    }

Just derive all your classes from BaseController. Here is the CookieTempDataProvider:

  public class CookieTempDataProvider : ITempDataProvider
    {
        internal const string TempDataCookieKey = "__ControllerTempData";
        HttpContextBase _httpContext;

        public CookieTempDataProvider(HttpContextBase httpContext)
        {
            if (httpContext == null)
            {
                throw new ArgumentNullException("httpContext");
            }
            _httpContext = httpContext;
        }

        public HttpContextBase HttpContext
        {
            get
            {
                return _httpContext;
            }
        }

        protected virtual IDictionary<string, object> LoadTempData(ControllerContext controllerContext)
        {
            HttpCookie cookie = _httpContext.Request.Cookies[TempDataCookieKey];
            if (cookie != null && !string.IsNullOrEmpty(cookie.Value))
            {
                IDictionary<string, object> deserializedTempData = DeserializeTempData(cookie.Value);



                return deserializedTempData;
            }

            return new Dictionary<string, object>();
        }

        protected virtual void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values)
        {
            bool isDirty = (values != null && values.Count > 0);

          
            string cookieValue = SerializeToBase64EncodedString(values);  
            var cookie = new HttpCookie(TempDataCookieKey);
            cookie.HttpOnly = true;

            // Remove cookie
            if (!isDirty)
            {
                cookie.Expires = DateTime.Now.AddDays(-4.0);
                cookie.Value = string.Empty;

                _httpContext.Response.Cookies.Set(cookie);

                return;

            }
            cookie.Value = cookieValue;

            _httpContext.Response.Cookies.Add(cookie);
        }

        public static IDictionary<string, object> DeserializeTempData(string base64EncodedSerializedTempData)
        {
            byte[] bytes = Convert.FromBase64String(base64EncodedSerializedTempData);
            var memStream = new MemoryStream(bytes);
            var binFormatter = new BinaryFormatter();
            return binFormatter.Deserialize(memStream, null) as IDictionary<string, object> /*TempDataDictionary : This returns NULL*/;
        }

        public static string SerializeToBase64EncodedString(IDictionary<string, object> values)
        {
            MemoryStream memStream = new MemoryStream();
            memStream.Seek(0, SeekOrigin.Begin);
            var binFormatter = new BinaryFormatter();
            binFormatter.Serialize(memStream, values);
            memStream.Seek(0, SeekOrigin.Begin);
            byte[] bytes = memStream.ToArray();
            return Convert.ToBase64String(bytes);
        }

        IDictionary<string, object> ITempDataProvider.LoadTempData(ControllerContext controllerContext)
        {
            return LoadTempData(controllerContext);
        }

        void ITempDataProvider.SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values)
        {
            SaveTempData(controllerContext, values);
        }
    }

Do not store any sensitive data inside TempData now as it is sent to the client, and it can be easily decoded with minimal effort.

I am also using twitter for posting useful tips, you can follow me at http://twitter.com/NadeemAfana

Tags:

ASP.NET | Website Optimization

Entity Framework Advanced Tips - part 2


Part 2: Eager loading and Lazy loading.

Introduction

In this post, I am going to show you how to load related entities in two different ways, and the pros and cons of each one.

Loading Related Entities

When you ask Entity Framework to load an entity like a publisher, it will not return that publisher’s books because EF has no way to know how much information you really need. There could be one million books by that publisher, why would EF load one million rows from database?! This makes sense because you are not likely to be interested in all the books; however, you might be interested in some of them.

Consider the following example that loads 3 publishers from the pubs database:

var pubs = context.publishers.Take(3).ToList();  // Take only 3 publishers

                foreach (var p in pubs)
                {
                    Console.WriteLine("{0} has published the following titles:", p.pub_name );
                    foreach (var t in p.titles)
                    {
                        Console.WriteLine("\t" + t.Title);
                    }
                    
                }

It also queries the titles of each publisher. Note that titles were not loaded with the original query, but instead they were loaded seamlessly by iterating through each publisher. This method is known as lazy loading or deffered loading. Because related entities (ie titles) have been loaded after the original query, extra roundtrips to the database were required to complete the query request. In this example, 3 extra queries were made to the database to return the titles for all the 3 publishers. Imagine you had 100 publishers, it would require 100 roundtrips to get the titles for all these publishers. In that case, lazy loading is not convenient.

Although Entity Framework 4 uses lazy loading by default, it may not be efficient depending on your scenario. For example, if you need all the related data (eg titles) as in the previous case, it makes sense to load them all in only one roundtrip. But if you need only few of these titles, lazy loading can be appropriate. You should always recall that lazy loading requires extra roundtrips to the database as you iterate through your data collection.

Another way to load all titles with the original query is to use the Include method. This method is called eager-loading. Here is an example:

var pubs = context.publishers.Include("titles").Take(3).ToList();  // Take only 3 publishers

                foreach (var p in pubs)
                {
                    Console.WriteLine("{0} has published the following titles:", p.pub_name );
                    foreach (var t in p.titles)
                    {
                        Console.WriteLine("\t" + t.Title);
                    }
                    
                }

The previous code will return all titles in the same query, thus no additional roundtrips are required. In case you wonder about the SQL statement generated by EF:

SELECT 
[Project2].[C1] AS [C1], 
[Project2].[pub_id] AS [pub_id], 
[Project2].[pub_name] AS [pub_name], 
[Project2].[city] AS [city], 
[Project2].[state] AS [state], 
[Project2].[country] AS [country], 
[Project2].[C2] AS [C2], 
[Project2].[title_id] AS [title_id], 
[Project2].[title] AS [title], 
[Project2].[type] AS [type], 
[Project2].[pub_id1] AS [pub_id1], 
[Project2].[price] AS [price], 
[Project2].[advance] AS [advance], 
[Project2].[royalty] AS [royalty], 
[Project2].[ytd_sales] AS [ytd_sales], 
[Project2].[notes] AS [notes], 
[Project2].[pubdate] AS [pubdate]
FROM ( SELECT 
    [Limit1].[pub_id] AS [pub_id], 
    [Limit1].[pub_name] AS [pub_name], 
    [Limit1].[city] AS [city], 
    [Limit1].[state] AS [state], 
    [Limit1].[country] AS [country], 
    [Limit1].[C1] AS [C1], 
    [Extent2].[title_id] AS [title_id], 
    [Extent2].[title] AS [title], 
    [Extent2].[type] AS [type], 
    [Extent2].[pub_id] AS [pub_id1], 
    [Extent2].[price] AS [price], 
    [Extent2].[advance] AS [advance], 
    [Extent2].[royalty] AS [royalty], 
    [Extent2].[ytd_sales] AS [ytd_sales], 
    [Extent2].[notes] AS [notes], 
    [Extent2].[pubdate] AS [pubdate], 
    CASE WHEN ([Extent2].[title_id] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C2]
    FROM   (SELECT TOP (3) 
        [Extent1].[pub_id] AS [pub_id], 
        [Extent1].[pub_name] AS [pub_name], 
        [Extent1].[city] AS [city], 
        [Extent1].[state] AS [state], 
        [Extent1].[country] AS [country], 
        1 AS [C1]
        FROM [dbo].[publishers] AS [Extent1] ) AS [Limit1]
    LEFT OUTER JOIN [dbo].[titles] AS [Extent2] ON [Limit1].[pub_id] = [Extent2].[pub_id]
)  AS [Project2]
ORDER BY [Project2].[pub_id] ASC, [Project2].[C2] ASC

Note that the string parameter of the Include method takes a query path that controls which related entities to return as part of the initial query. You can specify query path like this to return titles and their sales:

var pubs = context.publishers.Include("titles.sales").Take(3).ToList();

You can also eager load employees additionally:

var pubs = context.publishers.Include("titles.sales").Include("employees").Take(3).ToList();

However, you cannot filter the related data this way. For example, in the following code:

var pubs = context.publishers.Include("titles").Take(3).ToList();

You cannot query titles that start with letter 'T'. This is some limitation of the lazy-loading approach.

Eager loading or Lazy loading?

Now comes the big question. Which method should you use?

It depends on what you really need. Lazy loading postpones data retrieval until data is needed, but requires additional roundtrips to the database. Eager loading fetches all data together in only one roundtrip, but the query command can be complex depending on the model. Eager loading is ideal if all data is really needed.

I am also using twitter for posting useful tips, you can follow me at http://twitter.com/NadeemAfana

Tags:

ASP.NET | Entity Framework | Website Optimization

Google+