Archive for the ‘ASP.NET’ Category

Wishlist for Velocity (CPT4?)

I went to Rob’s presentation on Velocity tonight, it’s a great presentation and a lot of information shared with audience.

I prepare a wishlist for Velocity (CPT4 or next beta release) and hope the Velocity team can take my wishlist into consideration.

  1. A new method: bool DataCache.ContainsRegion(string region).
    • This method checks if the region exists in the named cache or not.
    • See my previous post for details.
  2. Update the method DataCache.GetObjectsByAnyTag(List<DataCacheTag> tags, string region) so the keys of returned cache items are unique.
    • Make sure the cache key is not contained in the keys, then add the item into the collection.
    • See my previous post for example.
  3. Make sure the default region name is not arbitratry.
    • DataCache.GetCacheItem Method – Gets a DataCacheItem object to retrieve all information associated with your cached object in the cluster.
    • DataCache.GetCacheItem (String) -> Gets a DataCacheItem object to retrieve all information associated with your cached object in the cluster.
    • DataCache.GetCacheItem (String, String) -> Gets a DataCacheItem object to retrieve all information associated with your cached object in the cluster. For objects stored in regions.
    • When using the DataCache.GetCacheItem (String) method (without region specified), the return object’s property -RegionName – is kind of arbitrary.
  4. A new commad for administrators to create region in PowerShell.
    • New-CacheRegion [-CacheName] <CacheName> [-RegionName] <RegionName>
    • Example 1:
      • New-CacheRegion -CacheName Products -RegionName MyRegion -> this creates a region “MyRegion” in the named cache “Products”.
    • Example 2:
      • New-CacheRegion -RegionName MyRegion -> this creates a region “MyRegion” in the default cache “default”

Velocity (CTP3) – DataCache.GetObjectsByAnyTag Method

I was writing some code to experiment the Tag search in Velocity CTP3 and I found there is some small bug in one of the get objects by tag methods – DataCache.GetObjectsByAnyTag.

The definition of the method of DataCache.GetObjectsByAnyTag is:

Gets an enumerable list of all cached objects in the specified region that have any of the same tags in common.

For example I have 3 cache items:

Item 1: key = “111″, content = “aaa”, tag = “a”

Item 2: key = “222″, content = “bbb”, tag = “b”

Item 3: key = “333″, content = “ccc”, tag = “a,b,c”

Here are some scenarios:
(1) If I search with Tag “a” via DataCache.GetObjectsByAnyTag(<a>, “my_region”), I get:
Item 1: key = “111″, content = “aaa”, tag = “a”
Item 3: key = “333″, content = “ccc”, tag = “a,b,c”
(2) If I search with Tag “b” via DataCache.GetObjectsByAnyTag(<b>, “my_region”), I get:
Item 2: key = “222″, content = “bbb”, tag = “b”
(3) But if I search with Tags “a,b” via DataCache.GetObjectsByAnyTag(<a, b>, “my_region”), I get:
Item 1: key = “111″, content = “aaa”, tag = “a”
Item 3: key = “333″, content = “ccc”, tag = “a,b,c”
Item 3: key = “333″, content = “ccc”, tag = “a,b,c”
Item 2: key = “222″, content = “bbb”, tag = “b”

Here are some scenarios:

(1) If I search with Tag “a” via DataCache.GetObjectsByAnyTag(<a>, “my_region”), I get:

Item 1: key = “111″, content = “aaa”, tag = “a”

Item 3: key = “333″, content = “ccc”, tag = “a,b,c”

(2) If I search with Tag “b” via DataCache.GetObjectsByAnyTag(<b>, “my_region”), I get:

Item 2: key = “222″, content = “bbb”, tag = “b”

Item 3: key = “333″, content = “ccc”, tag = “a,b,c”

(3) But if I search with Tags “a,b” via DataCache.GetObjectsByAnyTag(<a, b>, “my_region”), I get:

Item 1: key = “111″, content = “aaa”, tag = “a”

Item 2: key = “222″, content = “bbb”, tag = “b”

Item 3: key = “333″, content = “ccc”, tag = “a,b,c”

Item 3: key = “333″, content = “ccc”, tag = “a,b,c”

In scenario (3), the item 3 has been added twice because its tags are “a,b,c” and I search by <a,b> (Any Tag).

I believe this method - DataCache.GetObjectsByAnyTag - should check if the cache key is contained in the collection, if not, then add it to the collection – unless the velocity team want the developers do the checking of duplicated key in the caller program.

I will list my code and some screenshots.

The first file is CacheWorker.cs:

