Monday, August 18, 2008

ASP.NET Localization

There are a lot of articles talking about ASP.NET localization. This article is not going to give you an in-depth look of ASP.NET localization. Instead, it gives you a quick reference about localization of the commonly used content on an ASP.NET page including ASP.NET server control, HTML content, SiteMap and other resources.

To download sample application, please go to CodeProject

How to localize ASP.NET server control?

ASP.NET server control localization is the easiest of all. Once you added it to your page, you can simply switch your page to “Design” mode and then go to menu “Tools”->”Generate Local Resource”.

clip_image002

It will generate resource string for each ASP.NET server control on that page. In this example, a file with name Default.aspx.resx was created. It contains all the resource name/value pair for our Default.aspx page.

clip_image004      clip_image005

Switch back to source pane, you will see it added some code like the following in your HTML.       

<asp:Button ID="Button1" runat="server" Text="Hello" meta:resourcekey="Button1Resource1" />





You then can copy/paste to create resource file for other culture. For example, you may create Default.aspx.fr.resx for French user.



clip_image007  clip_image008



The following screen shot is when the language is set to English in Internet Explorer.



clip_image010



Change the language to French by going to Internet Explorer->Tools->Internet Options->Languages



clip_image012



Here is the French version page



clip_image014









How to localize HTML content?



To localize the regular HTML content, you can use <asp:Localize> control. Let’s use an example to explain it.



You have a header and a paragraph in your page.





<h1>Localization Page Header</h1>
<p>This is a demo page to show you how to do localization in ASP.NET</p>









 





To localize it, you need to add <asp:Localize> to them.




<h1><asp:Localize ID="Header" runat="server">Localization Page Header</asp:Localize></h1>
<p><asp:Localize ID="Localize1" runat="server">This is a demo page to show you how to do localization in ASP.NET</asp:Localize></p>







 





Then you may manually create a resource file and add meta:resourcekey="HeaderResource1” to your page. Or you can take advantage of Visual Studio to automatically generate it. Switch your page to “Design” mode. Go to menu “Tools”->”Generate Local Resource”.



 It will generate the following code and the resource file (i.e. yourfile.aspx.resx) for you.



Here is the code it changed:



 







<h1><asp:Localize ID="Header" runat="server" meta:resourcekey="HeaderResource1" Text="Localization Page Header"></asp:Localize></h1>
<p><asp:Localize ID="Localize1" runat="server" meta:resourcekey="Localize1Resource1" Text="This is a demo page to show you how to do localization in ASP.NET"></asp:Localize></p>







The rest steps are as same as how would you localize ASP.NET server control.








 





How to localize site map?



 This article will give you more detail on SiteMap localization.



1.       Enable site map localization by adding enableLocalization="true" to site map file, for example: Web.sitemap file




<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" enableLocalization="true">



2.       Change the value of the property that you want to localize to a resource string in siteMapNode using this format “$resources:ClassName,KeyName,DefaultValue”




<siteMapNode url="Default.aspx" title="$resources:SiteMapLocalizations,HomePageTitle" description="$resources:SiteMapLocalizations,HomePageDescription">



 



3.       Add global resource folder and files. Right click the web site in solution explorer. Click on “Add ASP.NET folder”. Click on “Add App_GlobalResources” from the sub-menu. It will add an “App_GlobalResources” folder to your root directory.



clip_image016



Then add resource file, for example, SiteMapLocalizations.resx. In the file, you will have name/value pair for each of the resource string. For example, 



Name                                    Value



HomePageTitle                 Home





 





4.       Create resource file for each culture you may have. For example, you may have a file called SiteMapLocalizations.fr.resx for French.



Below are the screen shot of menus in English and French



   clip_image018




How to localize string programmatically?



Sometimes you may need to display a string, for example, an error message at the run time. You will need to localize it programmatically. Here is how to do it:



1.       Add resource file under App_GlobalResources folder. In this example, we added a file called “CommonResource.resx” and a French version “CommonResource.fr.resx”



clip_image020   clip_image021



2.       Add the following code to use that resource file




ResourceManager rm = new ResourceManager("Resources.CommonResource", Assembly.Load("App_Glob alResources"));
this.Label1.Text = rm.GetString("HelloString");



 


After posting this on CodeProject, I received a comment from Alexander Nesterenko indicating there are better ways to get the resource string. Here is his comment:



You should get resources from ~\App_GlobalResources\MyMessages.resx by:

1. Generated code wrapper - string message = Resources.MyMessages.Hello;


2. Resource expression - <asp:Label Text="<%$ Resources: MyMessages, Hello %>" />


3. Method GetGlobalResourceObject - string message = GetGlobalResourceObject("MyMessages", "Hello");


You should get resources from ~\App_LocalResources\default.aspx.resx by:


1. Resource expression - <asp:Label Text="<%$ Resources: Hello %>" />


2. meta:resourceKey - <asp:Label meta:resourceKey="labelResourceKey" />


3. Method GetLocalResourceObject - string message = GetLocalResourceObject("Hello");




Thanks, Alexander.




Here are the screen shots:


clip_image023





 






How to dynamically change culture?



In the sample code, we added two LinkButtons. When you click on “English”, the page will switch to English culture; click on “Français”, it will switch to French. To detect which link button is clicked, we used the Request.Form[“__EventTarget”]. It works, but probably not the best way.



We override the “InitializeCulture” method of the page, so it will display the correct culture based on the selection we made.



HTML code:




<asp:LinkButton ID="LanguageEnglish" Text="English" runat="server"></asp:LinkButton>
<asp:LinkButton ID="LanguageFrench" Text="Français" runat="server"></asp:LinkButton>





ASP.NET code




protected override void InitializeCulture()
{
string language = Request.Form["__EventTarget"];
string languageId = "";

if (!string.IsNullOrEmpty(language))
{
if (language.EndsWith("French")) languageId = "fr-FR";
else languageId = "en-US";
Thread.CurrentThread.CurrentCulture =
CultureInfo.CreateSpecificCulture(languageId);

Thread.CurrentThread.CurrentUICulture = new CultureInfo(languageId);
}
base.InitializeCulture();
}





clip_image025






This works for everything except the menu. If you have a menu using SiteMap in your page. You may need to call menu.DataBind() in Page_Load event or set EnableViewState to false. Otherwise your menu will still show the previous culture string while other content change to the new culture.



When we dynamically change the culture, most likely we need to store it somewhere so it won’t get lost when we jump from page to page. In the sample application, we used Session to do that.



Happy Programming!

Drag and Drop in ASP.NET

First, I want to say I am not a JavaScript expert. The last time I used JavaScript was 7 years ago. Recently I started working on a web site which needs to allow user drag and drop items from a list. I did a lot of research and found some very useful articles. They helped me to implement this feature on my site.

These articles are

http://aspalliance.com/1300_Custom_Client_Side_Drag_and_Drop_Behavior_in_ASPNET_AJAX

http://www.codeproject.com/KB/ajax/JigsawPuzzleGame.aspx

http://msdn.microsoft.com/en-us/magazine/cc135985.aspx

Although they are very helpful, my scenario is not exactly the same. I want to be able to drag an item, drop to an area and then drag/drop it again later. I tried to mimic the Jigsaw Puzzle sample. However, since my item is not an image, only a string, I got lost during the re-writing. Finally I figured out my own solution.

Below is the screen shot of my sample. You can download the sample code from here.

clip_image002

User can drag/drop an item from the list on top to the “Drop it here” area. User can also move the items between grids. Finally, user can remove an item by drag/drop it to the Garbage Can area.

Let’s see how we implement this by using ASP.NET AJAX and Javascript:

Before you start, you will need to download the ASP.NET 3.5 Extension Preview:

http://www.microsoft.com/downloads/details.aspx?FamilyId=A9C6BC06-B894-4B11-8300-35BD2F8FC908&displaylang=en

and ASP.NET Future

http://www.asp.net/downloads/futures/default.aspx?wwwaspnetrdirset=1

  • Add reference to “Microsoft.Web.Preview for .NET 3.5” in your project.

  • Add ScriptManager to the page since we are using AJAX

  • Add ScriptReference

<asp:ScriptManager runat="server" ID="ScriptManager1">
<Scripts>
<asp:ScriptReference Name="PreviewScript.js" Assembly="Microsoft.Web.Preview" />
<asp:ScriptReference Name="PreviewDragDrop.js" Assembly="Microsoft.Web.Preview" />
</Scripts>
</asp:ScriptManager>





  • Create a page with the following html elements







<div id="dragItem1">Item 1</div><br />
<div id="dragItem2">Item 2</div><br />
<div id="dragItem3">Item 3</div><br />
<div id="dragItem4">Item 4</div><br />
<div>
<table border="1" id="dragDropZone">
<tr>
<td width="100" height="100" id="target1">Drop it here</td>
<td width="100" height="100" id="target2">Drop it here</td>
<td width="100" height="100" id="target3">Drop it here</td>
<td width="100" height="100" id="target4">Drop it here</td>
</tr>
<tr><td colspan="4" height="100" id="garbageCan">Garbage Can</td></tr>
</table>
</div>










  • Create DragDrop.js. I posted the whole file here. Please see comments for explaination.





Type.registerNamespace('DotNetIdeas.Ajax.UI');

//ItemDragSourceBehavior
DotNetIdeas.Ajax.UI.ItemDragSourceBehavior = function(element, item, parent, target)
{
DotNetIdeas.Ajax.UI.ItemDragSourceBehavior.initializeBase(this, [element]);
this._mouseDownHandler = Function.createDelegate(this,
this.mouseDownHandler);
this._item = item;
this._visual = null;
this._parent = parent; // Parent control which hold the drag items and drop targes
this._target = target; // current target the drag item is on
}

DotNetIdeas.Ajax.UI.ItemDragSourceBehavior.prototype =
{
// IDragSource methods
get_dragDataType: function() {
return 'DragDropItem';
},

getDragData: function(context) {
return this._item;
},

get_dragMode: function() {
return Sys.Preview.UI.DragMode.Move;
},

//The following two functions are for FireFox, so it won't select the text when drag starts
returnFalse: function() { return false; },
returnTrue: function() { return true; },

onDragStart: function() {
document.onmousedown = this.returnFalse;
// Set the leaving target, so we can remove this item from it later
if (this._parent) this._parent.setLeavingTarget(this._target);
},

onDrag: function() { },

onDragEnd: function(canceled) {
// If drag from the outside of the dragdrop control, then there will be a shadow when moving the item,
// so we need to remove it when drag is end
if (this._visual && this.get_element().parentNode)
this.get_element().parentNode.removeChild(this._visual);
document.onmousedown = this.returnTrue;
},

// Other methods
initialize: function() {
DotNetIdeas.Ajax.UI.ItemDragSourceBehavior.callBaseMethod(this,
'initialize');
$addHandler(this.get_element(), 'mousedown',
this._mouseDownHandler)
},

mouseDownHandler: function(ev) {
window._event = ev; // Needed internally by _DragDropManager

this._visual = this.get_element().cloneNode(true);
this._visual.style.opacity = '0.4';
this._visual.style.filter =
'progid:DXImageTransform.Microsoft.BasicImage(opacity=0.4)';
this._visual.style.zIndex = 99999;

this.get_element().parentNode.appendChild(this._visual);
var location =
Sys.UI.DomElement.getLocation(this.get_element());
Sys.UI.DomElement.setLocation(this._visual, location.x,
location.y);

Sys.Preview.UI.DragDropManager.startDragDrop(this,
this._visual, null);

},

dispose: function() {
if (this._mouseDownHandler)
$removeHandler(this.get_element(), 'mousedown',
this._mouseDownHandler);
this._mouseDownHandler = null;
DotNetIdeas.Ajax.UI.ItemDragSourceBehavior.callBaseMethod(this,
'dispose');
}
}

