Monday, March 31, 2008

Boosting performance on aspx pages

Most of internet tutorials tells you to use Page Load event to bind your data to controls. What's happening when you have to rebind controls again because your data source is changed due to some event where you have to re bind controls.
Well, the answer is: use OnPrender event for binding your control.
Let's presume we have the following situation: we have a page where we have a list with a lot of items(say for example products) and a button where we add products


<asp:TextBox ID="txtProduct" runat="server"></asp:TextBox>
<asp:Button ID="btnAdd" runat="server" Text="Add product" Width="57px"
onclick="btnAdd_Click" />

<br />
<asp:ListBox ID="lstProducts" runat="server"></asp:ListBox>
The usual way(without optimization) of doing yhis would be like this:
1. We use session to keep products list read from the database as optimization(not to read products on page load each time)


List<string> Products
{
get
{
if (Session["Products"] != null)
{
return (List<string>)Session["Products"];
}
return null;
}
set
{
Session["Products"] = value;
}
}
Here comes the page load event (our first binding)



protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
//here we read products form database or whereever we need
//I'll just pun some dummy products in here
Products = new List<string>();
for (int i = 0; i < 20; i++)
{
Products.Add("Product " + i.ToString());
}
}
//Binding No 1
//here we bind products to the list
lstProducts.DataSource = Products;
lstProducts.DataBind();
Now suppose we click Add product, let's see what happens:
1. Page do a page load ad do the binding once.
2. Page execute click event, add the new product to the list then do a rebind of the list of products
In conclusion we have 2 bindings.



protected void btnAdd_Click(object sender, EventArgs e)
{
//here we add the new product form textbox on Add event
Products.Add(this.txtProduct.Text);
//our product list is changed so we have to rebind list control
//Binding No 2
lstProducts.DataSource = Products;
lstProducts.DataBind();
}
Well, imagine you have a page with a lot of controls, and some of you controls events must force you to rebind a lot. Imagine how this can affect page loading performance.

Now, let's see the optimized way of doing some data binding.
1.First we see our new page load method without data bind. Here we only read products first time a page is loaded.


protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
//here we read products form database or whereever we need
//I'll just pun some dummy products in here
Products = new List<string>();
for (int i = 0; i < 20; i++)
{
Products.Add("Product " + i.ToString());
}
}
}
You can see no databinding here.

2.Second we change the button click


protected void btnAdd_Click(object sender, EventArgs e)
{
//Here we only add product to the list
//here we add the new product form textbox on Add event
Products.Add(this.txtProduct.Text);
}


3. Third and last thing is to implement binding in OnPrerender event:


protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
//Here is the only time when we do the binding
lstProducts.DataSource = Products;
lstProducts.DataBind();
}
Well, you can see binding is done only once in page. This is only a demonstrative case, but imagine your page have a lot of controls to bind, what happens then?
kick it on DotNetKicks.com

Saturday, March 29, 2008

Web user controls design pattern and data binding part 1

A lot of people are using user controls in asp because they are very easy to use, have design time support and so on.
Anyway, because this controls are like a web page that you can add it in every page you want, you tend to do some mistakes and forget about user control design line. Most common mistakes are:
  1. put logic functionality into control
  2. forget about implementing events, and use basic control events like button click, dropdown SelectedIndexChanged, etc.
  3. not treat it as a control, implementing a data source, and a data bind mechanism
You will get into a lot of trouble , especially when page will be very complex and of course you will loose the flow of this page, spend many hours of debug to patch problems of logic and so on.

Whats the plan?
The purpose of this article is to design a user control for editing a user details object, save data, cancel editing or deleting the user object.
You can download web site here (use Save target as) or you can read on.
Notice: this is a website with VS 2008 and framework 3.5. It'll not work on VS 2005
  • Data Model
Let's say we have a user object defined as below
public enum Gender
{
Female = 0,
Male = 1
}
[Serializable()]
public class User
{
public int UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
public Gender UserGender { get; set; }
public string Phone { get; set; }
public string Address { get; set; }
public int Age { get; set; }

public User()
{
}
}
Notice that we use an enum for user gender.

Well we will first design the control (let's say it's called UserDetails.ascs). I don't think it's a good idea to put code below, because it's just some long text with asp tags, so we got to code behind to implement some guidelines or this user control.
Well in my opinion a control should have:
    • a datasource containing data to be displayed
    • a binding method that fill control with fed data
    • an unbinding method so we can grab user changes back to datasource
    • some custom events for Save, Cancel, Delete to handle this actions in main page (not in user control as I said above)
Well, let's go to work.
  1. Implement a datasource:
public User DataSource
{
get
{
if (ViewState[this.ClientID + "USER"] != null)
{
return (User)ViewState[this.ClientID + "USER"];
}
else
{
return null;
}
}
set
{
ViewState[this.ClientID + "USER"] = value;
}
}
2. Implement a binding method

/// <summary>
/// Populate controls with user values
/// </summary>
public override void DataBind()
{
//call base method
base.DataBind();

#region Fill controls

//populate dropdown with options for gender
ddlGender.Items.Clear();
foreach (string enumName in Enum.GetNames(typeof(Gender)))
{
int enumValue = Convert.ToInt32( Enum.Parse(typeof(Gender), enumName));
ListItem li = new ListItem(enumName, enumValue.ToString());
ddlGender.Items.Add(li);
}

#endregion

#region Bind Controls

this.txtFirstName.Text = DataSource.FirstName;
this.txtMiddleName.Text = DataSource.MiddleName;
this.txtLastName.Text = DataSource.LastName;
this.txtAge.Text = DataSource.Age.ToString();
this.ddlGender.SelectedValue = ((int)DataSource.UserGender).ToString();
this.txtAddress.Text = DataSource.Address;
this.txtPhone.Text = DataSource.Phone;

#endregion
}
As you can see we set control values with our DataSource(User object) values in here so we decide when we want some binding in the main page.