using System;

using Microsoft.Data.Caching;

namespace VelocityTest

{

public class CacheWorker

{

public const string DEFAULT_CACHE_REGION = “Default_Cache_Region”;

private static object _synclock = new object();

private static CacheWorker _cacheWorker = null;

private static DataCacheFactory _cacheFactory = null;

private static DataCache _defaultCache = null;

private static DataCache _eventCache = null;

private CacheWorker()

{

}

public static CacheWorker Instance

{

get

{

lock (_synclock)

{

try

{

if (_cacheWorker == null)

{

_cacheWorker = new CacheWorker();

// we use configuration file

_cacheFactory = new DataCacheFactory();

// default cache

_defaultCache = _cacheFactory.GetDefaultCache();

_defaultCache.RemoveRegion(DEFAULT_CACHE_REGION);

_defaultCache.CreateRegion(DEFAULT_CACHE_REGION, true);

// names cache

_eventCache = _cacheFactory.GetCache(“Event2″);

}

return _cacheWorker;

}

catch (DataCacheException ex)

{

return null;

}

}

}

}

public DataCacheFactory CacheFactory

{

get { return _cacheFactory; }

}

public DataCache DefaultCache

{

get { return _defaultCache; }

}

public DataCache EventCache

{

get { return _eventCache; }

}

public DataCacheItemVersion AddCache(string key, object value, TimeSpan? timeout)

{

DataCacheItemVersion version = null;

if (timeout.HasValue)

{

version = _defaultCache.Put(key, value, timeout.Value);

}

else

{

version = _defaultCache.Put(key, value);

}

return version;

}

}

}

The second file is Default3.aspx page:

<%@ Page Language=”C#” AutoEventWireup=”true” CodeBehind=”Default3.aspx.cs” Inherits=”VelocityTest.Default3″ %>

<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>

<html xmlns=”http://www.w3.org/1999/xhtml” >

<head runat=”server”>

<title>Velocity – Tag Example</title>

</head>

<body>

<form id=”form1″ runat=”server”>

<p>

<h3>Velocity – Tag Example</h3>

<table border=”0″ cellpadding=”2″>

<tr>

<td><b>Cache Key</b></td>

<td>

<asp:TextBox ID=”txtCacheKey” runat=”server” />

<asp:RequiredFieldValidator ID=”vadCacheKey” runat=”server” ControlToValidate=”txtCacheKey” ErrorMessage=”* required” Display=”Dynamic” />

</td>

</tr>

<tr>

<td><b>Cache Content</b></td>

<td>

<asp:TextBox ID=”txtCacheContent” runat=”server” />

<asp:RequiredFieldValidator ID=”vadCacheContent” runat=”server” ControlToValidate=”txtCacheContent” ErrorMessage=”* required” Display=”Dynamic” />

</td>

</tr>

<tr>

<td><b>Cache Tags</b></td>

<td><asp:TextBox ID=”txtCacheTag” runat=”server” />(Tags are seperated by comma)</td>

</tr>

<tr>

<td>&nbsp;</td>

<td>

<asp:Button ID=”btnCancel” runat=”server” Text=”Cancel” CausesValidation=”false” />

<asp:Button ID=”btnSave” runat=”server” Text=”Save” />

</td>

</tr>

</table>

</p>

<hr />

<p>

<table border=”0″ cellpadding=”2″>

<tr>

<td><b>Search Method:</b></td>

<td>

<asp:RadioButtonList ID=”radSearchMethodList” runat=”server” RepeatDirection=”Horizontal”>

<asp:ListItem Text=”No Tag” Value=”NoTag” />

<asp:ListItem Text=”All Tags” Value=”AllTags” />

<asp:ListItem Text=”Any Tag” Value=”AnyTag” />

<asp:ListItem Text=”One Tag” Value=”OneTag” />

</asp:RadioButtonList>

</td>

</tr>

<tr>

<td><b>Search Tags:</b></td>

<td><asp:TextBox ID=”txtSearchTag” runat=”server” />(Tags are seperated by comma)</td>

</tr>

<tr>

<td>&nbsp;</td>

<td>

<asp:Button ID=”btnClearSearch” runat=”server” Text=”Clear” CausesValidation=”false” />

<asp:Button ID=”btnSearch” runat=”server” Text=”Search” CausesValidation=”false” />

</td>

</tr>

</table>

</p>

<hr />

<p>

<asp:GridView ID=”gvCache” runat=”server”

AutoGenerateColumns=”false”

EmptyDataText=”No cache data”>

<Columns>

