Introduction to Data-Tier Applications in SQL Server 2008 R2

Problem
I’m looking at the new features in SQL Server 2008 R2 and I see one called a Data-Tier application.   Can you provide an overview of this feature?

Solution
SQL Server 2008 R2 has a set of features called Application and Multi-Server Management that focus on reducing the complexity of managing databases and applications.  The Data-Tier application is one of these features.  At a high level the Data-Tier application provides development, deployment, and update capabilities for an application database.  The unit of deployment is a DAC package which is a file that contains everything needed to deploy (or update) the application database.  The DAC package can be created from a Visual Studio 2010 Data-Tier project or from SQL Server 2008 R2 Management Studio (SSMS).  In addition SQL Server 2008 R2 includes monitoring and dashboard reporting on key metrics for the Data-Tier application.

Before digging in to the details of the Data-Tier application, please note the following guidance as offered in the MSDN white paper Data-Tier Applications in SQL Server 2008 R2 (check the white paper for additional details):

  • This initial release is designed for simple, departmental systems with a database size up to a few gigabytes
  • There are a number of object types that are not supported; e.g. Service Broker objects, DDL triggers, Filestream columns, etc.  You cannot create a Data-Tier application if your database includes these
  • Filegroups are not stored in the DAC package; when you deploy you get one filegroup with a single file
  • Passwords for SQL logins are not stored in the DAC package; they will be created during deployment (or update) but disabled; you have to manually enable them and set the password
  • A Data-Tier application can be extracted from any version of SQL Server 2000 or later; however, a DAC package can only be deployed to SQL Server 2008 R2

Creating a Data Tier Application with SQL Server Management Studio

You can create a DAC package from an existing database with the SQL Server 2008 R2 version of Management Studio.  Navigate to the database in the Object Explorer then right click it and select Tasks, Extract Data-tier Application from the context menu as shown below:

The Extract Data-tier Application wizard will be launched.  Fill in the Properties dialog as shown below:

Note that the output of the wizard is a DAC package that you will import into a SQL Server Data-tier Application project in Visual Studio 2010.  After filling in the above dialog, the next wizard step checks the database to make sure it can be supported by the current version of the data tier application.  The final wizard step creates the DAC package which in this case is “C:\temp\Chinook.dacpac”.

Note from the Tasks context menu above that you could register a SQL Server 2008 R2 database as a Data-tier application.  I’m going to proceed with how to do it from scratch in Visual Studio 2010, how to import a DAC package, and how to deploy.

Creating a Data-Tier Application with Visual Studio 2010

Beginning with Visual Studio 2005 there has been a Database project that you could use to create, maintain and deploy a SQL Server database.  Visual Studio 2010 Premium and Ultimate versions include a new project template for a Data-Tier application.

To begin click File, New, Project then select SQL Server Data-tier Application as shown below:

After creating the new project the Solution Explorer has the folder layout shown below:

Like the database project in earlier versions of Visual Studio, the SQL Server Data-tier Application is used to organize each individual database object into its own .SQL file.  For example to create a table, right click on Tables, then click Add, Table from the context menu and type in the name the table.  A new source file will be added to the project with a stubbed out script; e.g.:

Use the text editor to complete the table schema.  If you want to create a project from an existing database there are two options available to do that.  Right click on the Project in the Solution Explorer and you will see the choices Import Script and Import Data-tier Application.  Import Script allows you to process one or more files that contain T-SQL commands to create database objects.  Import Data-tier Application will read a DAC package and create the database objects in the project.  If you manually add database objects to the project, the import data-tier application will be disabled.

To import the DAC package that we created earlier, select Import Data-tier Application and navigate to the DAC package:

The wizard will continue and create the database objects in the project from the DAC package.  The Solution Explorer now shows tables, indexes, etc.:

Data-Tier Application Project Settings

There are a number of project settings that you can customize to suit your needs.  Right click Properties under the Project in Solution Explorer then select Open from the context menu to display the Project Settings:

Note the Name and Version; when you deploy changes make sure to increment the Version.  Click Build to display the Build properties:

The Visual Studio paradigm is that you create an individual script file for tables, indexes, etc. then perform a Build which creates the DAC package.  Specify the name of the DAC package and folder in the above dialog.  Click Build Events to display the Build Events properties:

The Pre-build and Post-build event commands are available to specify commands that will be automatically executed when you Build.  These commands could be just anything that you need to do right before the Build and right after the Build completes.  Click Deploy to view the Deploy properties:

You can enable deployment directly to an instance of SQL Server 2008 R2 by specifying the connection string.  Click Code Analysis to view the Code Analysis properties:

Code Analysis provides a number of built-in rules that you can enable for your project.  You can specify if violating these rules will be treated as an error.

You can also specify database settings and server selection policies; expand Properties in the Solution Explorer underneath the project.  There you will find Database.sqlsettings and ServerSelection.sqlpolicy.  Dabtabase.sqlsettings is shown below:

ServerSelection.sqlpolicy is shown below:

The above settings will be stored in the DAC package and enforced on deployment.  The Facet properties above allow you to specify things like SQL Server edition, version, etc.  When you click a checkbox a dialog will be displayed allowing you to select a value from a dropdown list (e.g. True or False) or enter a value (e.g. a SQL Server major version).

Deploying a Data-Tier Application

There are two steps to deploying your Data-Tier application to an instance of SQL Server 2008 R2.  First you right click on the project in Solution Explorer and select Build (or Rebuild) from the context menu.  This will create the DAC package in the folder you specified in the Project Build settings dialog.  The second step is to deploy the DAC package; you can right click on the project in the Solution Explorer and select Deploy from the context menu or you can deploy with SQL Server Management Studio.

To deploy using the Deploy command in the project you need to specify a connection string for the destination database on the Project Deploy properties page.  To deploy using SQL Server Management Studio, navigate to the Management node in Object Explorer then right click on Deploy Data-tier Application as shown below:

The Deploy Data-tier Application wizard will be launched.  Select the DAC package to deploy:

Click Next to proceed to the Update Configuration step where you can specify the database name, data file path and log file path:

Click Next to display a summary then click Next again to perform the deployment.  You will see output similar to the following on completion:

Note that the database name that is initially created has a globally unique identifier (GUID) appended to it.  After the process completes successfully, the database is renamed to what you specified.

Monitoring Data-Tier Applications

There is a new feature in SQL Server 2008 R2 called a Utility Control Point which provides monitoring of database instances and Data-tier applications.  I will cover the setup in a future article, but right now I want to give you a preview of what you get for a data-tier application:

The above screen shot was taken from the Utility Explorer in SQL Server 2008 R2 Management Studio.  Since I just created the data-tier application there is no data available yet.  You get a dashboard view with CPU Utilization, Storage Utilization, Policy Details and Property Details.  The only requirement is to run through the Utility Control Point setup process which I will do in a future article.

Updating a Data-Tier Application

After you deploy a data-tier application, you will undoubtedly want to make changes; e.g. add or change tables, stored procedures, views, etc.  You can use the Visual Studio 2010 Data-tier project to do this and deploy your changes in the same way we did in the section above.  Make your changes and be sure to increment the Version in the Project Settings dialog, then performed a Build (or Rebuild) which creates an updated DAC package.  To deploy the update, go to SQL Server Management Studio, right click on the Data-tier application, then select Upgrade Data-tier Application:

The familiar wizard will be displayed; navigate to the DAC package and note that the Version has been incremented:

At a high level the wizard will create a new database, deploy everything in the DAC package to the new database, generate scripts that will copy the data from the current database to the new database, then rename the current and new databases appropriately.  This is a very brief overview of the upgrade process; you can get additional details fromUpgrading Data-Tier Applications in SQL Server Books on Line.  I will be creating a tip focused on upgrade in the near future.

