navigation
 Wednesday, May 31, 2006

There is a cool new feature in ASP.NET 2.0 that allows you to embed resources into your server side assemblies and have your web controls request these resources automatically. This is especially handy when you want to deploy a new control. No longer do you have to find just the right spot to place the JavaScript and image files that your control depends on. 

Microsoft already uses this technique with its validators. The JavaScript needed for client side validation is embedded in one of the .NET assemblies on the server. If you were to view the source of any web page that uses a validator in ASP.NET 2.0, you might see something like this:

<script src="/MyWebSite/WebResource.axd?d=68d8KT0ikvX6J4c8Z8zwvfZXzPdyyYYY8TuccizWlCePFWz&t=632847500868593750" type="text/javascript"></script>

This is a request to a new HttpHandler, WebResources.axd, for the necessary JavaScript. The first parameter, ‘d’ is an encrypted identifier telling the handler which resource it needs. The ‘t’ parameter is an encrypted timestamp, used to determine if the resource has changed.

Looks pretty simple right?

Well, lets see how easy it is for you to use this technique. Lets try to create a control that has its JavaScript embedded as a resource. For this example, I am going to use a useful script that buffers key presses and matches an entire word in a dropdownlist instead of just the first character. (Thanks to Jonathan Cogley at ASP Alliance for the original script: http://authors.aspalliance.com/thycotic/articles/view.aspx?id=3)

First, lets open up or create a new web site in Visual Studio 2005 and then add a new Web Control Library project to it. After adding the project, remember to add a reference to it from our web site.  This new assembly will contain all of the elements of our control and our web site has to know about it to use it.

Next, lets add a new class that inherits from DropDownList:

using System;
using System.Collections.Generic;
using System.Text;
using System.Web.UI.WebControls;
using System.Web.UI;


 

namespace WebControlLibrary1
{
    public class KeyedDropDownList : DropDownList
{
        private bool _caseSensitive = false;

        public KeyedDropDownList(){}

    
     public bool CaseSensitive
     {
     get
     {
     return _caseSensitive;
     }
     set
     {
     _caseSensitive = value;
     }
     }
    }
}

I have also added a property “CaseSensitive” which will be passed to the script to allow us to turn on and off case sensitivity when matching strings in our list.

Now add a new a JScript file and examine the properties. To embed this file as a resource the Build Action must be set to 'Embedded Resource'.  After setting the build action,  feel free to copy this code into the file or substitute JavaScript of your own:

function KeyedDropDownList_onkeypress(dropdownlist,caseSensitive) 
{
// check the keypressBuffer attribute is defined on the dropdownlist
var undefined;
if (dropdownlist.keypressBuffer == undefined)
   {
dropdownlist.keypressBuffer = '';
}
   // get the key that was pressed
var key = String.fromCharCode(window.event.keyCode);
dropdownlist.keypressBuffer += key;
  if (!caseSensitive)
   {
// convert buffer to lowercase
dropdownlist.keypressBuffer = dropdownlist.keypressBuffer.toLowerCase();
}
  // find if it is the start of any of the options
var optionsLength = dropdownlist.options.length;
for (var n=0; n < optionsLength; n++)
{
var optionText = dropdownlist.options[n].text;
if (!caseSensitive)
     {
optionText = optionText.toLowerCase();
  }
if (optionText.indexOf(dropdownlist.keypressBuffer,0) == 0)
     {
dropdownlist.selectedIndex = n;
return false; // cancel the default behavior since
// we have selected our own value
     }
}
// reset initial key to be inline with default behavior
dropdownlist.keypressBuffer = key;
return true; // give default behavior
}

Next,  you must add an attribute to the assemblyinfo.cs file to reference the embedded resource:

[assembly: System.Web.UI.WebResource("WebControlLibrary1.JScript1.js", 
"text/javascript")]

You will notice that the reference to the JScript file is fully qualified. You must do this using the project's default namespace, not just any old namespace in the assembly. You can see this  by right clicking on the project and viewing the properties. I should also mention that we intentionally left out an optional boolean parameter named 'PerformSubstitution'. By default 'PerformSubstitution' is off, and you should turn it on if the referenced resource accesses other embedded resources. An embedded style sheet that defines some style using an embedded image is a good example of when to set PerformSubstitution to true.

Now, instead of registering the script block with the necessary JavaScript ourselves, we will register a request to get the script block from our  assembly.  Thanks to the new ClientScriptManager in ASP.NET 2.0, this is  very easy.  We will first override the OnInit method of our KeyedDropDownList class and then  get a reference to this manager to do most of our work.

protected override void OnInit(EventArgs e)    
{
base.OnInit(e);
ClientScriptManager cs = Page.ClientScript;
Type rsType = this.GetType();
cs.RegisterClientScriptInclude("MyScript",
cs.GetWebResourceUrl(rsType,"WebControlLibrary1.JScript1.js"));
}

The last piece of coding is very simple.  Just attach  the onkeypress event of our control to our custom JavaScript function.  Although in this example we have not used the ViewState to track our lone property,  we might want to do that or add more properties that can be passed to the JavaScript function.  So here I overrode the OnLoad method to add the attribute to our webcontrol. By the time the OnLoad event is fired, the ViewState will have been  loaded.

protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
this.Attributes.Add("onkeypress",
"return KeyedDropDownList_onkeypress(this," +
_caseSensitive.ToString().ToLower() + ")");
}

If all goes well, we should be able to now use this control on any form in our web app and the necessary JavaScript will automatically accompany it wherever it goes. I should also point out that when debugging, the JavaScript is not cached, it is downloaded each time. When debugging is off, the JavaScript is cached on the client automatically. 

I have  included the markup for the test page that registers and uses this new control below.

<%@ Page Language="VB" AutoEventWireup="false"
CodeFile="Default.aspx.vb" Inherits="_Default" %>
<%@ Register Assembly="WebControlLibrary1"
Namespace="WebControlLibrary1" TagPrefix="wcl" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
<title>Test Page</title>
</head>
<body>
<form id="form1" runat="server">
<wcl:KeyedDropDownList id="ddlTest" runat="server">
<asp:ListItem Value="1">Calendar</asp:ListItem>
<asp:ListItem Value="2">Can</asp:ListItem>
<asp:ListItem Value="3">Candle</asp:ListItem>
<asp:ListItem Value="4">Canary</asp:ListItem>
<asp:ListItem Value="5">Card</asp:ListItem>
<asp:ListItem Value="6">Cat</asp:ListItem>
<asp:ListItem Value="7">Cow</asp:ListItem>
</wcl:KeyedDropDownList>
</form>
</body>
</html>
Name
E-mail
Home page

Comment (Some html is allowed: a@href@title, i, strike, u) where the @ means "attribute." For example, you can use <a href="" title=""> or <blockquote cite="Scott">.  

Enter the code shown (prevents robots):

Live Comment Preview