<asp:TemplateField HeaderText=”Key” ShowHeader=”true”>

<ItemTemplate>

<asp:Label ID=”lblCacheKey” runat=”server” />

</ItemTemplate>

</asp:TemplateField>

<asp:TemplateField HeaderText=”Content” ShowHeader=”true”>

<ItemTemplate>

<asp:Label ID=”lblCacheContent” runat=”server” />

</ItemTemplate>

</asp:TemplateField>

<asp:TemplateField HeaderText=”Tags” ShowHeader=”true”>

<ItemTemplate>

<asp:Label ID=”lblCacheTag” runat=”server” />

</ItemTemplate>

</asp:TemplateField>

</Columns>

</asp:GridView>

</p>

</form>

</body>

</html>

The third file is Default3.aspx.cs code-behind:


using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Text;

using Microsoft.Data.Caching;

namespace VelocityTest

{

[Serializable]

public class MyCacheItem

{

public string Key { get; set; }

public string Content { get; set; }

public List<DataCacheTag> Tags { get; set; }

public MyCacheItem(string key, string conent)

{

Key = key;

Content = conent;

Tags = new List<DataCacheTag>();

}

public MyCacheItem(string key, string conent, string[] tags)

{

Key = key;

Content = conent;

Tags = new List<DataCacheTag>();

foreach (string tag in tags)

{

Tags.Add(new DataCacheTag(tag));

}

}

public string[] GetTags()

{

string[] tags = null;

if (Tags != null && Tags.Count > 0)

{

tags = new string[Tags.Count];

int i = 0;

foreach (DataCacheTag tag in Tags)

{

tags[i++] = tag.ToString();

}

}

return tags;

}

}

public enum TagSearchMethod

{

NoTag,

AllTags,

AnyTag,

OneTag

}

public partial class Default3 : System.Web.UI.Page

{

protected const string DEFAULT_DELIMITER = “,”;

protected DataCacheFactory _factory = CacheWorker.Instance.CacheFactory;

protected DataCache _cache = CacheWorker.Instance.DefaultCache;

protected void Page_Load(object sender, EventArgs e)

{

btnSave.Click += new EventHandler(btnSave_Click);

btnCancel.Click += new EventHandler(btnCancel_Click);

btnClearSearch.Click += new EventHandler(btnClearSearch_Click);

btnSearch.Click += new EventHandler(btnSearch_Click);

gvCache.RowDataBound += new GridViewRowEventHandler(gvCache_RowDataBound);

gvCache.DataSource = GetCacheDataSource(TagSearchMethod.NoTag, null);

gvCache.DataBind();

}

protected List<MyCacheItem> GetCacheDataSource(TagSearchMethod method, List<DataCacheTag> tags)

{

List<MyCacheItem> items = new List<MyCacheItem>();

IEnumerable<KeyValuePair<string, Object>> cachePairs = null;

if (method == TagSearchMethod.NoTag)

{

cachePairs = _cache.GetObjectsInRegion(CacheWorker.DEFAULT_CACHE_REGION);

}

else if (method == TagSearchMethod.AllTags)

{

cachePairs = _cache.GetObjectsByAllTags(tags, CacheWorker.DEFAULT_CACHE_REGION);

}

else if (method == TagSearchMethod.AnyTag)

{

cachePairs = _cache.GetObjectsByAnyTag(tags, CacheWorker.DEFAULT_CACHE_REGION);

}

else if (method == TagSearchMethod.OneTag)

{

cachePairs = _cache.GetObjectsByTag(tags[0], CacheWorker.DEFAULT_CACHE_REGION);

}

if (cachePairs != null)

{

foreach (KeyValuePair<string, object> item in cachePairs)

{

items.Add((MyCacheItem)item.Value);

}

}

return items;

}

protected void gvCache_RowDataBound(object sender, GridViewRowEventArgs e)

{

if (e.Row.RowType == DataControlRowType.DataRow)

{

MyCacheItem data = e.Row.DataItem as MyCacheItem;

if (data != null)

{

Label lblCacheKey = e.Row.FindControl(“lblCacheKey”) as Label;

if (lblCacheKey != null)

{

lblCacheKey.Text = data.Key;

}

Label lblCacheContent = e.Row.FindControl(“lblCacheContent”) as Label;

if (lblCacheContent != null)

{

lblCacheContent.Text = data.Content;

}

Label lblCacheTag = e.Row.FindControl(“lblCacheTag”) as Label;

if (lblCacheTag != null)

{

if (data.Tags != null)

{

lblCacheTag.Text = String.Join(“,”, data.GetTags());

}

else

{

lblCacheTag.Text = “&nbsp;”;

}

}

}

}

}

protected void btnSearch_Click(object sender, EventArgs e)

{

if (radSearchMethodList.SelectedItem != null)

{

TagSearchMethod method = (TagSearchMethod)Enum.Parse(typeof(TagSearchMethod), radSearchMethodList.SelectedValue);

List<DataCacheTag> tags = GetTags(txtSearchTag.Text.Trim());

gvCache.DataSource = GetCacheDataSource(method, tags);

}

else

{

gvCache.DataSource = GetCacheDataSource(TagSearchMethod.NoTag, null);

}

gvCache.DataBind();

}

protected void btnClearSearch_Click(object sender, EventArgs e)

{

Response.Redirect(Request.RawUrl);

}

protected void btnCancel_Click(object sender, EventArgs e)

{

Response.Redirect(Request.RawUrl);

}

protected void btnSave_Click(object sender, EventArgs e)

{

if (!String.IsNullOrEmpty(txtCacheKey.Text.Trim()) &&

!String.IsNullOrEmpty(txtCacheContent.Text.Trim()))

{

MyCacheItem item = null;

string[] tags = null;

if (!String.IsNullOrEmpty(txtCacheTag.Text.Trim()))

{

tags = (string[])txtCacheTag.Text.Trim().Split(DEFAULT_DELIMITER.ToCharArray());

}

if (tags != null && tags.Length > 0)

{

item = new MyCacheItem(txtCacheKey.Text.Trim(), txtCacheContent.Text.Trim(), tags);

}

else

{

item = new MyCacheItem(txtCacheKey.Text.Trim(), txtCacheContent.Text.Trim());

}

_cache.Put(item.Key, item, item.Tags, CacheWorker.DEFAULT_CACHE_REGION);

}

gvCache.DataSource = GetCacheDataSource(TagSearchMethod.NoTag, null);

gvCache.DataBind();

}

protected List<DataCacheTag> GetTags(string tagString)

{

List<DataCacheTag> tags = new List<DataCacheTag>();

string[] items = tagString.Split(DEFAULT_DELIMITER.ToCharArray());

foreach (string item in items)

{

tags.Add(new DataCacheTag(item));

}

return tags;

}

}

}