Next Steps

  • The Data-tier Application is an interesting new feature in SQL Server 2008 R2 that may be able to save time when deploying and upgrading departmental databases.
  • For additional details on data-tier applications take a look at this white paper on MSDN.
  • Take a look at the SQL Server 2008 R2 Update for Developers Training Kit (June 2010 Update) for Presentations, Demos, Hands on Labs, and Videos.  There is a wealth of material on Data-tier Applications.
  • You can download the sample code used in this tip here.
Advertisements

Cloning an entity in linq-to-entities

making a clone of a record and popping it back into the database

There are a lot of reason you may want to do something like this, for me, users wanted to be able to make a copy of a huge record so they would then be able to go in and change a few things rather than make a whole new record which was very time consuming. At first, I though of pulling the item, manually copying each property, and inserting… but this is programming, there must be a better way. So then I thought about Reflection and how I might be able to work with that, but that became a big mess that I never got working prior to being discouraged. Next I hit Google, and an awesome blog had a great post to get me on the right track:http://damieng.com/blog/2009/04/12/linq-to-sql-tips-and-tricks-2.

That solution used Serialization to clone the entity, so simple, and so effective. The example they used there was Linq-to-SQL, but it will work for Linq-to-Entities just the same. I modified the code slightly to be used as an extension to basically any type… as this can really clone almost anything you throw at it (I think):

public static T Clone<T>(this T source)
{
  var dcs = new System.Runtime.Serialization
    .DataContractSerializer(typeof(T));
  using (var ms = new System.IO.MemoryStream())
  {
    dcs.WriteObject(ms, source);
    ms.Seek(0, System.IO.SeekOrigin.Begin);
    return (T)dcs.ReadObject(ms);
  }
}

That simple little bit of code will now clone *anything* – perfect! Now is just how to push it back into the db, which can be a little confusing. Initially, I tried this, which was intuitive to me:

MyEntities db = new MyEntities();
note n = db.note.First(nt => nt.note_id == some_int); 
note new_n = n.Clone();
db.AddTonote(new_n);
db.SaveChanges();

But was met with this error:

An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.

But, after actually thinking about it, that makes sense, sincenew_n is the same *exact* object as n, I can’t really insert it into the DB, it is basically confusing the two. So to get around that, you have to detach the original Entity:

using (MyEntities db = new MyEntities())
{
  note n = db.note.First(nt => nt.note_id == some_int);
  note new_n = n.Clone();
  db.Detach(n);
  db.AddTonote(new_n);
  db.SaveChanges();
}

Closer, but still did not work, I now got the following error:

The object cannot be added to the ObjectStateManager because it already has an EntityKey. Use ObjectContext.Attach to attach an object that has an existing key.

Which also makes sense as you can’t insert an Entity that already has an EntityKey. So one final change and we have a working clone:

using (MyEntities db = new MyEntities())
{
  note n = db.note.First(nt => nt.note_id == some_int);
  note new_n = n.Clone();
  db.Detach(n);
  new_n.EntityKey = null;
  // make any other changes you want on your clone here
  db.AddTonote(new_n);
  db.SaveChanges();
}

And no we have successfully cloned a record (Entity) and put a copy into the database. This would work very similarly for Linq-to-SQL, but I have not implemented it quite yet.

Globalization, Internationalization and Localization in ASP.NET MVC 3, JavaScript and jQuery – Part 1

Posted 2011-05-25 07:56 PM in ASP.NET | ASP.NET MVC | Internationalization | Javascript.

There are several books worth of information to be said about Internationalization (i18n) out there, so I can’t solve it all in a blog post. Even 9 pages of blog posts. I like to call it Iñtërnâtiônàlizætiøn, actually.

There’s a couple of basic things to understand though, before you create a multilingual ASP.NET application. Let’s agree on some basic definitions as these terms are often used interchangeably.

  • Internationalization (i18n) – Making your application able to support a range of languages and locales
  • Localization (L10n) – Making your application support a specific language/locale.
  • Globalization – The combination of Internationalization and Localization
  • Language – For example, Spanish generally. ISO code “es”
  • Locale – Mexico. Note that Spanish in Spain is not the same as Spanish in Mexico, e.g. “es-ES” vs. “es-MX”