3. Implement an Unbind method
You can see here we set the control values changed by the user to our DataSource and we decide in the main page if we want to do the saving(save the control changed data source) to some other object, database, etc.
Notice: you can make this method public an call it from the main page also.

/// <summary>
/// Binds changed values from controls to DataSource(user object)
/// </summary>
private void UnBind()
{
DataSource.FirstName = this.txtFirstName.Text;
DataSource.MiddleName = this.txtMiddleName.Text;
DataSource.LastName = this.txtLastName.Text;
DataSource.Age = Int32.Parse( this.txtAge.Text);
DataSource.UserGender = (Gender)Int32.Parse(this.ddlGender.SelectedValue);
DataSource.Address = this.txtAddress.Text;
DataSource.Phone = this.txtPhone.Text;

//add user Id as command argument if needed in parent page
this.btnCancel.CommandArgument = DataSource.UserId.ToString();
this.btnDeleteUser.CommandArgument = DataSource.UserId.ToString();
this.btnSaveUser.CommandArgument = DataSource.UserId.ToString();
}
4. Implement some custom events.
As I said before this custom events will help you do the job in the parent page, where control is placed so let's implement some basic events this controls needs:
Declaring events is very simple:
public event EventHandler SaveUser;
public event EventHandler DeleteUser;
public event EventHandler CancelEdit;
and implementing them is also not hard to do:

protected virtual void OnSaveUser(object sender, EventArgs e)
{
if (this.SaveUser != null)
{
SaveUser(sender, e);
}
}

protected virtual void OnDeleteUser(object sender, EventArgs e)
{
if (this.DeleteUser != null)
{
DeleteUser(sender, e);
}
}

protected virtual void OnCancelEdit(object sender, EventArgs e)
{
if (this.CancelEdit != null)
{
CancelEdit(sender, e);
}
}
Now we just have to call this defined events on button click events (for btnSave, btnDelete, btnCancel):



protected void btnSaveUser_Click(object sender, EventArgs e)
{
//bind back values
this.UnBind();
//we treat saving event in the parent page not in control
this.OnSaveUser(sender, e);
}
protected void btnDeleteUser_Click(object sender, EventArgs e)
{
//we treat saving event in the parent page not in control
this.OnDeleteUser(sender, e);
}
protected void btnCancel_Click(object sender, EventArgs e)
{
//we treat saving event in the parent page not in control
this.OnCancelEdit(sender, e);
}
Please notice in Save click method I call Unbind to set DataSource with values user canged in textboxes, dropdowns, etc.

As you can see this is not hard to do, sometimes missing small details makes your pages hard to follow by others, or event by yourself after a while.

Using the control

Using the control in the age will be very easy job for us now. In my code I also added some status labels so you can see what's happening and some status values for User properties on the page.
I'll skip asp markup and go to the code behind.

Steps to do here:

1. Declare user control events in OnInit method:

protected override void OnInit(EventArgs e)
{
base.OnInit(e);
this.UserDetails1.SaveUser += new EventHandler(UserDetails1_SaveUser);
this.UserDetails1.DeleteUser += new EventHandler(UserDetails1_DeleteUser);
this.UserDetails1.CancelEdit += new EventHandler(UserDetails1_CancelEdit);
}
2. DataSource and DataBind
We first have to initialize the control with some data, the do the binding.
Suppose we have and object in the main page called SelectedUser, we obtained form database or whatever. All we have to to is:


protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
this.UserDetails1.DataSource = SelectedUser;
this.UserDetails1.DataBind();
}
You notice here I put this code OnPreRender method, this is because this is executed after all page user defined events like Save, Cancel, Delete. I did this because I don't want to call this code twice : page load and then user handled event. This way we only bind control once after all custom logic is executed.

3. Handle events in page.
We have only one step, decide what to do when user save, delete or cancel changes to this User control.



void UserDetails1_CancelEdit(object sender, EventArgs e)
{
//we cancel changes by databind control again with initial values
this.UserDetails1.DataSource = SelectedUser;
this.UserDetails1.DataBind();

lblAction.Text = "Cancel Edit";
}

void UserDetails1_DeleteUser(object sender, EventArgs e)
{
//do you delete logic here
lblAction.Text = "Delete User";
}

void UserDetails1_SaveUser(object sender, EventArgs e)
{
//set changed user value in user control to page User object
this.SelectedUser = this.UserDetails1.DataSource;
lblAction.Text = "Save User";
}
Well, I hope you enjoyed this reading.
kick it on DotNetKicks.com

Friday, March 28, 2008

ASP wwDataBinder with dictionaries and lists

Hello people,
This is my first article of my blog so I hope you find this helpful.
How many of you used wwDataBinder extender from MSDN in asp pages . If you did, maybe you wanted to bind some lists or dictionaries and had a surprise, it does not work.
I changed extender a little bit so you can use lists and dictionaries.
This can be a lot of help if you're using lists or dictionaries in your data model.
Well, you can download it from here as compiled version or source code (use Save target as).

Using is very simple:

<ww:wwDataBindingItem runat="server" BindingMode="TwoWay" DisplayFormat="{0:00.00}"
BindingSource="this" ControlId="TextBox33" BindingSourceMember="SomeDictionary[1]">
</ww:wwDataBindingItem>

I will come with another article regarding using this extender for those who does not know about this.
kick it on DotNetKicks.com