Monday, March 31, 2008

Localisation and Profiles: Programmatically changing the user's preferred culture.

A while ago I wrote a post on how to use Localisation in ASP.NET using resource files etc. We'll since then I been asked about how to change the user's culture programmatically by clicking a button or similar.

In that previous article I described how culture selection is based on browser preferences and HTTP language headers. While this is pretty good, there are many scenarios where this could be improved. Say you are travelling overseas and are accessing your appl remotely from someone's elses machine, if the culture of that browser is set to Japanese, then your application might not be readable to you. You might not be able to change your language preferences either because that might not be available in a controlled environment.

What options do we have?

In general, to get manage the user's preferences we have:

  • Use the browser settings ( not that great as described above but good enough for 80% of the time)
  • When a user registers of first uses your application, they can choose language of choice and then save this in the database for later retrieval
  • Persist the above choice in Session state or Cookies.
  • Use ASP.NET Profiles.
  • Let the users choose this in the application. ( buttons. ddls etc ) 

In ASP.NET is very straightforward to select the preferred culture based on the user's browser settings, you can do it at page level like I did in in my prev post but you can also set it up for all your pages in the web.config file saving you the pain of adding the attribute to all your pages throughout your application.

<system.web>
        <globalization culture="auto" uiCulture="auto" fileEncoding="utf-8" requestEncoding="utf-8" 
            responseEncoding="utf-8" />
 ....
What if I want to save the user settings after they register/access the app for the first time?

Think about the options you have to persist the preferred culture: Session: Session is not the best for many reasons, most importantly Session is not there forever, for example by default ASP.NET assumes that the user left the site after no requests have been made for 20 mins ,  your user might get up to get a cup of tea and his Session might expire, therefore when she gets back she might not have the application in her culture of choice. Also, you might not be able to access the Session object when you want to access the user's preferences. So I wouldn't use Session myself.

What about Cookies?

Cookies are more favourable if all you want is to persist a simple user' preference like the culture or their favourite colour I think, it will get hard pretty quickly if you where storing more than just their colour and horoscope preferences.

For example in Global.asax you could implement the Application_BeginRequest event and read the cookie there and setup your CurrentThread to the value stored in the cookie that you created after the user made the choice etc.

void Application_BeginRequest(Object sender, EventArgs args)
{
    HttpCookie myPreferencesCookie = (HttpCookie)Request.Cookies["Culture"];
    // check for null etc etc
    string culture = myPreferencesCookie.Value;
    System.Threading.Thread.CurrentThread.CurrentUICulture = 
         new System.Globalization.CultureInfo(culture);
    System.Threading.Thread.CurrentThread.CurrentCulture = 
         System.Globalization.CultureInfo.CreateSpecificCulture(culture);
}

But I think it will get complicated very quickly and I don't think is the best way. Much better I think is ASP.NET's Profile

The Profile objects is strongly typed and persisted, and you can even implement your own ProfileProvider!

The easiest way to create a Profile is by creating some properties in the your root web.config. Just like the Resource files, ASP.NET compiles the Profile's propertied dynamically and then you have this strongly typed profile assets.

<profile>
   <properties>
      <add name="MyFavouriteNumber" allowAnonymous="true"/>
      <group name="Preferences">
        <add name="Culture" allowAnonymous="true" />
        <add name="Color" allowAnonymous="true"  />
       </group>
   </properties>
 </profile>

Note the attributes, name is very simple, but note alllowAnonymous: This allows anonymous users to read/write properties, you have to set it explicitly because by default, this is set to false. ASP.NET cannot know which user has which profile unless the user is authenticated. So to use this anonymous feauture you have to enable anonymous indentification in your web.config too. More details in MSDN here.

All I did what this...

<anonymousIdentification  enabled="true" />

You can also set defaultvalues, type, readonly attributes to your profile entries too.

Also I defined what is called 'Profile Groups', that lets you organise the properties better and into more logical groups. Then in your app the intellisense will pick it up beautifully! ( Profile.Preferences.Culture )

So far so good, we can store it in the Profile Object but using Profile is a bit of a problem in the same league as the Sesssion Object: Can we access Profile in Global.asax BeginRequest()? Nop, we can't, the only way is to write some code to access the data store where you are persisting the user's preferences. The reason this is the case is that just like Session, Profile is not initialised until Session is ready to roll.

Getting started with a solution to the problem

So far we know we don't want to use Session, we don't want to use Cookies and that Profile is good but we can't really use it straight out of the box.

Question I had was, how can I localise pages based on the choices the user made? and what if the user wants to change back and forth this setting for say language preferences. Anyway, I started easy, lets give them a UI so they can change languages for example.

I created a MasterPage and added some big, impossible to miss flags that can be associated to languages and cultures as shown below. The plan is that by clicking on the flag, the culture associated with the flags will be the Thread.Culture that the page will be running under.

Flags

Simple code too..just a few asp:Images..

<asp:ImageButton CommandName="es-UY" OnCommand="Flag_ClickedCommand" AlternateText="Spanish"
       CssClass="Flag" ImageUrl="~/Profile/FlagsImages/uy.gif" ID="imgUruguay" runat="server"
       />
<asp:ImageButton CommandName="zh-CN" OnCommand="Flag_ClickedCommand" AlternateText="Chinese"
       CssClass="Flag" ImageUrl="~/Profile/FlagsImages/cn.gif" ID="ImageButton2" runat="server"
        />
<asp:ImageButton CommandName="en-AU" OnCommand="Flag_ClickedCommand" AlternateText="English"
       CssClass="Flag" ImageUrl="~/Profile/FlagsImages/au.gif" ID="ImageButton1" runat="server"
        />