The following are screenshots.

This screenshot has nothing is cache.

ScreenShot001

This screenshot has 3 cache items.

ScreenShot002

This screenshot is search by AllTags <a>.

ScreenShot003

This screenshot is search by AllTags <a, b>.

ScreenShot004

This screenshot is search by AnyTag <a>.

ScreenShot005

This screenshot is search by AnyTag <b>.

ScreenShot006

This screenshot is search by Tag <a, b> – there are 4 cache items!

ScreenShot007

Velocity (CTP3) – How do I know if a cache region is created?

In Velocity (CTP3) we can create, remove, and clean a cache region programmatically by calling the following methods:

  • DataCache.CreateRegion
  • DataCache.RemoveRegion
  • DataCache.ClearRegion

But there is no method to indicate if a cache region is created or not; so if I create a region which has been created already, an exception is thrown with the error code DataCacheErrorCode.RegionAlreadyExsits.

So far the walkaround is remove the cache region before creating it:

_defaultCache.RemoveRegion(DEFAULT_CACHE_REGION);

_defaultCache.CreateRegion(DEFAULT_CACHE_REGION, true);

But this is not a good approach; so I hope the Velocity team will add the feature in the next release to check if a cache region is created.

Use Velocity (CTP3) as Session State Provider

The blog shows how to configure web.config to use Velocity as session state provider, but it only works for CTP2.

I did some experiment and found out the following configuration in web.config will work in Velocity CTP3:

<sessionState mode=Custom customProvider=SessionStoreProvider>

<providers>

<add name=SessionStoreProvider type=Microsoft.Data.Caching.DataCacheSessionStoreProvider, ClientLibrary />

</providers>

</sessionState>


Notification callback in Velocity CTP3

This is an example of how to make the Notification callback work in Velocity CTP3.

Here is my web.config – the cache is a routing client:

<?xml version=1.0?>

<configuration>

<configSections>

<!– required to read the <dataCacheClient> element –>

<section name=dataCacheClient

type=Microsoft.Data.Caching.DataCacheClientSection,CacheBaseLibrary

allowLocation=true

allowDefinition=Everywhere/>

<!– required to read the <fabric> element, when present –>

<section name=fabric

type=System.Data.Fabric.Common.ConfigFile,FabricCommon

allowLocation=true

