Thursday, August 26, 2010

Customization vs. Extension

Introduction


Developing ISD applications is all about customization, customization and customization. Some customizations are project specific, while others are of generic purpose, which can be used across multiple projects. Below are some examples of general purpose customizations.

This article is about how to better manage general purpose customizations.

Background


A trick for general purpose customization is to put the code in ISD's project template folder. When ISD generates a new project, the customization is automatically available. You don't have to manually cut and paste the customization from a previous project. An ideal example of code reuse, isn't it? Nope. It is cut-and-paste in disguise. Although ISD does the cut-and-paste for you, you are left to deal with the consequence of having multiple copies of the same code, a maintenance headache.

Take the Roaming Alert as an example. I put it in ISD's project template, so it became a standard feature in all of my projects. After implemented it in more than 10 projects, however, I found a bug in the original implementation. MiscUtils.RegisterJScriptAlert() method did not handle multi-line message correctly. I had to replace it with my own method. For all the previous projects, I had to go over their life cycles (development, staging, production) all over again, because the modification was at the source code level.

Solution? Do not customize ISD projects. Extend them.

Solution


To customize, insert your code directly into ISD generated projects so that
you get customized ones. To extend, keep your code outside of ISD projects so that they are extended. The following diagram illustrates the difference between customization and extension.



Implementation


Here is a sample implementation of the extension BasePage.

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

namespace DingJing {
public class BasePage : BaseClasses.Web.UI.BasePage {

public BasePage() {
PreRender += new EventHandler(My_PreRender);
}

void My_PreRender(object sender, EventArgs e) {
Dictionary<string, string> AlertQueue =
Session["AlertQueue"] as Dictionary<string, string>;
if (AlertQueue != null) {
foreach (string key in AlertQueue.Keys)
DisplayAlert(key, AlertQueue[key]);
AlertQueue.Clear();
}
}

private void DisplayAlert(string key, string msg) {
msg = msg.Replace(@"\", @"\\").Replace("'", @"\'").Replace("\n", @"\n");
string script = string.Format("alert('{0}');", msg);
ScriptManager.RegisterStartupScript(this, GetType(), key, script, true);
}

public virtual void RegisterAlert(string key, string msg, bool roaming) {
if (!roaming) {
DisplayAlert(key, msg);
} else {
Dictionary<string, string> AlertQueue =
Session["AlertQueue"] as Dictionary<string, string>;
if (AlertQueue == null)
Session["AlertQueue"] = AlertQueue = new Dictionary<string, string>();
AlertQueue.Add(key, msg);
}
}

}
}

Then, make your project's BaseApplicationPage a subclass of the extension BasePage.

namespace ProjectNamespace.UI {
public class BaseApplicationPage : DingJing.BasePage {

You can make the class hierarchy change in generated projects, or in ISD project template. You can extend BaseApplicationTableControl and BaseApplicationRecordControl similarily.

using System.Web.UI;

namespace DingJing {
public class BaseTableControl : System.Web.UI.Control {

public void SetColumnVisibility(string colName, bool visible) {
Control header = FindControl(colName + "Header");
if (header != null)
header.Visible = visible;

Control[] rows = GetType().GetMethod("GetRecordControls").Invoke(this, null) as Control[];
foreach (Control row in rows) {
Control cell = row.FindControl(colName + "Cell");
if (cell != null)
cell.Visible = visible;
}
}

}
}


namespace ProjectNamespace.UI {
public class BaseApplicationTableControl : DingJing.BaseTableControl {


using System.Linq;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace DingJing {
public class BaseRecordControl : System.Web.UI.Control {

public void SetRowAppearance(string cssName) {
WebControl ctrl = Controls
.OfType()
.Where(c => !(c is Literal) && c.Visible)
.First();
string script = string.Format(
"$('td', $('#{0}').closest('tr')).addClass('{1}');",
ctrl.ClientID,
cssName);
ScriptManager.RegisterStartupScript(this, GetType(), ClientID, script, true);
}

}
}


namespace ProjectNamespace.UI {
public class BaseApplicationRecordControl : DingJing.BaseRecordControl {


Benefit


First, maintenance overhead is greatly reduced. You have only one copy of the source code to maintain. Other projects only need to update their reference if there is any changes. If they are already deployed to production, you only need to copy over one dll file.

Second, I no longer have to post my code samples in 2 languages. Even if your main ISD project is in VB, you can still reference libraries written in C#. If you are a consultant developing projects for different clients using different languages, this is also for you.

Conclusion


For general purpose customizations, refactoring them into a separate project will greatly reduce maintenance overhead.

1 comment:

Unknown said...

Hi, have you any email to contact with you?