Sunday, June 24, 2012

ISD Code Customization Tips, Part I: Post-Transaction Customization

Introduction

There are two ways to extend ISD-generated base methods:
  1. Call the base method, and insert custom code before or after the call.
  2. Cut and paste base method's code, and modify the pasted code.
ISD makes it easier to use the second approach, because the cut-and-paste is done automatically. From code maintenance point of view, however, the first approach is preferred. This is because, 
  1. Custom code is not mixed with ISD-generated code, making it easier to understand and maintain.
  2. Any changes in the base method are automatically propagated to the override method in the first approach. While in the second approach, you have to redo the customization. It is VERY easy to forget to redo the customization, and result in bugs.
However, it is not always possible to go with the first approach. Sometimes you have to insert your custom code into the middle of ISD code. For some of those cases related to transactions, I have a few tips to offer.

Tip #1: Post-Transaction Customization

It is quite common to add post-transaction customization. For example, you want to send out email notifications only if data is saved into database successfully. You cannot insert your code after calling SaveButton_Click_Base like this,
public void SaveButton_Click(object sender, EventArgs args) {
// Cannot insert here. Data not saved yet.
// Click handler for SaveButton.
// Customize by adding code before the call or replace the call to the Base function with your own code.
SaveButton_Click_Base(sender, args);
// NOTE: If the Base function redirects to another page, any code here will not be executed.
// Cannot insert here either.
// If data is saved successfully, the page is redirected. This code won't be executed.
}
You may think that you have to cut-and-paste all code from SaveButton_Click_Base, and then insert your code right after this.CommitTransaction(sender).
public void SaveButton_Click(object sender, EventArgs args) {
bool shouldRedirect = true;
string TargetKey = null;
string DFKA = TargetKey;
string id = DFKA;
string value = id;
try {
// Enclose all database retrieval/update code within a Transaction boundary
DbUtils.StartTransaction();
if (!this.IsPageRefresh) {
this.SaveData();
}
this.CommitTransaction(sender);
// Here is my custom code.
TargetKey = this.Page.Request.QueryString["Target"];
if (TargetKey != null) {
DFKA = NetUtils.GetUrlParam(this, "DFKA", false);
if (this.CategoriesRecordControl != null && this.CategoriesRecordControl.DataSource != null) {
id = this.CategoriesRecordControl.DataSource.CategoryID.ToString();
if (!String.IsNullOrEmpty(DFKA)) {
if (DFKA.Trim().StartsWith("=")) {
System.Collections.Generic.IDictionary<string, object> variables = new System.Collections.Generic.Dictionary<string, object>();
variables.Add(this.CategoriesRecordControl.DataSource.TableAccess.TableDefinition.TableCodeName, this.CategoriesRecordControl.DataSource);
value = EvaluateFormula(DFKA, this.CategoriesRecordControl.DataSource, null, variables);
} else {
value = this.CategoriesRecordControl.DataSource.GetValue(this.CategoriesRecordControl.DataSource.TableAccess.TableDefinition.ColumnList.GetByAnyName(DFKA)).ToString();
}
}
if (value == null) {
value = id;
}
string formula = this.Page.Request.QueryString["Formula"];
if (formula != null && formula != "") {
System.Collections.Generic.IDictionary<string, object> variables = new System.Collections.Generic.Dictionary<string, object>();
variables.Add(this.CategoriesRecordControl.DataSource.TableAccess.TableDefinition.TableCodeName, this.CategoriesRecordControl.DataSource);
value = EvaluateFormula(formula, this.CategoriesRecordControl.DataSource, null, variables);
}
BaseClasses.Utils.MiscUtils.RegisterAddButtonScript(this, TargetKey, id, value);
}
shouldRedirect = false;
}
} catch (Exception ex) {
// Upon error, rollback the transaction
this.RollBackTransaction(sender);
shouldRedirect = false;
this.ErrorOnPage = true;
// Report the error message to the end user
BaseClasses.Utils.MiscUtils.RegisterJScriptAlert(this, "BUTTON_CLICK_MESSAGE", ex.Message);
} finally {
DbUtils.EndTransaction();
}
if (shouldRedirect) {
this.ShouldSaveControlsToSession = true;
this.RedirectBack();
} else if (TargetKey != null && !shouldRedirect) {
this.ShouldSaveControlsToSession = true;
this.CloseWindow(true);
}
}
Here is the tip. You can avoid cut-and-paste by overriding CommitTransaction method.
public override void CommitTransaction(object sender) {
base.CommitTransaction(sender);
// Your custom code here.
}

Conclusion

Compare and see how the 2nd example greatly simplify the customization, making it much easier to maintain.