DotNetIdeas.Ajax.UI.ItemDragSourceBehavior.registerClass
('DotNetIdeas.Ajax.UI.ItemDragSourceBehavior', Sys.UI.Behavior,
Sys.Preview.UI.IDragSource);


//ItemDropTargetBehavior
DotNetIdeas.Ajax.UI.ItemDropTargetBehavior = function(element, parent)
{
DotNetIdeas.Ajax.UI.ItemDropTargetBehavior.initializeBase(this, [element]);
this._items = new Array();
this._parent = parent;
this.targetType = "Target";
}

DotNetIdeas.Ajax.UI.ItemDropTargetBehavior.prototype =
{
// IDropTarget methods
get_dropTargetElement: function()
{
return this.get_element();
},

canDrop: function(dragMode, dataType, data)
{
return (dataType == 'DragDropItem' && data);
},

drop: function(dragMode, dataType, data)
{
if (dataType == 'DragDropItem' && data)
{
if (this.targetType != 'GarbageCan')
{
// If the item already exists in this target area, do not add it again.
for(var id in this._items)
{
if (this._items[id] == data) return;
}

// Add new item to item list in this target area
var newId = this._items.length;
this._items[newId] = data;
var itemBuilder = new Sys.StringBuilder();
var targetId = this.get_element().id.toString();

// Re-generate the HTML string for the items
for (var id in this._items) {
itemBuilder.append("<div id='droppedItem_" + targetId + "_" + id.toString() + "'>" );
itemBuilder.append(this._items[id]);
itemBuilder.append("</div>");
}
this.get_element().innerHTML = itemBuilder.toString();

// Create the draggable items
for (var id in this._items) {
var itemId = "droppedItem" + "_" + targetId + "_" + id.toString();
var droppedItem = new DotNetIdeas.Ajax.UI.ItemDragSourceBehavior($get(itemId), $get(itemId).innerHTML, this._parent, this);
droppedItem.initialize();
}
}

// Remove this item from its previous target
var leavingTarget = this._parent.getLeavingTarget();
if (leavingTarget)
{
leavingTarget.removeItem(data);
}
this._parent.setLeavingTarget(null);
}
},

onDragEnterTarget: function(dragMode, dataType, data)
{
// Highlight the drop zone by changing its background
// color to light gray
if (dataType == 'DragDropItem' && data)
{
this._color = this.get_element().style.backgroundColor;
this.get_element().style.backgroundColor = '#E0E0E0';
}
},

onDragLeaveTarget: function(dragMode, dataType, data)
{
// Unhighlight the drop zone by restoring its original
// background color
if (dataType == 'DragDropItem' && data)
{
this.get_element().style.backgroundColor = "White";
}
},

onDragInTarget: function(dragMode, dataType, data) {},

// Other methods
initialize: function()
{
DotNetIdeas.Ajax.UI.ItemDropTargetBehavior.callBaseMethod(this,
'initialize');
Sys.Preview.UI.DragDropManager.registerDropTarget(this);
},

dispose: function()
{
Sys.Preview.UI.DragDropManager.unregisterDropTarget(this);
DotNetIdeas.Ajax.UI.ItemDropTargetBehavior.callBaseMethod(this,
'dispose');
},

// Remove items from target area
removeItem : function(data)
{
// Remove item from the item list
var j = 0;
while (j < this._items.length) {
if (this._items[j] == data) {
this._items.splice(j, 1);
}
else {
j++;
}
}

// Re-generate HTML for the items
var itemBuilder = new Sys.StringBuilder();
var targetId = this.get_element().id.toString();
for (var id in this._items) {
itemBuilder.append("<div id='droppedItem_" + targetId + "_" + id.toString() + "'>" );
itemBuilder.append(this._items[id]);
itemBuilder.append("</div>");

}
this.get_element().innerHTML = itemBuilder.toString();

// Re-create the draggable items
for (var id in this._items) {
var itemId = "droppedItem" + "_" + targetId + "_" + id.toString();
var droppedItem = new DotNetIdeas.Ajax.UI.ItemDragSourceBehavior($get(itemId), $get(itemId).innerHTML);
droppedItem.initialize();
}
}
}

