Introduction
Why intercept postbacks? Here is a use case. Your users ask for an Excel-like page to edit multiple records at the same time. No big deal! ISD's EditTable page looks just like an Excel sheet out of the box. You can implement the page in less than 5 minutes with minimal customization. However, there is a tricky issue. If a user pages/sorts/filters the table without saving existing changes, any unsaved changes will lost. It is not an acceptable solution to educate your users to always click the Save button before paging/sorting/filtering. There is no question that you have to save changes automatically. The question is how to implement it.
Brute-Force Implementation
A brute-force implementation is to add code to all event handlers of paging/sorting/filtering. For example,
Repeat this 4 times for PreviousPage, FirstPage, LastPage, and PageSizeButton. There are 10 sortable columns. Repeat 10 more times on the column headers' click event handlers. There are 5 filters above the table control. Repeat another 5 times. Even though you can refactor the try block into a method, so that it is reusable in the event handlers, there are still a lot of boilerplate code.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public partial class EditProductsTable2 | |
: BaseApplicationPage | |
// Code-behind class for the EditProductsTable2 page. | |
// Place your customizations in Section 1. Do not modify Section 2. | |
{ | |
#region "Section 1: Place your customizations here." | |
public EditProductsTable2() { | |
this.Initialize(); | |
Init += new EventHandler(EditProductsTable2_Init); | |
} | |
void EditProductsTable2_Init(object sender, EventArgs e) { | |
ProductsPagination.NextPage.Click += new ImageClickEventHandler(NextPage_Click); | |
} | |
void NextPage_Click(object sender, ImageClickEventArgs e) { | |
try { | |
DbUtils.StartTransaction(); | |
if (!this.IsPageRefresh) { | |
this.SaveData(); | |
} | |
this.CommitTransaction(sender); | |
} catch (Exception ex) { | |
// Upon error, rollback the transaction | |
this.RollBackTransaction(sender); | |
this.ErrorOnPage = true; | |
// Report the error message to the end user | |
BaseClasses.Utils.MiscUtils.RegisterJScriptAlert(this, "BUTTON_CLICK_MESSAGE", ex.Message); | |
} finally { | |
DbUtils.EndTransaction(); | |
} | |
} | |
... |
Repeat this 4 times for PreviousPage, FirstPage, LastPage, and PageSizeButton. There are 10 sortable columns. Repeat 10 more times on the column headers' click event handlers. There are 5 filters above the table control. Repeat another 5 times. Even though you can refactor the try block into a method, so that it is reusable in the event handlers, there are still a lot of boilerplate code.
Better Implementation
A better implementation is to intercept a postback earlier in page load event, and check which control initiates the postback. If it is one of the above mentioned controls, execute SaveData(). Otherwise, return execution to normal life cycle.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public partial class EditProductsTable | |
: BaseApplicationPage | |
// Code-behind class for the EditProductsTable page. | |
// Place your customizations in Section 1. Do not modify Section 2. | |
{ | |
#region "Section 1: Place your customizations here." | |
HashSet<string> AutoSaveCtrlIDs = new HashSet<string>(); | |
public EditProductsTable() { | |
this.Initialize(); | |
Init += new EventHandler(EditProductsTable_Init); | |
Load += new EventHandler(EditProductsTable_Load); | |
} | |
void EditProductsTable_Init(object sender, EventArgs e) { | |
// Register AutoSave controls | |
// Register pagination control | |
AutoSaveCtrlIDs.Add(ProductsPagination.NextPage.UniqueID); | |
AutoSaveCtrlIDs.Add(ProductsPagination.PreviousPage.UniqueID); | |
AutoSaveCtrlIDs.Add(ProductsPagination.FirstPage.UniqueID); | |
AutoSaveCtrlIDs.Add(ProductsPagination.LastPage.UniqueID); | |
AutoSaveCtrlIDs.Add(ProductsPagination.PageSizeButton.UniqueID); | |
// Register filters | |
AutoSaveCtrlIDs.Add(ProductsTableControl.CategoryIDFilter1.UniqueID); | |
AutoSaveCtrlIDs.Add(ProductsTableControl.SupplierIDFilter1.UniqueID); | |
// Other filters ... | |
// Register column sorting headers | |
AutoSaveCtrlIDs.Add(ProductNameLabel.UniqueID); | |
// Other columns ... | |
// Register any other controls ... | |
} | |
void EditProductsTable_Load(object sender, EventArgs e) { | |
if (IsPostBack) { | |
var pbCtrlID = GetPostbackTarget(); | |
if (AutoSaveCtrlIDs.Contains(pbCtrlID)) { | |
try { | |
DbUtils.StartTransaction(); | |
if (!this.IsPageRefresh) { | |
this.SaveData(); | |
} | |
this.CommitTransaction(sender); | |
} catch (Exception ex) { | |
// Upon error, rollback the transaction | |
this.RollBackTransaction(sender); | |
this.ErrorOnPage = true; | |
// Report the error message to the end user | |
BaseClasses.Utils.MiscUtils.RegisterJScriptAlert(this, "BUTTON_CLICK_MESSAGE", ex.Message); | |
} finally { | |
DbUtils.EndTransaction(); | |
} | |
} | |
} | |
} | |
string GetPostbackTarget() { | |
var uid = Request.Form["__EVENTTARGET"]; | |
if (uid == string.Empty) { | |
// ImageButton return UniqueId.x and UniquedId.y | |
var btn = Request.Form.AllKeys | |
.Where(k => k.EndsWith(".x") || k.EndsWith(".y")) | |
.Select(k => k.SubString(0, k.Length - 2)) | |
.Distinct() | |
.Where(k => FindControl(k) is ImageButton); | |
if (btn.Count() > 0) | |
uid = btn.First(); | |
} | |
return uid; | |
} | |
// ... | |
} |
Conclusion
If you find yourself doing the same thing again and again in many postback events, try intercepting the postbacks.