OK, note that I have setup an EventHandler for the OnCommand event, when this fires, Flag_ClickedCommand will be called and then culture that that particular flag represents will be passed on as CommandName.

protected void Flag_ClickedCommand(Object sender, CommandEventArgs args)
{
      if (!args.CommandName.IsValidCulture()) return;        
       Profile.Preferences.Culture = args.CommandName;
       Response.Redirect(Request.Path);        
}

Note what I set the Profile.Preferences.Culture to the command name passed on from the ImageButton, but this could well be a simple button or a dropdownlist value etc. I redirect the page to itself since if the user clicks on a flag, they'll expect the changes to take change immendiately! ( I would!)

I created an extension method for strings to check if the culture passed along was valid too. Not relevant to this post but since they are very neat I'll copy it here too. :-)

public static bool IsValidCulture(this string cult)
{
     if (Constants.ChineseCulture == cult
              || Constants.EnglishCulture == cult
              || Constants.SpanishCulture == cult)
     {
         return true;
     }
     return false;    
}

Setting the Thread.CurrentThread.CurrentCulture to your preferred culture

What we now want to do is for a page to load and pick the user's preferred culture.

I noted that you can override the InitializeCulture() method solely to intialise the page's culture. This function gets called very early in the page life cycle, well before and controls are created, meaning that if you want to get some values from controls, you must get them directly from the request using Form!

protected override void InitializeCulture()
{       
    string culture = Profile.Preferences.Culture;
    if (!culture.IsEmpty())
    {
        Thread.CurrentThread.CurrentUICulture = new CultureInfo(culture);
        Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(culture);
    }
}

Unfortunately you have to override this function for every page, not very practical if you have a zillion pages. Next best is to provide a base common class and then inherit from this class so all your webforms can benefit and you don't have to implement the above in every form. This works fine!

Note that the Thread culture is only set for the current page and will not affect your "other" pages, initially I thought that the CurrentThread's culture will be set to the new values and we'll all happy. But then I also thought what If a user has a different entry point to the application, so the need for all your pages to be able to read and set the user's fav culture.

public partial class LocalisationBase : System.Web.UI.Page
{
    protected override void InitializeCulture()
    {
        ProfileCommon common = HttpContext.Current.Profile as ProfileCommon;
        string culture = common.Preferences.Culture;

        if (!culture.IsEmpty())
        {
            Thread.CurrentThread.CurrentUICulture = new CultureInfo(culture);
            Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(culture);
        }
    }    
}
So base class it was and all my other pages inherited from this..
public partial class Profile_Default : LocalisationBase
{
    protected void Page_Load(object sender, EventArgs e)
    {
    }
...........

Now lets have a look at my initial UI that I wanted to localise and see if this works.

 englishUI

Just a simple form with some dates, numbers and a calendar what we hope we can localise and so the right names and formatting appear for the right culture setting. For the form labels I used resource files just like in my previous post. I have three resource files, one for english, one for spanish and one for mandarin.

The currency is interesting. All I have is a number where I call ToString("c")

private void FillPersonalisedMoney()
{
    lblMoney.Text = (500.23D).ToString("c");
}

Now when the user click on the Chinese flag...then the Chinese version of the page will render, dates will be rendered accordingly and the calendar will be in Chinese too..

chinaUI

And in Spanish too...

spanishUI

Clicking on Employee Details will take you to a page that also inherits from LocalisationBase and where Employees from ubiquitous Northwind will be displayed with to their likely annoyance, their date of birth, nicely formatted according to the culture of choice!

EmployeesDetails

EmployeesChineseDetails

And there you have it.

In conclusion I used the native Profile ASP.NET objects, you can implement your own Provider or you can use SQLProfileProvider that ASP.NET offers, either way I honestly think that these guys offer you much more flexibility and power when you want to customize your apps according to user's preferences.

Au revoir!

Resources:

#Must read for any localisation entrepreneurs

http://quickstarts.asp.net/QuickStartv20/aspnet/doc/localization/default.aspx

# ASP.NET 2.0 Localization (MSDN Article)

http://msdn2.microsoft.com/en-us/library/ms379546.aspx

#Profiles

http://msdn2.microsoft.com/en-us/library/at64shx3(VS.80).aspx

Saturday, March 29, 2008

Caching Awareness Day: Part 1

There are probably zillions of posts, tutorials etc around on how to do caching in ASP.NET. However a zillion+1 will not hurt. This post is more for sel-reference more than anything. I admit that sometimes I need to go over features I haven't used in a while (or ever?) to refresh my mind, this is one of those days.

In my current job we have an application that was suffering from a performance hangover when retrieving some records from WebServices and the db. I thought for a while how caching can improve the user experience of some of guilty pages.

After a few months in the thinking room waiting for the go ahead from the bosses, we got right into it. The idea was simple: when a particular record was requested from the services it was first stored in the Cache object and any subsequent request for that data will first check in the Application Cache to see if it's there, if there retrieve it, else go to the WebServices, get it, store it in the Cache etc etc. Needless to say the results where fantastic, the users are happy and they love their 'new' app. We are happy, but I kicked myself in the guts why didn't I do it in the first place!

Anyway, enough yarns. I plan to post a series on Caching and the different techniques that can be used to put some Caching love into your apps.

What is Caching?

Caching is all about storing data, objects, pages, part of pages etc in memory immediately after is requested from the original source. These objects are usually stored for example on the Web server, proxy servers or even the client browser. Think of the benefits, much faster to retrieve data from a proxy server 'nearby'  for example, where this data has been previously requested and stored,  than retrieving the data from probably a data source thousands of miles away and likely to be some data that was computationally expensive to generate and retrieve.

ASP.NET provides two types of caching:

  1. Output Caching, where you can store page and controls responses from the original server to the requesting browser.
  2. Data Caching, where you can programmatically store objects to the server's memory so that the application can fetch them from there rather than recreating them.

Caching ASP.NET Pages: Output Caching

Page output caching allows for subsequent requests for a given page to be provided from the cache so that the code that initially created the page does not execute. This is good for pages that are static and that you are confident are very frequently accessed pages. However if you have content on your page that is generated dynamically or has some dependency on a parameter or even a random image, then the same item will be displayed for the duration of the caching! beware! :-)

To enable OutputCaching, you add a <%@ OutputCache directive to a page. For example, in the page below I have cached this page for 10 seconds.

<%@ OutputCache VaryByParam="none" Duration="10" %>

The contents of your page will not be regenerated each time a user requests the page, also the class behind the page will not be executed during that time. This is the most basic kind of OutputCaching you can set up for a page, although very limited and only useful for very static pages.

Moving on, let's see all the properties that can be set for the OutputCache directive.

ouputIntellisense

Note that there is a property called 'VaryByParam". This one is very useful. For example, typical Master/Detail scenario, say you have a page where you show all Categories of Products in the Northwind database.

categories

..and then you wanted to display the Products for each category in  a different page, passing the CategoryID in the query string.

Capture2

Capture3 

Now if you did the above setup for your details page, then all your users will see whatever was cached first.

Enter VaryByParam. This helps you by making ASP.NET cache a new instance of the page when a different value for the CategoryID query string parameter is passed to the Products page.

<%@ OutputCache Duration="100" VaryByParam="CategoryID" %>

You can also vary the output cache by multiple parameters by separating the list of params by semicolon in the VaryByParam attribute ..

<%@ OutputCache Duration="100" VaryByParam="CategoryID;ProductID" %>

Also you can generate a new version of the page when any of the params change by assigning a  * to the attribute.

<%@ OutputCache Duration="100" VaryByParam="*" %>

More information in caching by param can be found in MSDN 

OutputCaching by browser Header

It is possible to also use the VaryByHeader attribute to create different versions of a page according to the browser header. For example, my Firefox browser sends the below set of headers with information.

Connection: keep-alive
Keep-Alive: 300
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-gb,en;q=0.5
Host: localhost
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13

My IE7 sends:

Connection: Keep-Alive
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-ms-application, application/vnd.ms-xpsdocument, application/xaml+xml, application/x-ms-xbap, application/x-silverlight, application/x-shockwave-flash, application/x-silverlight-2-b1, */*
Accept-Encoding: gzip, deflate
Accept-Language: en-AU,es-UY;q=0.7,zh-CN;q=0.3
Host: localhost
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SV1; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506; .NET CLR 3.5.21022)
UA-CPU: x86

You can check what your browser is sneaking out here or here ( as an aside, I heard that in the in 2001 a browser had some customer headers that were used to sneak information about that particular user...read it and see). In .Net I did the above by calling and then iterating the collection.

NameValueCollection headerCollection = Request.Headers;


So......
<%@ OutputCache Duration="1000" VaryByParam="none" VaryByHeader="Accept-Language" %>

That will cache a different page version according to the language used by the user.Right?

...Cache by Browser, getting introduced to the very handy VaryByCustom :-)

You can also cache by the user's browser by using VaryByCustom attribute and using the 'special' browser attribute. By using this attribute, the page will be cached for each mayor browser, its   name and version, eg IE7 and IE8 will result in two different cached versions.

<%@ OutputCache Duration="1000" VaryByParam="none" VaryByCustom="browser" %>

VaryByCustom is very handy. Basically what you can do is write a function in Global.asax or write an HttpModule to handle caching for pages in any way you want.

In Global.asax you do it by overriding the GetVaryCustomString() function.

public override string GetVaryByCustomString(HttpContext context, string custom)
{
     return base.GetVaryByCustomString(context, custom);
}
The function takes two parameters, the HttpContext for the request and a string which is the value you can set in VaryByCustom="[custom_string]", this string can be also be a nunber of string passed along separated by semicolon and you can then split it in GetVaryByCustomString() The context gives you access to everything you expect from HttpContext, session, Request etc etc, 
Example: Say you want to cache pages according to the bosses's mood. 
<%@ OutputCache Duration="1000" VaryByParam="none" VaryByCustom="BossHappinessLevel" %>
Then your custom function could look like..
  
public override string GetVaryByCustomString(HttpContext context, string custom)
{
    if (custom == "BossHappinessLevel")
    {
        MyAjaxEnabledService a = new MyAjaxEnabledService();
        return a.BossHappinessLevel().ToString();       
    }
    return "default";        
}

Based on the string passed, the logic will just return a string that can uniquely identify a request and thus cache a different version of the page according to the current mood! ( The above example doesn't make any sense if your boss is agro all the time, meaning that the "angry" page will be cached most of the time. Of course for this to make more sense, your page will return different content according to the mood too..if he/she is angry or happy or just indifferent. Please drop me an email if you don't get it, I need a drink now! LOL

Caching profiles in Web.Config

Instead of setting the cache policy for each page individually, you can configure page caching in the web.config and then apply your killer settings to lots of pages...

<system.web>
   <caching>
       <outputCacheSettings>
         <outputCacheProfiles>
               <add duration="1000" name="ByBossHappiness" varyByParam="none" varyByCustom="BossHappinessLevel" />
               <add duration="1000" name="ByBrowser" varyByCustom="browser" varyByParam="none" />
         </outputCacheProfiles>
       </outputCacheSettings>
   </caching>
</system.web>

Then your pages can recall any of the cacheprofiles by using the CacheProfile attribute of OutputCache

<%@ OutputCache CacheProfile="ByBossHappiness" %>

Next post:

In my next post I'd like to investigate how to do the above programmatically and also expire pages in code due to some event for example, if someone in the Northwind database adds a new Product by using the Reponse.RemoveOutputCacheItem() function.

Other things left to do include:
  • Partial Page caching.
  • UserControl caching.
  • Data caching
  • Caching by dependencies
  • SQL caching policies by using dependencies!

Wednesday, March 19, 2008

Localisation in ASP.NET: How to write multilingual applications

I been thinking of learning how to do this for a while now, number of reasons for that, my first language is not English, is Spanish, Uruguayan Spanish to be more precise, those that hear me from day to day can witness my struggles with English from time to time. Sometimes you wake up and words just flow, some other times, well.. that's not quite the case.

I always wondered how applications be developed that can then be easily extended to support other languages. Especially in this global village we live in today, in our Pacific region for example there are so many languages that is just mind blowing, from Oceania, South-East Asia, to Japan, the Koreas and even to the other side of the Pacific Ocean there is South and Central America.

Also by pure coincidance a couple of months we were asked to provide an English language version to a  charity website we have been maintaining at home for a number of years now called uruguayosunidos (I'm not responsible for a terrible spelling mistake on the homepage).  Problem is this site is just a plain html site where unfortunately we cannot use localisation. :-(

Enter ASP.NET 2.0 and resource files

One common approach in ancient times was to provide two versions of the same web, one for each language for example, one for Spanish another for English. This can clearly turn into a nightmarish scenario when maintaining the app, imagine!. The good news is that when creating an application with ASP.NET 2.0 or later you can use resource files to contain language specific content, eg one resx file can contain your French content, another can contain your English specific text etc.

ASP.NET supports two types of resources, local and global resources, local resources are scoped to a particular page while a global resource is scoped to the entire application. The equation is simple, if you want content specifically for a particular page, use local resources, now if you want the text that say what your 'Save' button should display through your whole app, then this should be in your global resource file.

To discover how this all works together I first put some labels and textboxes into an aspx page and got right into it. To get started, I just want to display a simple form where a user can enter her first name, last name and a button to submit the entries.

localisation_1

<table>
   <tr>
      <td><asp:Label ID="lblFirstName" runat="server" Text="Name"  /></td>
      <td><asp:TextBox ID="txtFirstName" runat="server"/></td>
      <td><asp:Label ID="lblLastName" runat="server" Text="Last Name" /></td>
      <td><asp:TextBox ID="txtLastName" runat="server"></td>
   </tr>
   <tr class="ButtonRow">
      <td colspan="4"><asp:Button ID="btnSave" runat="server" CssClass="Button" Text="Save" /></td>
   </tr>    
</table>

How to localise Page using Explicit Localisation Expressions:

So far nothing new. What I wanted to do was render those controls text's either in English, Spanish or Chinese according to the user's preferred language.

For that to happen first of all what I had to do was 'localise' the page, first I had to modify the controls to use resources's expressions. This expression is going to get the content from the resource files according to the user's language of choice. For example:

<asp:Label ID="lblFirstName" runat="server" Text='<%$ Resources:lblFirstName %>' />

The value of the Text property is now an "Explicit" resource expression, it is called Explicit because the property is set explicitly to a given value in the resource file for the preferred language.

Also you have to set the Page directive to include UICulture to auto, so when a user requests the page, a file matching her settings will be loaded automatically.

<%@ Page Language="C#" UICulture="auto"...

Associate a resource file to a Page

I created three resource files for this page and I then had to adde them to a special folder called App_LocalResources. You have to create this folder within the same folder as the page you want to localise.

Right-click where you want to add the folder and then Add ASP.NET folder->App_LocalResources,  once this is done, I was ready to add the resource files to the folder. To do this right click on the App_LocalResources --> Add New Item and selecting Resource File as shown below.

localisation_3

Important: To associate a resource file to a particular page, the naming convention is very very important.

It has to be in the format of page_Name.[Culture_Name].resx

See how above I have my Default.aspx page, I want to associate a resource file to deal with users who's language of choice is Spanish-Uruguayan (es-UY)

localisation_2

I added three files, a default resource file that is used in case none of the other resource files match the user's preference/settings (it is strongly recommended to always have a default resource file) and then the files for spanish and chinese simplified. Also note that in this example I have my Default.aspx page and that at the same level I added the App_LocalResources folder.

You can also add a 'neutral' culture resource file and name it in our case Default.aspx.en.resx, in this case the language is set to English without specifying the specific culture name as say AU for Australian, this is used if the user's preferred language is english but not say Australian english.

Resource Files:

Visual Studio has an editor for the resource files which enables you to enter name and value pairs as shown below..

localisation_4

Note that I have associated for my Uruguayan Spanish page the name of the properties to their corresponding values that I will then retrieve from my aspx page.

Resource files are nothing more than XML files that are then compiled by the framework into assemblies in the background! Open them in Notepad and see!

localisation_5

Once I 'localise' all the controls in the page, my aspx looked  like this.

<table>
    <tr>
       <td><asp:Label ID="lblFirstName" runat="server" Text='<%$ Resources:lblFirstName %>' /></td>
       <td><asp:TextBox ID="txtFirstName_Ex" runat="server"/></td>
       <td><asp:Label ID="lblLastName_Ex" runat="server" Text='<%$ Resources:lblLastName %>' /></td>
       <td><asp:TextBox ID="txtLastName_Ex" runat="server"/></td>
    </tr>
    <tr class="ButtonRow">
       <td colspan="4"><asp:Button ID="btnSave" runat="server" CssClass="Button" Text='<%$ Resources:btnSave %>' /></td>
    </tr>    
</table>

Now is up to the user's settings to see what gets loaded. I'm going to setup my preferred language in IE7 to Spanish and see what happens..

To do this: Tools->Internet Options -> Languages button at the bottom. I then added Spanish Uruguay and Moved it to the top to set it as my preferred language as shown below.

localisation_6

Now is all ready to go and loading the page should display my labels and button in Spanish.

localisation_7

And yeah! it worked, look how I associated the lblFirstName property in the Resource file:

 <asp:Label ID="lblFirstName" runat="server" Text='<%$ Resources:lblFirstName %>' />

localisation_8 

If I then set Chinese to my preferred language ( provided I have the Chinese culture resource file as shown below)

localisation_9

Thanks Jerry for the characters! I hope it means what we intend to ;-)

Then loading the page shows the correct content.

localisation_10

Using Implicit Localisation Expressions:

An alternative to explicit expressions is that you use implicit localisation which is very handy if you want to localise controls throughout your page with just one resource key.  The resource expression looks as follows:

<table>
   <tr>
     <td><asp:Label ID="lblFirstName" runat="server" Text="Name" meta:resourceKey="lblFirstName" /></td>
     <td><asp:TextBox ID="txtFirstName" runat="server"></asp:TextBox></td>
     <td><asp:Label ID="lblLastName" runat="server" Text="Last Name" meta:resourceKey="lblLastName" /></td>
     <td><asp:TextBox ID="txtLastName" runat="server"></asp:TextBox></td>
   </tr>
   <tr class="ButtonRow">
      <td colspan="4"><asp:Button ID="btnSave" runat="server" CssClass="Button" Text="Save" meta:resourceKey="btnSave" /></td>
   </tr>    
</table>

The labels and button include the meta:resourceKey property that represents a key in the local resource file. The entries are set to the Text property of the labels and button. I noted that you have to define all the meta resourceKey for each localisation resource file you have.

localisation_11

The advantages of using implicit localisation is that you can use the content for multiple controls, say you have 4+ 'Save' buttons through your page, all you need to do is associate a resource key to the controls. Also it is easier to localise existing apps, all that needs to be done is add meta:resourceKey="Property_Name" attributes  to your existing controls.

Note that is also possible to load resources programmatically by using GetLocalResourceObject(String name):Object and that you can also used it to format text, you can store values in the resource file such as "Welcome {0}!" and then at run time format the string as necessary. 

Global Resources:

While local resources are cool, their limitation is that they are relevant at a page level scope rather than at an application scope. A global resource can then be used through the app for you to setup the localised text. By convention, any resource that you want to be available to all your pages, controls etc should be in a global resource file.

To add global resource files you need to first add another special folder to your solution, this time the folder must be at the root of your application. To add it right-click on your application node --> Add ASP.NET folder --> App_GlobalResources.

Then you just add resource files as I did it for local page level, following the same naming convention as shown before for the language and culture.

Resource_File_Name.[Culture_Name].resx

localisation_12

I called this file Resource.es-UY.resx

Now the interesting thing comes when you want to retrieve the values from a global resource file. The sytnax is slightly different and it is as follows:

   <h2><asp:Literal ID="litWelcome" runat="server" Text='<%$ Resources:Resource,Welcome %>' /></h2>

With global resources you have to specify the name of the resource file and then the property you want to get to.

The result is:

localisation_13

It is also possible to retrieve values from global resources programmatically using GetGlobalResourceObject(string, string) method that takes two parameters, name of the global resource page and name of the property key you want to retrieve.

Because the .net framework converts global resources into compiled classes, you can use strongly typed expressions to retrieve values from the resource files. When you create a resource file, a new class is added to the Resource namespace for you to use! How good is that!!? The snippet  shown below with Visual Studio intellisense doing the damage for the above example. Wicked!

localisation_14

Last but not least there is the Localize control. It is used when you want to localise bigger pieces of text in your apps/pages. It works the same way as described above using meta:resourceKey at page level or using the global resource as in the last example. I can't see a super good use for it since it seems very similar to the Literal and Label controls. Jury still out.

<asp:Localize ID="elLocalisator" runat="server" Text='<%$ Resources:Resource,GoodMorning %>' />

That all!

This was a very interesting post to write, learned some tricks, there's still a lot to cover for example, how to format currency and DateTime according to culture, setting cultures programmatically or in web.config or page level. Maybe is there is interest we can do it later.

Hope it helps someone out there!

el#

Resources:

Tuesday, March 18, 2008

How to change IE7 default 'View Page Source' text viewer in Windows XP/Vista

I really love Notepad</grin>, I think it does a wonderful job with the really simple things, like a quick and dirty way of opening text files, web.configs (if you are in a rush only of course).

Now, if you are a web developer and say you want to see the source code of any given page or you might get a JavaScript error in line 1209 and want to have a look at it..then honestly NotePad does not cut it really.

I use Notepad++ at work http://notepad-plus.sourceforge.net/uk/site.htm and so far I'm a very happy chap.

 

So how do you change to say..Vi in Windows XP

  1. Download TweakUI from http://www.microsoft.com/windowsxp/downloads/powertoys/xppowertoys.mspx
  2. Start Tweak UI and update your view source editor under Internet Explorer | View Source, and then point it to the exe of your editor of choice.

 

But mate, what about Vista?

oh yeah!, TweakUI does not work in Vista so I read this IE7 blog entry in the msdn blogs

 

 http://blogs.msdn.com/ie/archive/2007/03/28/ie7-in-vista-configuring-your-view-source-editor.aspx

 

You can also download this mini-app to do it for you ( preferred choice, since I could not find the HKey described above on my Vista sys)

 

http://www.iconico.com/viewSourceEditor/  ( skip the registration!)

 

Worked very well.

 

Capture

WCF Services - Part 2

In the last post I tried to show how to use HTTP GET to get data back from my Services. Now I want to put my thoughts in order and show how I created new Employees and updated them using programmatic POST using the ubiquitous Northwind database.

My first attempts where at trying to update a record using values embedded in the querystring... easier said than done, it was quite an effort.. At then end it worked, it just required a shift in the way I was tackling the problem. I ended up having my contract accept a Stream parameter and then had to read the stream and get the values back.

 

My contract was:

//STREAM EXAMPLE
[OperationContract]
[WebInvoke(UriTemplate = "*", Method = "POST")]
void UpdateEmployeeName(Stream id);

My uri template was "*" since I wanted to have the querystring collection of values straight after the 'root' of my Service.svc

My implementation was very simple...

public void UpdateEmployeeName(Stream id)
 {
     StreamReader reader = new StreamReader(id);
     String res = reader.ReadToEnd();
     NameValueCollection coll = HttpUtility.ParseQueryString(res);
     
     int employeeID = Int32.Parse(coll["elid"]);
     String newName = coll["newName"];
     // Bless LinqToSql
     NorthwindDataContext cts = new NorthwindDataContext(ConnectionManager.GetHomeLocalConnectionString);
     Employee e = cts.Employees.Where(a => a.EmployeeID == employeeID ).Single();
     e.FirstName = newName;
     cts.SubmitChanges();
 }

Now, how the heck do I make a programmatic POST to my contract?

I just created a WebRequest and set the content type to form-urlencoded and set the request length to the length of my queryString collection.

//set the data
ASCIIEncoding enc = new ASCIIEncoding();
string datatext = "elid=" + elEmployeeID.Text;
datatext += "&newName=" + elEmployeeNewName.Text;

byte[] data = enc.GetBytes(datatext);

//HTTP POST query
WebRequest request = HttpWebRequest.Create("http://localhost/DemoWCF/Service.svc");
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = data.Length;

Stream datasteam = request.GetRequestStream();
datasteam.Write(data, 0, data.Length);
datasteam.Close();
WebResponse response = request.GetResponse();
 

To test it I threw a few textfields together and magic....

part2_1

 

And checking the db....

 

part2_2

 

I also wanted to create a new Employee using Javascript using a programmatic post as above but this time using Javascript Object Notation (JSON ) http://www.json.org/

 

My contract..

[OperationContract]
[WebInvoke(BodyStyle = WebMessageBodyStyle.Wrapped,
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json,
UriTemplate = "/CreateEmployee")]
void CreateEmployee(string FirstName, string LastName);
Implementation:
public void CreateEmployee(string FirstName, string LastName)
{
      NorthwindDataContext cts = new NorthwindDataContext();
      Employee e = new Employee();      
      e.LastName = LastName;
      e.FirstName = FirstName;
      cts.Employees.InsertOnSubmit(e);
      cts.SubmitChanges();           
}

 

To generate an HTTP POST call with JavaScript, I created a XMLHTTPRequest and then I just constructed the url, set the header content type to json and was careful to format my JSON pair's payload..

<asp:Button  ID="btnJSONCreateEmployee" runat="server" OnClientClick="doEmployeeCreate()"
function doEmployeeCreate()
{            
       var newFirstName = document.getElementById('TextBox1').value;            
       var newLastName = document.getElementById('TextBox2').value;
            
       var xmlHttp = new XMLHttpRequest();            
          
       // Create result handler 
       xmlHttp.onreadystatechange=function()
       {
          if(xmlHttp.readyState == 4)
          {
             document.getElementById("result").value = xmlHttp.responseText;
           }
       }
            
       var url = "Service.svc/CreateEmployee";
       var body = '{"FirstName": '+ '"' + newFirstName + '"';
       body += ', "LastName": ' + '"'+newLastName + '"' + '}';
            
       //Send the Http Request
       xmlHttp.open("POST", url, true);
       xmlHttp.setRequestHeader("Content-type", "application/json");
       xmlHttp.send(body); 
}

 

To create a JSON payload in C# is very simple, just format a string carefully, escaping the " and you'll be fine..

protected void btnCallService_Clicked(object sender, EventArgs args)
{
   ASCIIEncoding enc = new ASCIIEncoding();
   string employeeID = txtJsonEmpID.Text;
   string firstName = txtJsonFirstName.Text;
   string lastName = txtJsonLastName.Text;           
   string datatext = "{\"EmployeeID\":{0}, \"FirstName\":\"{1}\"  , \"LastName\":\"{2}\" }";
        
   datatext = datatext.Replace("{0}", employeeID)
                      .Replace("{1}",firstName)
                      .Replace("{2}", lastName);
               
   byte[] data = enc.GetBytes(datatext);

   WebRequest request = HttpWebRequest.Create("http://localhost/DemoWCF/Service.svc/CallJason");
   request.Method = "POST";
   request.ContentType = "application/json";
   request.ContentLength = data.Length;
   
   Stream datasteam = request.GetRequestStream();
   datasteam.Write(data, 0, data.Length);
   datasteam.Close();
   WebResponse response = request.GetResponse();
}

 

Ok, now unfortunately one of the new Employees didn't leave good comments in SVN and el# got a bit upset when he had a look at the logs. So let's fire the guy, tough choice yes, but then also we need to demo a programmatic HTTP DELETE.

part2_3 

Simple contract...I just specify the request format, the response format and the method that my client will be using..

 
[OperationContract]
[WebInvoke(BodyStyle = WebMessageBodyStyle.Wrapped,
    RequestFormat = WebMessageFormat.Json,
    ResponseFormat = WebMessageFormat.Json,
    UriTemplate = "/FireEmployee",
    Method="DELETE" )]
void FireEmployee(int EmployeeID);

Implementation is dead simple...this has got nothing to do with WCF, but I just post it here for completeness.

public void FireEmployee(int EmployeeID)
{
    NorthwindDataContext ctx = new NorthwindDataContext();
    Employee emp = ctx.Employees.Where(e => e.EmployeeID == EmployeeID).Single();
    ctx.Employees.DeleteOnSubmit(emp);
    ctx.SubmitChanges();  
}  

Calling using JavaScript is v. simple too..

function fireTheGuy()
{
    var employeeID  =  document.getElementById('txtToDeleteEmployeeID').value;
    var xmlHttp = new XMLHttpRequest();            
          
    // Create result handler 
    xmlHttp.onreadystatechange=function()
    {
        if(xmlHttp.readyState == 4)
        {
          document.getElementById("result").value = xmlHttp.responseText;
        }
    }
            
    // void CreateEmployee(string FirstName, string LastName);
    var url = "Service.svc/FireEmployee";
    var body = ' { "EmployeeID": ' +  employeeID + ' }';
            
    //Send the Http Request
    xmlHttp.open("DELETE", url, true);
    xmlHttp.setRequestHeader("Content-type", "application/json");
    xmlHttp.send(body);       
}
 

And trust me, it works like a charm.

Next post I would like to return complex types back to the client, as in Employee's objects etc..

 

Wednesday, March 12, 2008

REST with WCF

At the MS Heroes day, a few thursdays ago, our first session was on WCF and is fair to say a few of us were very impressed by what I saw and I just couldn't wait to recreate those presentations on my desk..

What is WCF?

From MSDN,

Is a set of .net Technologies for building and running connected systems. It is a new breed of communications infrastructure built around the Web services architecture. Advanced Web services support in Windows Communication Foundation provides secure, reliable, and transacted messaging along with interoperability. The service-oriented programming model of Windows Communication Foundation is built on the Microsoft .NET Framework and simplifies development of connected systems

 

What is REST ?

REST is a architectural model ( it is not a standard ) for distributed computing that defines how resources are distributed and addressed.

The acronym means Representational State Transfer, translated to mere mortals language it means that each unique URL is a representation of some data. It was term invented by Roy Fielding one of the principal authors of the HTTP protocol.

I will post resources and links later in the post. However Google for REST and you'll get heaps of results if you are curious. Definitions are boring! I like doing things. But you need to know a bit of the theory.

eg:

http://localhost/myWebService/Employee/2

http://localhost/myWebService/Employee

http://localhost/myWebService/Product/89

REST WebServices: An alternative to SOAP

All the above urls will return you different data ( a representation of the resource requested ) according to (of course) on how you map this URL to your contracts.  First link might be mapped to return you the Employee with ID =2 and so on, or the Product with ID = 89

Basic principles

  1. Identify the entities you want to expose a representation eg Employee, Product
  2. Create URL for each resource/entity. Resources should be nouns.
  3. Categorise resources according to whether clients can just get a representation of your data or whether they can modify the data. For resources that you just want to make available to clients, make them accessible using HTTP GET, for resources that you want to give the client the possibility to modify data, make them accessible using HTTP POST, PUT or DELETE
  4. Resources accessible via GET should be 'side effect' free. This means the web service call should not modify the data in the back end.

Yahoo and Flickr amongst others use REST web services. Example of Flickr REST web service can be seen here

Main advantages of using REST

  • Easy to work with, and easy to read for 'humans'.
  • Easy to consume ( not always, I hear Amazon services can be a pain to parse )
  • Improved server response times.

 

I started working with this on the weekend after Microsoft Heroes in Sydney. At the time I challenged myself not to Google for answers and get the examples working only with the help of MSDN for WCF.

It was not easy at first, MSDN is huge and it throws so much information at you, if you are not careful you start losing track of what link you open where and soon you lose track of what you are reading, at least I do!

Needless to say it wasn't that easy. I find it interesting to see how much developers including myself sometimes google problems searching for tailor-made answers. Imagine a world without Google? Is mindblowing to think how older programmers looked for answers 20 years ago, maybe things where simpler those days. Hrmm..

Ok, so lets get started, first of all I went NewSite -> WCF Service. Visual Studio will create a simple solution with an IService and a Service class and also a Service.svc file.

First of all lets look at the IService interface is provided by default.

[ServiceContract]
public interface IService
{

    [OperationContract]
    string GetData(int value);

    [OperationContract]
    CompositeType GetDataUsingDataContract(CompositeType composite);

    // TODO: Add your service operations here
}

FIrst thing you do is define a contract. You do this by adding the [ServiceContract]  attribute to your interface.  This contract will define what operations your webservice will ultimately support. Think of an operation as a method call that your service will expose. Each method in the interface that you want to expose, must then have the [OperationContract]  attribute defined / applied to it. If a method does not have this attribute, it won't be exposed therefore it wont be accessible by the service's clients.

We then have to implement this interface and implement each method that we defined in our ServiceContract.

I want to expose a method by a REST WebService to return an Employee Name representation when the URL is "GetEmployeeName/{id}".

Remember that if we just want a representation of our data without any side effect by the REST principles we have to use the HTTP GET verb. WCF Web model allows you to do this by using the WebGet method attribute for Get operations. All you have to do is adorn your method with WebGet.

For that to happen I have to define  my operation contract in the interface as follows:

[OperationContract]
[WebGet(UriTemplate= "GetEmployeeName/{id}", ResponseFormat = WebMessageFormat.Xml)]
stringGetEmployeeName(Stringid);

In the WebGet attribute we can also describe the layout of the URL that our WebService will respond to. For example I want the method above to be called upon to answer the GetEmployeeFirstName call when the URL is MyService.svc/GetEmployeeName/2

We do this by adding a UriTemplate property to our WebGet attribute. This templates allow you to match an incoming URL to an operation in the service. The ResponseFormat attribute is self explanatory, you can choose XML or JSON.

Now we have to implement our GetEmployeeeFirstName method in our class.

public class Service : IService
{

    public string GetEmployeeName(string id)
    {
        NorthwindDataContext ct = new NorthwindDataContext();
        return ct.Employees.Where(a => a.EmployeeID == Int32.Parse(id)).Select(a => a.FirstName + " " + a.LastName).Single();
    }
}

I'm using LinqToSql again to retrieve employees from the Northwind database.

We are almost ready to go now. I had my biggest struggle with the web.config and configuring behaviours and endpoints. I wasn't the only one that struggled, for what I can gather around blogs etc and the poor MSDN documentation this wasn't that straightforward.

I settled for the config settings below...

<system.serviceModel>
        <bindings>
            <webHttpBinding>
                <binding name="ServiceBehavior"></binding>                
            </webHttpBinding>        
        </bindings>
        <services>
            <service name="Service" behaviorConfiguration="ServiceBehavior">
                <!-- Service Endpoints -->
                <endpoint address=""  binding="webHttpBinding" behaviorConfiguration="ServiceBehavior" contract="IService"></endpoint>
                <endpoint contract="IService" address="mex" binding="mexHttpBinding"></endpoint>                
            </service>            
        </services>
        <behaviors>
            <endpointBehaviors>
                <behavior  name="ServiceBehavior">
                    <webHttp />                     
                </behavior>                
            </endpointBehaviors>
            <serviceBehaviors>
                <behavior name="ServiceBehavior">
                     To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment 
                    <serviceMetadata httpGetEnabled="true" />
                                     To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information 
                    <serviceDebug includeExceptionDetailInFaults="true"/>
                </behavior>  
            </serviceBehaviors>
       </behaviors>
</system.serviceModel>

Since I'm using the WCF web model and all my requests are through HTTP and not SOAP I have to use webHttpBinding .

From MSDN..

The WCF Web Programming Model allows developers to expose WCF Web services through HTTP requests that use "plain old XML" (POX) style messaging instead of SOAP-based messaging. For clients to communicate with a service using HTTP requests, an endpoint of the service must be configured with the WebHttpBinding that has the WebHttpBehavior attached to it. The WCF Web Programming Model also requires that the individual service operations are annotated with the WebGetAttribute or WebInvokeAttribute attributes. This defines a mapping from a URI and HTTP method to the service operation, as well as the format of the messages used to call the operation and return the results. Support in WCF for syndication and ASP.AJAX integration are both built on top of the WCF Web Programming Model.

SO far so good, Now if we run our WCF app and we invoke the URL with GetEmployeeName/2 the employee name should be returned...

http://localhost/DemoWCF/Service.svc/GetEmployeeName/2

Capture

http://localhost/DemoWCF/Service.svc/GetEmployeeName/3

Capture2

(BTW Vista Snipping Tool kicks bum big time!!!) ( David, I got to talk you into Vista for blogging Windows Live Writer Full, is awesome)

Lets say I know want to get all the Employees Names by going to an url like this

http://localhost/DemoWCF/Service.svc/Employees

[OperationContract]
[WebGet(UriTemplate = "Employees", ResponseFormat = WebMessageFormat.Xml)]
List<String> GetEmployeesNames();

My implementation of the operation would be something along the lines of..

public List<String> GetEmployeesNames()
{
    NorthwindDataContext ct = new NorthwindDataContext();
    Enumerable<String> ab = from emps in ct.Employees
                            select emps.FirstName + " " + emps.LastName;
    return ab.ToList();
}

And the result would be..

Capture3 

Now if I wanted the Response to be of

WebMessageFormat.Json

I would then have to change my OperationContract to note my intention as follows

[OperationContract]
[WebGet(UriTemplate = "Employees", ResponseFormat = WebMessageFormat.Json)]
List<String> GetEmployeesNames();

When I then go to the URI, you will then get prompted to save the response as a file...

image 

that upon opening with trusty Notepad you can see the JSON string...

Capture5 

In this post I just wanted to show how I got my REST services to work and I then plan to post about how to use PUT, POST and DELETE , how to return complex types, DataContracts and also how to pass querystring values to your OperationContract as a Stream, that in fact was the first thing that I got working. ( Quick preview below). I would also like to post all the source code of my working examples.

[OperationContract]
[WebInvoke(UriTemplate = "*", Method = "POST")]
void UpdateEmployeeName(Stream id);
Implementation
public void UpdateEmployeeName(Stream id)
{
   StreamReader reader = new StreamReader(id);
   String res = reader.ReadToEnd();
   NameValueCollection coll = HttpUtility.ParseQueryString(res);
   int employeeID = Int32.Parse(coll["elid"]);
   String newName = coll["newName"];
   NorthwindDataContext cts = new NorthwindDataContext();
   Employee e = cts.Employees.Where(a => a.EmployeeID == employeeID ).Single();
   e.LastName = newName;
   cts.SubmitChanges();
}
See you then.........