allowDefinition=Everywhere/>

<!– routing client–>

<dataCacheClient deployment=routing>

<clientNotification pollInterval=300 />

<!– cache host(s) –>

<hosts>

<host

name=VIRTUALB-UJT42H

cachePort=22233

cacheHostName=DistributedCacheService/>

</hosts>

</dataCacheClient>

</configuration>

Here is the cache cluster configuration via the command Export-CacheClusterConfig:

<?xml version=1.0 encoding=utf-8?>

<configuration>

<configSections>

<section name=dataCache type=Microsoft.Data.Caching.DataCacheSection, CacheBaseLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91 />

</configSections>

<dataCache cluster=Velocity_Cluster_1 size=Small>

<caches>

<cache type=partitioned consistency=strong name=default>

<policy>

<eviction type=lru />

<expiration defaultTTL=10 isExpirable=true />

<serverNotification isEnabled=true />

</policy>

</cache>

<cache type=partitioned consistency=strong name=default_2>

<policy>

<eviction type=lru />

<expiration defaultTTL=10 isExpirable=true />

<serverNotification isEnabled=true />

</policy>

</cache>

</caches>

<hosts>

<host clusterPort=22234 hostId=1825498580 size=1024 quorumHost=true

name=VIRTUALB-UJT42H cacheHostName=DistributedCacheService

cachePort=22233 />

</hosts>

<advancedProperties>

<partitionStoreConnectionSettings providerName=System.Data.SqlClient

connectionString=Data Source=VIRTUALB-UJT42H\SQLEXPRESS;Initial Catalog=Velocity_Shared_1;User Id=Not_sa;Password=ForYourEyesOnly;

leadHostManagement=false />

</advancedProperties>

</dataCache>

</configuration>

Here is the code of CacheWorker.cs (kind of helper class with singleton pattern):

using System;

using Microsoft.Data.Caching;

namespace VelocityTest

{

public class CacheWorker

{

private static object _synclock = new object();

private static CacheWorker _cacheWorker = null;

private static DataCacheFactory _cacheFactory = null;

private static DataCache _defaultCache = null;

private CacheWorker()

{

}

public static CacheWorker Instance

{

get

{

lock (_synclock)

{

try

{

if (_cacheWorker == null)

{

_cacheWorker = new CacheWorker();

// we use configuration file

_cacheFactory = new DataCacheFactory();

// default cache

_defaultCache = _cacheFactory.GetDefaultCache();

}

return _cacheWorker;

}

catch (DataCacheException ex)

{

return null;

}

}

}

}

public DataCacheFactory CacheFactory

{

get { return _cacheFactory; }

}

public DataCache DefaultCache

{

get { return _defaultCache; }

}

}

}

Here is the partial code-behind of the test page:

public partial class _Default : System.Web.UI.Page

{

public const string CACHE_KEY = “{8768036E-2B99-4563-8BA5-C2D07B5B6023}”;

protected static DataCacheFactory _factory = CacheWorker.Instance.CacheFactory;

protected static DataCache _cache = CacheWorker.Instance.DefaultCache;

protected void Page_Load(object sender, EventArgs e)

{

if (!Page.IsPostBack)

{

DataCacheOperation filter = DataCacheOperation.AddItem | DataCacheOperation.RemoveItem | DataCacheOperation.ReplaceItem | DataCacheOperation.ClearRegion | DataCacheOperation.CreateRegion | DataCacheOperation.RemoveRegion;

DataCacheNotificationDescriptor dn = _cache.AddCacheLevelCallback(filter, OnCacheNotificationReceived);

SetCache(MyPerson, true);

ShowCache(GetCache());

}

btnUpdate.Click += new EventHandler(btnUpdate_Click);

btnRefresh.Click += new EventHandler(btnRefresh_Click);

btnRemove.Click += new EventHandler(btnRemove_Click);

btnGet.Click += new EventHandler(btnGet_Click);

btnAnotherPage.Click += new EventHandler(btnAnotherPage_Click);

}

// other code is here….


public void OnCacheNotificationReceived(string cacheName, string regionName, string key, DataCacheItemVersion version, DataCacheOperation cacheOperation, DataCacheNotificationDescriptor nd)

{

StringBuilder sb = new StringBuilder();

sb.Append(“cacheName: “ + cacheName + Environment.NewLine);

sb.Append(“regionName: “ + regionName + Environment.NewLine);

sb.Append(“key: “ + key + Environment.NewLine);

sb.Append(“cacheOperation: “ + cacheOperation.ToString() + Environment.NewLine);

txtMessageBox.Text = sb.ToString();

}

}