Culture and UICulture

The User Interface Culture is a CultureInfo instance from the .NET base class library (BCL). It lives on Thread.CurrentThread.CurrentUICulture and if you felt like it, you could set it manually like this:

1
Thread.CurrentThread.CurrentUICulture = new CultureInfo("es-MX");

The CurrentCulture is used for Dates, Currency, etc.

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

However, you really ought to avoid doing this kind of stuff unless you know what you’re doing and you really have a good reason.

The user’s browser will report their language preferences in the Accept-Languages HTTP Header like this:

GET http://www.hanselman.com HTTP/1.1
Connection: keep-alive
Cache-Control: max-age=0
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8

See how I prefer en-US and then en? I can get ASP.NET to automatically pass those values and setup the threads with with the correct culture. I need to set my web.config like this:

1
2
3
<system.web>
    <globalization culture="auto" uiCulture="auto" enableClientBasedCulture="true" />
...snip...

That one line will do the work for me. At this point the current thread and current UI thread’s culture will be automatically set by ASP.NET.

The Importance of Pseudointernationalization

Back in 2005 I updated John Robbin’s Pseudoizer (and misspelled it then!) and I’ve just ported it over to .NET 4 and used it for this application. I find this technique for creating localizable sites really convenient because I’m effectively changing all the strings within my app to another language which allows me to spot strings I missed with the tedium of translating strings.

You can download the .NET Pseudoizer here.

Here’s an example from that earlier post before I run it through Pseudointernationalization:

1
2
3
4
5
6
7
8
9
<data name="Accounts.Download.Title">
  <value>Transaction Download</value>
</data>
<data name="Accounts.Statements.Action.ViewStatement">
  <value>View Statement</value>
</data>
<data name="Accounts.Statements.Instructions">
  <value>Select an account below to view or download your available online statements.</value>
</data>

I can convert these resources with the pseudoizer like this:

PsuedoizerConsole examplestrings.en.resx examplestrings.xx.resx

and here’s the result:

1
2
3
4
5
6
7
8
9
<data name="Accounts.Download.Title">
  <value>[Ŧřäʼnşäčŧįőʼn Đőŵʼnľőäđ !!! !!!]</value>
</data>
<data name="Accounts.Statements.Action.ViewStatement">
  <value>[Vįęŵ Ŝŧäŧęmęʼnŧ !!! !!!]</value>
</data>
<data name="Accounts.Statements.Instructions">
  <value>[Ŝęľęčŧ äʼn äččőūʼnŧ þęľőŵ ŧő vįęŵ őř đőŵʼnľőäđ yőūř äväįľäþľę őʼnľįʼnę şŧäŧęmęʼnŧş. !!! !!! !!! !!! !!!]</value>
</data>

Cool, eh? If you’re working with RESX files a lot, be sure to familiarize yourself with the resgen.exe command-line tool that is included with Visual Studio and the .NET SDK. You have this on your system already. You can move easily between the RESX XML-based file format and a more human- (and translator-) friendly text name=value format like this:

resgen /compile examplestrings.xx.resx,examplestrings.xx.txt

And now they are a nice name=value format, and as I said, I can move between them.

Accounts.Download.Title=[Ŧřäʼnşäčŧįőʼn Đőŵʼnľőäđ !!! !!!]
Accounts.Statements.Action.ViewStatement=[Vįęŵ Ŝŧäŧęmęʼnŧ !!! !!!]
Accounts.Statements.Instructions=[Ŝęľęčŧ äʼn äččőūʼnŧ þęľőŵ ŧő vįęŵ őř đőŵʼnľőäđ yőūř äväįľäþľę őʼnľįʼnę şŧäŧęmęʼnŧş. !!! !!! !!! !!! !!!]