DotNetIdeas.Ajax.UI.ItemDropTargetBehavior.registerClass
('DotNetIdeas.Ajax.UI.ItemDropTargetBehavior', Sys.UI.Behavior,
Sys.Preview.UI.IDropTarget);


//DragDrop Zone. It is used as an container of the drop target areas. It is the parent of all the targets.
//When one item is moved from one target to another. It will call setLeavingTarget() to store the previous target,
//so when the item is drop, it knows to remove it from the previous target.
DotNetIdeas.Ajax.UI.DragDropZone = function(element)
{
DotNetIdeas.Ajax.UI.DragDropZone.initializeBase(this, [element]);
this._leavingTarget = null;
}

DotNetIdeas.Ajax.UI.DragDropZone.prototype =
{
initialize : function()
{
DotNetIdeas.Ajax.UI.DragDropZone.callBaseMethod(this, "initialize");
this._renderControl();
},

dispose : function()
{
DotNetIdeas.Ajax.UI.DragDropZone.callBaseMethod(this, "dispose");
},

_renderControl : function()
{
// Create drag/drop targets
var target1 = new DotNetIdeas.Ajax.UI.ItemDropTargetBehavior($get('target1'), this);
var target2 = new DotNetIdeas.Ajax.UI.ItemDropTargetBehavior($get('target2'), this);
var target3 = new DotNetIdeas.Ajax.UI.ItemDropTargetBehavior($get('target3'), this);
var target4 = new DotNetIdeas.Ajax.UI.ItemDropTargetBehavior($get('target4'), this);
var garbageCan = new DotNetIdeas.Ajax.UI.ItemDropTargetBehavior($get('garbageCan'), this);
target1.initialize();
target2.initialize();
target3.initialize();
target4.initialize();
garbageCan.initialize();
garbageCan.targetType = "GarbageCan";
},

setLeavingTarget : function(leavingTarget)
{
this._leavingTarget = leavingTarget;
},

getLeavingTarget : function()
{
return this._leavingTarget;
}
};

DotNetIdeas.Ajax.UI.DragDropZone.registerClass("DotNetIdeas.Ajax.UI.DragDropZone", Sys.UI.Control);

///////////////////////////////////////////////////////////////////////
// Script registration

Sys.Application.notifyScriptLoaded();









  • Add ScriptReference for DragDrop.js









<asp:ScriptReference Path="~/DragDrop.js" />











  • Add following code to header section





<script type="text/javascript">
function pageLoad() {
var source1 = new DotNetIdeas.Ajax.UI.ItemDragSourceBehavior($get('dragItem1'), $get('dragItem1').innerHTML);
var source2 = new DotNetIdeas.Ajax.UI.ItemDragSourceBehavior($get('dragItem2'), $get('dragItem2').innerHTML);
var source3 = new DotNetIdeas.Ajax.UI.ItemDragSourceBehavior($get('dragItem3'), $get('dragItem3').innerHTML);
var source4 = new DotNetIdeas.Ajax.UI.ItemDragSourceBehavior($get('dragItem4'), $get('dragItem4').innerHTML);
source1.initialize();
source2.initialize();
source3.initialize();
source4.initialize();

var dragDropZone = new DotNetIdeas.Ajax.UI.DragDropZone($get('dragDropZone'));
dragDropZone.initialize();
}
</script>