Basically the configuration has the Notification enabled and set the client as routing client. The callback is fired when ever there is a change in cache item or region.

Google Language ASP.NET Controls

I published my second project at CodePlex – Google Language ASP.NET Controls.

In my previuos post, I wrote the sample code to create Google Ajax Language Custom Controls – they are Ajax-able, but this time I just build regular server control to save some time :-)

The usage is:

  • Include library:
<%@ Register Assembly="GoogleLanguage.WebControls" Namespace="GoogleLanguage.WebControls" TagPrefix="glc" %>
  • Bootstrap Javascript with LanguageManager:
<glc:LanguageManager ID="LanguageManager1" runat="server" />
  • Use <Literal> tag for next text:
<glc:Literal ID="Literal5" runat="server" Text="Do you like music too?" DestinationLanguage="FRENCH" />
  • Use <Translator> tag for existing text in server controls:
<asp:TextBox ID="TextBox1" runat="server" Text="Important Notice" />
<glc:Translator ID="Translator2" runat="server" TargetControlID="TextBox1" DestinationLanguage="GERMAN" />

Screenshot – Sample Code

Sample Code

Screenshot – Sample Output

Sample Output

DotNetNuke Reset Password

When I use DotNetNuke.Entities.Users.UserController.ResetPassword(), I assume this method will set the password length to the length I defined in Membership Provider Settings, but which is not the case. So I have to manually create a new password with the minimum password length and change the current password.

Here is the code:

DotNetNuke.Entities.Users.UserInfo userInfo = DotNetNuke.Entities.Users.UserController.GetUserByName(PortalId, UserName);

DotNetNuke.Security.Membership.MembershipProvider membershipProvider = DotNetNuke.Security.Membership.MembershipProvider.Instance();

if (membershipProvider.PasswordFormat == DotNetNuke.Security.Membership.PasswordFormat.Encrypted)

{

string oldPassword = DotNetNuke.Entities.Users.UserController.GetPassword(ref userInfo, userInfo.Membership.PasswordAnswer);

string newPassword = DotNetNuke.Entities.Users.UserController.GeneratePassword(membershipProvider.MinPasswordLength);

DotNetNuke.Entities.Users.UserController.ChangePassword(userInfo, oldPassword, newPassword);

}

else

{

DotNetNuke.Entities.Users.UserController.ResetPassword(userInfo, userInfo.Membership.PasswordAnswer);

}

The method GetPassword() will only return the password if the membership provider supports and is using a password encryption method that supports decryption.

Google AJAX Language Custom Server Control

I tried Google AJAX Language API yesterday with a “Hello World” example, today I build an ASP.NET AJAX-enabled custom server control for it. The coding process is fun and it’s way simpler than building Virtual Earth Map AJAX-enabled controls at work. If you need to learn how to build an ASP.NET AJAX-enabled custom server control, this tutorial will help you.

My Google AJAX Language custom server control is straightforward and it extends System.Web.UI.WebControls.Label class and implements System.Web.UI.IScriptControl interface. I will list the source code and give some notes.

GoogleLanguageLabel.cs – server-side code of control

  1. Text property is overridden – this is the text will be translated to target language based on the LanguageCode property.
  2. LanguageCode property – See Google Language Enum for valid codes.
  3. AssemblyInfo.cs and WebResource – Add the following line to AssemblyInfo.cs because we will make the associated Javascript as web resource: [assembly: System.Web.UI.WebResource("Posts.AjaxControl.GoogleLanguageLabel.js", "application/x-javascript")]

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Text;

using System.Web;

using System.Web.UI;

using System.Web.UI.WebControls;

namespace Posts.AjaxControl