During development time I like to add this Pseudoizer step to my Continuous Integration build or as a pre-build step and assign the resources to a random language I’m NOT going to be creating, like Polish (with all due respect to the Poles) so I’d make examplestrings.pl.resx and the then we can test our fake language by changing our browser’s UserLanguages to prefer pl-PL over en-US.

Localization Fallback

Different languages take different amounts of space. God bless the Germans but their strings will take an average of 30% more space than English phrases. Chinese will take 30% less. The Pseudoizer pads strings in order to illustrate these differences and encourage you to take them into consideration in your layouts.

Localization within .NET (not specific to ASP.NET Proper or ASP.NET MVC) implements a standard fallback mechanism. That means it will start looking for the most specific string from the required locale, then fallbackcontinuing to look until it ends on the neutral language (whatever that is). This fallback is handled by convention-based naming. Here is an older, but still excellent live demo of Resource Fallback at ASPAlliance.

For example, let’s say there are three resources. Resources.resx, Resources.es.resx, and Resources.es-MX.resx.

Resources.resx:
HelloString=Hello, what’s up?
GoodbyeString=See ya!
DudeString=Duuuude!

Resources.es.resx:
HelloString=¿Cómo está?
GoodbyeString=Adiós!

Resources.es-MX.resx:
HelloString=¿Hola, qué tal?

Consider these three files in a fallback scenario. The user shows up with his browser requesting es-MX. If we ask for HelloString, he’ll get the most specific one. If we ask for GoodbyeString, we have no “es-MX” equivalent, so we move up one to just “es.” If we ask for DudeString, we have no es strings at all, so we’ll fall all the way back to the neutral resource.

Using this basic concept of fallback, you can minimize the numbers of strings you localize and provide users with not only language specific strings (Spanish) but also local (Mexican Spanish) strings. And yes, I realize this is a silly example and isn’t really representative of Spaniards or Mexican colloquial language.

Views rather than Resources

If you don’t like the idea of resources, while you will still have to deal with some resources, you could also have difference views for different languages and locales. You can structure your ~/Views folders like Brian Reiter and others have. It’s actually pretty obvious once you have bought into the idea of resource fallback as above. Here’s Brian’s example:

/Views
    /Globalization
        /ar
            /Home
                /Index.aspx
            /Shared
                /Site.master
                /Navigation.aspx
        /es
            /Home
                /Index.aspx
            /Shared
                /Navigation.aspx
        /fr
            /Home
                /Index.aspx
            /Shared
    /Home
        /Index.aspx
    /Shared
        /Error.aspx
        /Footer.aspx
        /Navigation.aspx
        /Site.master

Just as you can let ASP.NET change the current UI culture based on UserLanguages or a cookie, you can also control the way that Views are selected by a small override of your favorite ViewEngine. Brian includes a few lines to pick views based on a language cookie on his blog.

He also includes some simple jQuery to allow a user to override their language with a cookie like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var mySiteNamespace = {}
mySiteNamespace.switchLanguage = function (lang) {
    $.cookie('language', lang);
    window.location.reload();
}
$(document).ready(function () {
    // attach mySiteNamespace.switchLanguage to click events based on css classes
    $('.lang-english').click(function () { mySiteNamespace.switchLanguage('en'); });
    $('.lang-french').click(function () { mySiteNamespace.switchLanguage('fr'); });
    $('.lang-arabic').click(function () { mySiteNamespace.switchLanguage('ar'); });
    $('.lang-spanish').click(function () { mySiteNamespace.switchLanguage('es'); });
});

I’d probably make this a single client event and use data-language or an HTML5 attribute (brainstorming) like this:

1
2
3
4
5
$(document).ready(function () {
        $('.language').click(function (event) {
            $.cookie('language', $(event.target).data('lang'));
        })
});

But you get the idea. You can set override cookies, check those first, then check the UserLanguages header. It depends on the experience you’re looking for and you need to hook it up between the client and server

