How to Create a Two-way Object Binding for ASP.NET WebForms
Posted by Randolph Cabral on Wednesday August 8, 2007
One of the features we spent a lot of time implementing is the concept of two-way object binding in ASP.NET. The idea was to create a closer analog to windows forms style development where objects are stateful and can be bound to HtmlControls and WebControls alike. After many cycles, we finally iterated to a successful implementation that is natural and easy to use.
Two-way object binding in ASP.NET with Jammer.NET is achieved by the use of serializable entities, the “PersistState” attribute, the “WebPageBase” class, and the “IAutoBindable” interface as shown in the code sample below.
1 using System;
2 using Jmr.Web.UI;
3 using Northwind.Entities;
4
5 public partial class Pages_JmrWebForm : WebPageBase, IAutoBindable
6 {
7 [PersistState] protected Customer _customer = null;
8
9 protected void Page_Load(object sender, EventArgs e)
10 {
11 if (!IsPostBack)
12 {
13 _customer = new Customer(1);
14 _customer.Load(); // Loads customer object with values from db.
15
16 BindBusEntity(txtName, “_customer.ContactName”);
17 DataBind();
18 }
19 }
20
21 protected void btnSave_Click(object sender, EventArgs e)
22 {
23 _customer.Save();
24 }
25 }
At first glance, this code file looks like any other until you examine the logic further. Line 5 is the first clue that something different is in the works. Firstly, the page derives from our very own “WebPageBase” class which can be found in the “Jmr.Web.UI” namespace. This class encapsulates many features including the ability to persist variables over postbacks and two-way object binding. Secondly, the “IAutoBindable” interface is added to the inheritance chain. This tells the base page class to enable binding logic as a part of page processing. We use the “IAutoBindable” interface as a switch and therefore does not actually implement any public members.
Line 16 is, as I like to say, where the “magic” happens. The “WebPageBase” class implements the “BindBusEntity()” method which accepts both HtmlControls and WebControls as the first argument, and a string representation of the named object and property name as the second argument. It is important to note that the object that is to be bound to the control must be declared at the class level and marked protected. It also must be persisted using the PersistState attribute. For more information about the “PersistState” attribute, please see How to Persist Object State Over Postbacks in ASP.NET WebForms. Lastly, you must call the “DataBind()” method of the base page to invoke binding logic. Take note, however, that the “DataBind()” method must only be called on initial page load only. Be sure to wrap the method call with an “IsPostback” check.
The event handler “btnSave_Click” is remarkably telling. Only one line is needed in the handler to call the “Save()” method of the “_customer” object. Remember, the “PersistState” attribute persists the object state automatically and the object binding was defined in the “Page_Load” handler on line 16 eliminating the need to re-hydrate the “_customer” object and set object values from the “txtName” control. All that’s left is to save the business object and if you’re using Jammer.NET entities, you get the “Save()” method automatically which updates the data store with the new values.
Clearly, this methodology indeed enables a more windows forms-like development experience for ASP.NET as the need to assign values manually or re-hydrate object state manually is eliminated. Yes, there will still be the case to use manual value assignments and manual object state persistence, but having these capabilities when you need it has proven to be a huge time-saver for us and we hope it will be for your development efforts too.
Saturday October 6, 2007 at 9:31 am
Dear(s),
First thank you for making your smart framework available for everyone.
1) While testing your framework, this line (BindBusEntity(txtName, “_customer.ContactName”)) is not binding to the textbox, although its persisting the customer object in viewstate and also its saving the changes, but I could not make it binding, please advise.
Thanks very much
Sam
Saturday October 6, 2007 at 7:35 pm
Dear(s),
Would you have more examples about how to bind a datagrid or a user control. Also expamples showing the business validation layer and businees logic layer.
Saturday October 6, 2007 at 10:35 pm
Hi Sam,
My apologies. There is a line missing from the code sample posted in this article. The “DataBind()” method of the base page must be called to activate business entity binding. I will correct the code sample immediately. Thanks for pointing this out!
And, yes, we will be adding more articles soon. We’ve just been so busy with business projects lately and haven’t had much time to polish off the other articles we’ve been working on.
-Randolph Cabral
Thursday May 29, 2008 at 2:39 pm
How can one validate the data while insertion, like we have a Status field in my table defaulted to 1 after generating entity class how can one complete insertion without passing this as parameter.
Note : Stored Procedure is expecting this value as parameter. We don’t like to send it as its defaulted during insertion.
Thursday May 29, 2008 at 2:40 pm
Can you please provide example of using Validate() method.
Friday May 30, 2008 at 1:47 pm
In this case, I would recommend overriding the base Save() method.
public override void Save(){
if (Status == 1 && this.IsLoadedFromDb)
Status == 0;
base.Save();
}
IsLoadedFromDb property is the flag used by the base class to determine whether to run the insert or update stored procedures.
The Validate() method is meant to be used with IValidator classes. Each entity has a protected member called Validators where you can implement your own validator if you wish. Note that calling the Validate() method will now throw exceptions. Instead, it puts all of the validation error objects in a ValidationErrors errors collection that you must implement as a part of the IEntityValidateable interface. Our typical usage of this feature looks like this:
public override void Save(){
Validate();
if (ValidationErrors.Count > 0)
{
StringBuilder sb = new StringBuilder();
foreach (var error in ValidationErrors)
{
sb.AppendLine(error.Description);
}
throw new Jmr.Common.ValidationException(sb.ToString());
}
base.Save();
}
-Randy