{

[DefaultProperty("Text")]

[ToolboxData("<{0}:GoogleLanguageLabel runat=server></{0}:GoogleLanguageLabel>")]

public class GoogleLanguageLabel : Label, IScriptControl

{

[Category("Appearance")]

public override string Text

{

get { return ((ViewState["Text"] != null) ? ViewState["Text"].ToString() : String.Empty); }

set { ViewState["Text"] = value; }

}

[Category("Appearance")]

public string LanguageCode

{

get { return ((ViewState["LanguageCode"] != null) ? ViewState["LanguageCode"].ToString() : “en”); }

set { ViewState["LanguageCode"] = value; }

}

protected virtual IEnumerable<ScriptReference> GetScriptReferences()

{

ScriptReference reference = new ScriptReference(Page.ClientScript.GetWebResourceUrl(typeof(GoogleLanguageLabel), “Posts.AjaxControl.GoogleLanguageLabel.js”));

return new ScriptReference[] { reference };

}

protected virtual IEnumerable<ScriptDescriptor> GetScriptDescriptors()

{

ScriptControlDescriptor descriptor = new ScriptControlDescriptor(“AjaxControl.GoogleLanguageLabel”, this.ClientID);

descriptor.AddProperty(“text”, this.Text);

descriptor.AddProperty(“languageCode”, this.LanguageCode);

return new ScriptDescriptor[] { descriptor };

}

IEnumerable<ScriptReference> IScriptControl.GetScriptReferences()

{

return GetScriptReferences();

}

IEnumerable<ScriptDescriptor> IScriptControl.GetScriptDescriptors()

{

return GetScriptDescriptors();

}

protected override void OnPreRender(EventArgs e)

{

if (!this.DesignMode)

{

// Test for ScriptManager and register if it exists

ScriptManager sm = ScriptManager.GetCurrent(Page);

if (sm == null)

{

throw new HttpException(“A ScriptManager control must exist on the current page.”);

}

sm.RegisterScriptControl(this);

}

base.OnPreRender(e);

}

protected override void Render(HtmlTextWriter writer)

{

if (!this.DesignMode)

{

ScriptManager.GetCurrent(Page).RegisterScriptDescriptors(this);

}

base.Render(writer);

}

}

}

GoogleLanguageLabel.js – client-side code of control

  1. Global variable – I define a global variable “global_google” to make sure it’s hooked up with the “google” object from the external Javascript http://www.google.com/jsapi, and this global variable can be accessed inside Javascript functions.
  2. Save context – Remember to save context (”this._languageCode” and “this.get_element()” in this case) before going inside the inner functions.

Type.registerNamespace(‘AjaxControl’);

var global_google = google;

AjaxControl.GoogleLanguageLabel = function(element)

{

AjaxControl.GoogleLanguageLabel.initializeBase(this, [element]);

this._text = null;

this._languageCode = null;

}

AjaxControl.GoogleLanguageLabel.prototype =

{

initialize : function() {

AjaxControl.GoogleLanguageLabel.callBaseMethod(this, ‘initialize’);

// save context

var langugaeCode = this._languageCode;

var element = this.get_element();

// current text

var text = this.get_element().innerHTML;

// detect and translate

global_google.language.detect(text, function(result) {

if (!result.error && result.language) {

global_google.language.translate(text, result.language, langugaeCode, function(result) {

if (result.translation) {

element.innerHTML = result.translation;

}

});

}

});

},

dispose : function() {

AjaxControl.GoogleLanguageLabel.callBaseMethod(this, ‘dispose’);

},

// *** Control properties ***

set_text : function(value) {

if (this._text !== value) {

this._text = value;

this.raisePropertyChanged(‘text’);

}

},

get_text : function() { return this._text; },

set_languageCode : function(value) {

if (this._languageCode !== value) {

this._languageCode = value;

this.raisePropertyChanged(‘languageCode’);

}

},

get_languageCode : function() { return this._languageCode; }

}

AjaxControl.GoogleLanguageLabel.registerClass(‘AjaxControl.GoogleLanguageLabel’, Sys.UI.Control);

if (typeof(Sys) !== ‘undefined’) Sys.Application.notifyScriptLoaded();

GoogleLanguageLabel.aspx – demo page

  1. Register web server control – <%@ Register Namespace=”Posts.AjaxControl” Assembly=”Posts” TagPrefix=”ac” %>
  2. Link to the external Javascript http://www.google.com/jsapi.
  3. Include Google AJAX Lauguage API in the page – call google.load(”language”, “1″); to load version 1 of the AJAX Language API.
  4. Create <ac:GoogleLanguageLabel> controls and assign Language Code.

<%@ Page Language=”C#” AutoEventWireup=”true” CodeBehind=”GoogleLanguageLabel.aspx.cs” Inherits=”Posts.AjaxControlDemo.GoogleLanguageLabel” %>

<%@ Register Namespace=”Posts.AjaxControl” Assembly=”Posts” TagPrefix=”ac” %>

<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>

<html xmlns=”http://www.w3.org/1999/xhtml” >

<head runat=”server”>

<title>Google Language Label</title>

<script type=”text/javascript” src=”http://www.google.com/jsapi”></script>

<script type=”text/javascript”>

google.load(“language”, “1″);

</script>

</head>

<body>

<form id=”form1″ runat=”server”>

<asp:ScriptManager ID=”ScriptManager1″ runat=”server” />