Globalized JavaScript Validation

If you’re doing a lot of client-side work using JavaScript and jQuery, you’ll need to get familiar with the jQuery Global plugin. You may also want the localization files for things like the DatePicker and jQuery UI on NuGet via “install-package jQuery.UI.i18n.”

Turns out the one thing you can’t ask your browser via JavaScript is what languages it prefers. That is sitting inside an HTTP Header called “Accept-Language” and looks like this, as it’s a weighted list.

en-ca,en;q=0.8,en-us;q=0.6,de-de;q=0.4,de;q=0.2

We want to tell jQuery and friends about this value, so we need access to it from the client side in a different way, so I propose this.

This is Cheesy – use Ajax

We could do this, with a simple controller on the server side:

1
2
3
4
5
public class LocaleController : Controller {
    public ActionResult CurrentCulture()  {
        return Json(System.Threading.Thread.Current.CurrentUICulture.ToString(), JsonRequestBehavior.AllowGet);
    }
}

And then call it from the client side. Ask jQuery to figure it out, and be sure you have the client side globalization libraries you want for the cultures you’ll support. I downloaded all 700 jQuery Globs from GitHub. Then I could make a quick Ajax call and get that info dynamically from the server. I also include the locales I want to support as scripts like  /Scripts/globinfo/jquery.glob.fr.js. You could also build a dynamic parser and load these dynamically also, or load them ALL when they show up on the Google or Microsoft CDNs as a complete blob.

1
2
3
4
5
6
7
8
9
<script>
    $(document).ready(function () {
        //Ask ASP.NET what culture we prefer
        $.getJSON('/locale/currentculture', function (data) {
            //Tell jQuery to figure it out also on the client side.
            $.global.preferCulture(data);
        });
    });
</script>

But that is a little cheesy because I have to make that little JSON call. Perhaps this belongs somewhere else, like a custom META tag.

Slightly Less Cheesy – Meta Tag

Why not put the value of this header in a META tag on the page and access it there? It means no extra AJAX call and I can still use jQuery as before. I’ll create an HTML helper and use it in my main layout page. Here’s the HTML Helper. It uses the current thread, which was automatically set earlier by the setting we added to the web.config.

1
2
3
4
5
6
7
8
9
10
11
namespace System.Web.Mvc
{
    public static class LocalizationHelpers
    {
        public static IHtmlString MetaAcceptLanguage<T>(this HtmlHelper<T> html)
        {
            var acceptLanguage = HttpUtility.HtmlAttributeEncode(Threading.Thread.CurrentThread.CurrentUICulture.ToString());
            return new HtmlString(String.Format("<meta name=\"accept-language\" content=\"{0}\" />",acceptLanguage));
        }
    }
}

I use this helper like this on the main layout page:

1
2
3
4
5
6
7
8
9
10
11
12
<html>
<head>
    <meta charset="utf-8" />
    <title>@ViewBag.Title</title>
    <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/globinfo/jquery.glob.fr.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/modernizr-1.7.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.global.js")" type="text/javascript"></script>
    @Html.MetaAcceptLanguage()
</head>
...

And the resulting HTML looks like this. Note that this made-up META tag would be semantically different from the Content-Language or the lang= attributes as it’s part of the the parsed HTTP Header that ASP.NET decided was our current culture, moved into the client.

1
2
3
4
5
6
7
8
9
10
11
<html>
<head>
    <meta charset="utf-8" />
    <title>Home Page</title>
    <link href="/Content/Site.css" rel="stylesheet" type="text/css" />
    <script src="/Scripts/jquery-1.5.1.min.js" type="text/javascript"></script>
    <script src="/Scripts/globinfo/jquery.glob.fr.js" type="text/javascript"></script>
    <script src="/Scripts/modernizr-1.7.min.js" type="text/javascript"></script>
    <script src="/Scripts/jquery.global.js" type="text/javascript"></script>
    <meta name="accept-language" content="en-US" />
