Creating Custom ASP.NET Server Controls with Embedded JavaScript
I did some consulting work recently for a company that had a lot of JavaScript embedded in pages that was used used to perform advanced client-side functionality and make AJAX calls back to the server. The company needed additional team members to be able to contribute to the application without spending a lot of time learning client-side Web technologies. One solution was to provide good documentation of the JavaScript objects and methods that could be called. This still required some fundamental knowledge of JavaScript though. The focus, however, seemed to be on getting other team members involved with learning C# and server-side technologies so that they could also build back-end code tiers rather than having everyone spend time learning JavaScript and other related client-side technologies such as CSS and DHTML/DOM.
After seeing the existing application and hearing about the challenges the team was facing, I provided a demo of how custom ASP.NET server controls could have JavaScript embedded in them fairly easily. By going this route the people that knew JavaScript could still leverage their existing skills, but they could wrap the client-side functionality in a server control that other developers could more easily consume and use without having to be client-side gurus. Going this route also allowed properties that scripts may utilitize to be exposed along with documentation of what the properties do.
The following steps show how to accomplish this type of JavaScript encapsulation. The steps extend ASP.NET's standard GridView control and customize it by adding additional client-side functionality. I'll admit upfront that this is a fairly simple example designed only as a starting point. However, the concepts can be applied to more advanced cases. I'm using the exact same principles in version 2.5 of my OrgChart.NET server control (due out soon).
Step 1: Choose how to create your custom control. You have two main choices when creating custom server controls. You can choose to write a control from scratch, or you can extend an existing control provided by ASP.NET or a 3rd party vendor. Let's say that you'd like to create a "fancy" grid control that highlights rows as a user mouses over them and allows users to select one or more rows and highlight them. All of this is done on the client-side without any postback operations. While you could write the grid functionality from scratch, why not leverage what Microsoft has already done if it gets you to the desired end result faster and satisfies project requirements? This is easily done by creating a new Class Library project in VS.NET 2005 and deriving the custom control class from GridView as shown next:
public class CustomGridView : GridView
{
}
The ToolboxData attribute defines what markup code should be added to an ASP.NET page as a developer drags the control onto a page's design surface.
Step 2. Write the client-side code. This step could certainly be performed later, but I like to write the client-side enhancements upfront. In this case, if we want end users to be able to see highlighted rows as they mouse over them, or select rows by clicking them, we can use code similar to the following.
{
if (row.getAttribute("Selected") == "true") return;
row.style.backgroundColor = bgColor;
row.style.color = textColor;
}
function MouseLeave(row)
{
if (row.getAttribute("Selected") == "true") return;
row.style.backgroundColor = row.getAttribute("OriginalColor");
row.style.color = row.getAttribute("OriginalTextColor");
}
function MouseDown(row,bgColor,textColor)
{
if (row.getAttribute("Selected")=="true")
{
row.style.backgroundColor = row.getAttribute("OriginalColor");
row.style.color = row.getAttribute("OriginalTextColor");
row.setAttribute("Selected","false");
}
else
{
row.style.backgroundColor = bgColor;
row.style.color = textColor
row.setAttribute("Selected","true");
}
}
Since we want to embed the JavaScript code directly into the control, add a new JavaScript file into the Class Library project and set its Build Action to "Embedded Resource". This can be done by right-clicking on the .js file in the Solution Explorer, selecting Properties, and changing the Build Action property.
Step 3. Define the JavaScript file as a Web Resource. There are two main ways to output JavaScript from a server-side control. First, you can embed JavaScript in the control's code and then output it using the ClientScriptManager class's RegisterClientScriptBlock() method (or another custom technique). This works, but makes the control's code less maintainable since JavaScript is embedded in VB.NET or C# code. Also, when the JavaScript is output it is embedded in the page's HTML which isn't a good option when you want to leverage client-side caching of JavaScript files.
The second option (the better option in my opinion) is to use ASP.NET 2.0's WebResource.axd HttpHandler in conjunction with the ClientScriptManager class to dynamically output embedded resources such as JavaScript files. By using WebResource.axd the custom control can output a <script src="..."></script> tag into the page that dynamically references the JavaScript resource on the server. This allows the client browser to cache the script which should lead to faster page loads especially if the script is large. By embedding the JavaScript file as a resource rather than putting it in the control's code, the overall code base is also much easier to maintain.
To allow the custom control to write out <script src="..."></script> tags, the target JavaScript file must first be added as a resource into the project as shown in the previous step. After doing that, the following code should be added into the AssemblyInfo.cs file (this file is added automatically when you create a Class Library project).
This code uses the WebResourceAttribute class to give the embedded script a name and define the appropriate content type of the resource. The name starts with the namespace of the project and ends with the name of the script that is embedded in the project as a resource. This technique can also be used to embed images, CSS files or other types of resources.
Step 4: Render the script to the browser. Once the WebResource attribute has been added into AssemblyInfo.cs, the following code can be added into the custom control to cause a <script src="..."></script> tag referencing the embedded script resource to be output to the page. The call to the ClientScriptManager's RegisterClientScriptResource() method does all the work. It takes the type of control being rendered as well as the resource name defined in AssemblyInfo.cs as parameters.
{
base.OnPreRender(e);
string resourceName = "CustomControls.GridViewScript.js";
ClientScriptManager cs = this.Page.ClientScript;
cs.RegisterClientScriptResource(typeof(CustomControls.CustomGridView), resourceName);
}
Step 5: Hook the custom control to JavaScript functions. Now that the JavaScript is ready to be output, the custom control needs to be "hooked" to the client-side functions shown earlier in Step 2 in order to perform the desired behaviors. The following code uses the GridView's RowCreated event to add client-side mouse over, mouse out and mouse down attributes to rows in the grid. Adding these event handlers could also be done entirely on the client-side in cases where the size of the HTML markup sent from the server to the browser needs to be kept to a minimum.
{
base.OnRowCreated(e);
GridViewRow row = e.Row;
Color bgColor = Color.Empty;
Color color = Color.Empty;
if (row.RowIndex % 2 == 0)
{
bgColor = (this.RowStyle.BackColor == Color.Empty) ? Color.White : this.RowStyle.BackColor;
color = (this.RowStyle.ForeColor == Color.Empty) ? Color.Black : this.RowStyle.ForeColor;
}
else
{
bgColor = (this.AlternatingRowStyle.BackColor == Color.Empty) ? Color.White : this.AlternatingRowStyle.BackColor;
color = (this.AlternatingRowStyle.ForeColor == Color.Empty) ? Color.Black : this.AlternatingRowStyle.ForeColor;
}
if (row.RowType == DataControlRowType.DataRow)
{
row.Attributes.Add("onmouseover", String.Format("MouseEnter(this,'{0}','{1}')",
ColorTranslator.ToHtml(this.MouseOverColor),
ColorTranslator.ToHtml(this.TextOverColor)));
row.Attributes.Add("onmouseout", "MouseLeave(this)");
row.Attributes.Add("onmousedown", String.Format("MouseDown(this,'{0}','{1}')",
ColorTranslator.ToHtml(this.SelectedRowColor),
ColorTranslator.ToHtml(this.SelectedRowTextColor)));
row.Attributes.Add("OriginalColor", ColorTranslator.ToHtml(bgColor));
row.Attributes.Add("OriginalTextColor", ColorTranslator.ToHtml(color));
}
}
The end result of following these steps is that JavaScript is dynamically output from the control in an efficient manner. An example of what the ClientScriptManager outputs to reference the JavaScript resource is shown next (some characters were removed for the sake of brevity):
The control is now self-contained and does not require any additional files to be deployed before it can be used. It's also easy to maintain since the JavaScript is stored as an external resource rather than being embedded in VB.NET or C# code. While this is a simple example, more complex scripts and code can be written to handle the needs of any Web application. The complete code for the custom control discussed in this article can be downloaded here.