<div>

<ac:GoogleLanguageLabel ID=”lblMessage1″ runat=”server” Text=”Hello!” LanguageCode=”zh-TW” />

<br />

<ac:GoogleLanguageLabel ID=”lblMessage2″ runat=”server” Text=”How are you?” LanguageCode=”fr” />

<br />

<ac:GoogleLanguageLabel ID=”lblMessage3″ runat=”server” Text=”Can I borrow 100 dollars from you?” LanguageCode=”hi” />

<br />

<ac:GoogleLanguageLabel ID=”lblMessage4″ runat=”server” Text=”Can I return your money when I win the lottery?” LanguageCode=”ja” />

<br />

<ac:GoogleLanguageLabel ID=”lblMessage5″ runat=”server” Text=”Actually I just find I still have 1000 dollars in my wallet.” LanguageCode=”ru” />

</div>

</form>

</body>

</html>

Here is the result:

Hello!
Comment vas-tu?
उधार लेने से 100 डॉलर कर सकता हूँ ?
あなたのお金を返すときに私に勝つの宝くじですか?
На самом деле я просто найти Я еще 1000 долларов в моем кошельке.

Google AJAX Language in ASP.NET

I found another treasure in Google CodeGoogle AJAX Languages API. Now I can detect and translate blocks of text in a web page using Javascript. I am thinking to build a custom server control which inherits System.Web.UI.WebControls.Label, and this control is able to translate its Text and Tooltip to the language specified when the control is rendered.

Today I just try the “Hello World” example:

<%@ Page Language=”C#” AutoEventWireup=”true” CodeBehind=”Default.aspx.cs” Inherits=”Posts._Default” %>

<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>

<html xmlns=”http://www.w3.org/1999/xhtml” >

<head runat=”server”>

<title>Google AJAX Language</title>

<script type=”text/javascript” src=”http://www.google.com/jsapi”></script>

<script type=”text/javascript”>

google.load(“language”, “1″);

function pageLoad(sender, args)

{

translate_text(“<%=lblMessage1.ClientID%>”, “zh-TW”);

translate_text(“<%=lblMessage2.ClientID%>”, “zh-TW”);

}

function translate_text(element_id, language_code)

{

var text = $get(element_id).innerHTML;

google.language.detect(text, function(result) {

if (!result.error && result.language) {

google.language.translate(text, result.language, language_code, function(result) {

if (result.translation) {

$get(element_id).innerHTML = result.translation;

}

});

}

});

}

</script>

</head>

<body>

<form id=”form1″ runat=”server”>

<asp:ScriptManager ID=”ScriptManager1″ runat=”server” />

<h3>Google AJAX Language</h3>

<div>

<asp:Label ID=”lblMessage1″ runat=”server” Text=”Hello World” />

<br />

<asp:Label ID=”lblMessage2″ runat=”server” Text=”This is a test page demonstrating the language translation.” />

</div>

</form>

</body>

</html>

The output is:

Google AJAX Language
世界您好
這是一個測試網頁上展示的語言翻譯。

JavaScriptSerializer example

Today at work I had a chance to try the JavaScriptSerializer class in the System.Web.Script.Serialization namespace. My task was simple enough – deserialize a JSON string to an object then serialize the object back to a JSON string. Before I used the two methods Deserialize() and Serialize(), I had to create two classes for my experiment:

[Serializable]

public class LatLong

{

public double? latitude;

public double? longitude;

}

[Serializable]

public class MapView

{

public LatLong center;

public int? zoom;

}

So here is the code to deserialize and Serialize:

using System.Text;

using System.Web.Script.Serialization;

protected void Page_Load(object sender, EventArgs e)

{

string example = “{\”center\”:{\”latitude\”:\”49.266214\”,\”longitude\”:\”-122.998577\”},\”zoom\”:\”12\”}”;

JavaScriptSerializer serializer = new JavaScriptSerializer();

// Deserialize

MapView view = serializer.Deserialize<MapView>(example);

StringBuilder sb = new StringBuilder();

sb.Append(“center = (” + view.center.latitude.ToString() + “, “ + view.center.longitude.ToString() + “)” + “<br/>”);

sb.Append(“zoom = “ + view.zoom.ToString());

litDeserialization.Text = sb.ToString();

// Serialize

string jsonString = serializer.Serialize(view);

litSerialization.Text = jsonString;

}

The result is like:

Deserialization:

center = (49.266214, -122.998577)
zoom = 12

Serialization:

{”center”:{”latitude”:49.266214,”longitude”:-122.998577},”zoom”:12}