</head>

Now I can access it with similar code from the client side. I hope to improve this and support dynamic loading of the JS, however preferCulture isn’t smart and actually NEEDS the resources loaded in order to make a decision. I would like a method that would tell me the preferred culture so that I might load the resources on-demand.

1
2
3
4
5
6
7
8
<script>
    $(document).ready(function () {
        //Ask ASP.NET what culture we prefer, because we stuck it in a meta tag
        var data = $("meta[name='accept-language']").attr("content")
        //Tell jQuery to figure it out also on the client side.
        $.global.preferCulture(data);
    });
</script>

So what? Now when I am on the client side, my validation and JavaScript is a little smarter. Once jQuery on the client knows about your current preferred culture, you can start being smart with your jQuery. Make sure you are moving around non-culture-specific data values on the wire, then convert them as they become visible to the user.

1
2
3
var price = $.format(123.789, "c");
jQuery("#price").html('12345');
1
2
var date = $.format(new Date(1972, 2, 5), "D");
jQuery("#date").html(date);
1
2
var units = $.format(12345, "n0");
jQuery("#unitsMoved").html(units);

Now, you can apply these concepts to validation within ASP.NET MVC.

Globalized jQuery Unobtrusive Validation

Adding onto the code above, we can hook up the globalization to validation, so that we’ll better understand how to manage values like 5,50 which is 5.50 for the French, for example. There are a number of validation methods you can hook up, here’s number parsing.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$(document).ready(function () {
    //Ask ASP.NET what culture we prefer, because we stuck it in a meta tag
    var data = $("meta[name='accept-language']").attr("content")
    //Tell jQuery to figure it out also on the client side.
    $.global.preferCulture(data);
    //Tell the validator, for example,
    // that we want numbers parsed a certain way!
    $.validator.methods.number = function (value, element) {
        if ($.global.parseFloat(value)) {
            return true;
        }
        return false;
    }
});

If I set my User Languages to prefer French (fr-FR) as in this screenshot:

Language Preference Dialog preferring French

Then my validation realizes that and won’t allow 5.50 as a value, but will allow 5,50, given this model:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Example
{
    public int ID { get; set; }
    [Required]
    [StringLength(30)]
    public string First { get; set; }
    [Required]
    [StringLength(30)]
    public string Last { get; set; }
    [Required]
    public DateTime BirthDate { get; set; }
    [Required]
    [Range(0,100)]
    public float HourlyRate { get; set; }
}

I’ll see this validation error, as the client side knows our preference for , as a decimal separator.

NOTE: It seems to me that the [Range] attribute that talks to jQuery Validation doesn’t support globalization and isn’t calling into the localized methods so it won’t work with the , and . decimal problem. I was able to fix this problem by overriding the range method in jQuery like this, forcing it to use the global implementation of parseFloat. Thanks to Kostas in the comments on this post for this info.

1
2
3
4
5
6
7
jQuery.extend(jQuery.validator.methods, {
    range: function (value, element, param) {
        //Use the Globalization plugin to parse the value
        var val = $.global.parseFloat(value);
        return this.optional(element) || (val >= param[0] && val <= param[1]);
    }
});

Here it is working with validity…The Value 4.5 is not valid for Hourly Rate

And here it is in a Danish culture working with [range]:

Localized Range

I can also set the Required Attribute to use specific resources and names and localized them from an ExampleResources.resx file like this:

1
2
3
4
5
6
7
8
public class Example
{
    public int ID { get; set; }
    [Required(ErrorMessageResourceType=typeof(ExampleResources),
              ErrorMessageResourceName="RequiredPropertyValue")]
    [StringLength(30)]
    public string First { get; set; }
...snip...

And see this:

image

NOTE: I’m looking into how to set new defaults for all fields, rather than overriding them individually. I’ve been able to override some with a resource file that has keys called “PropertyValueInvalid” and “PropertyValueRequired” then setting these values in the Global.asax, but something isn’t right.

1
2
DefaultModelBinder.ResourceClassKey = "ExampleResources";
ValidationExtensions.ResourceClassKey = "ExampleResources";

I’ll continue to explore this.

Dynamically Localizing the jQuery DatePicker

Since I know what the current jQuery UI culture is, I can use it to dynamically load the resources I need for the DatePicker. I’ve installed the “MvcHtml5Templates” NuGet library from Scott Kirkland so my input type is “datetime” and I’ve added this little bit of JavaScript that says, do we support dates? Are we non-English? If so, go get the right DatePicker script and set it’s info as the default for our DatePicker by getting the regional settings given the current global culture.

1
2
3
4
5
6
7
8
9
10
11
12
//Setup datepickers if we don't support it natively!
if (!Modernizr.inputtypes.date) {
    if ($.global.culture.name != "en-us" && $.global.culture.name != "en") {
        var datepickerScriptFile = "/Scripts/globdatepicker/jquery.ui.datepicker-" + $.global.culture.name + ".js";
        //Now, load the date picker support for this language
        // and set the defaults for a localized calendar
        $.getScript(datepickerScriptFile, function () {
            $.datepicker.setDefaults($.datepicker.regional[$.global.culture.name]);
        });
    }
    $("input[type='datetime']").datepicker();
}

Then we set all input’s with type=datetime. You could have used a CSS class if you like as well.

image

Now our jQuery DatePicker is French.

Right to Left (body=rtl)

For languages like Arabic and Hebrew that read Right To Left (RTL) you’ll need to change the dir= attribute of the elements you want flipped. Most often you’ll change the root <HTML> element to <HTML dir=”rtl”> or change it with CSS like:

1
2
3
div {
   direction:rtl;
}

The point is to have a general strategy, whether it be a custom layout file for RTL languages or just flipping your shared layout with either CSS or an HTML Helper. Often folks put the direction in the resources and pull out the value ltr or rtl depending.

Conclusion

Globalization is hard and requires actual thought and analysis. The current JavaScript offerings are in flux and that’s kind.

A lot of this stuff could be made boilerplate or automatic, but much of it is a moving target. I’m currently exploring either a NuGet package that sets stuff up for you OR a “File | New Project” template with all the best practices already setup and packaged into one super-package. What’s your preference, Dear Reader?

The Complete Script

Here’s my current “complete” working script that could then be moved into its own file. This is a work in progress, to be sure. Please forgive any obvious mistakes as I’m still learning JavaScript.

    <script>
        $(document).ready(function () {
            //Ask ASP.NET what culture we prefer, because we stuck it in a meta tag
            var data = $(“meta[name=’accept-language’]”).attr(“content”)
            //Tell jQuery to figure it out also on the client side.
            $.global.preferCulture(data);
            //Tell the validator, for example,
            // that we want numbers parsed a certain way!
            $.validator.methods.number = function (value, element) {
                if ($.global.parseFloat(value)) {
                    return true;
                }
                return false;
            }
            //Fix the range to use globalized methods
            jQuery.extend(jQuery.validator.methods, {
                range: function (value, element, param) {
                    //Use the Globalization plugin to parse the value
                    var val = $.global.parseFloat(value);
                    return this.optional(element) || (val >= param[0] && val <= param[1]);
                }
            });
            //Setup datepickers if we don’t support it natively!
            if (!Modernizr.inputtypes.date) {
                if ($.global.culture.name != ‘en-us’ && $.global.culture.name != ‘en’) {
                    var datepickerScriptFile = “/Scripts/globdatepicker/jquery.ui.datepicker-” + $.global.culture.name + “.js”;
                    //Now, load the date picker support for this language
                    // and set the defaults for a localized calendar
                    $.getScript(datepickerScriptFile, function () {
                        $.datepicker.setDefaults($.datepicker.regional[$.global.culture.name]);
                    });
                }
                $(“input[type=’datetime’]”).datepicker();
            }
        });
    </script>