If you've built any reasonably sized website, I can all but guarantee that someone will utter the immortal words "Can this work a bit quicker?". You'll then spend days/weeks/months doing just that so, over the coming weeks I'm going to write a series of blogs to help with this. Each blog will work on a different area and today's area is page size.
From experience, I've found that this is one of the biggest factors (well, at least in terms of client performance). The smaller the page, the faster it is to download, the faster it is to render, it's just plain faster!
So, how do you go about reducing page size? Here's a few options...
UpdatePanels / AJAX / PageMethods
Well, firstly, use AJAX calls or UpdatePanels whenever possible. Both will only cause a small portion of your page to be sent to the client, rather than the whole page. This has a dramatic improvement. If you use UpdatePanels then you'll still have the overhead of the page life cycle but, if you use AJAX calls or PageMethods (which are just ajax calls) then you'll avoid this so it'll be even quicker.
Remove ViewState
If you ever view the HTML that an ASP.NET WebForms page produces, then you'll see a hidden field with the name __VIEWSTATE which will consist of a huge string. This string is how WebForms maintains state but, by passing it to the client each time, the size of the page is a lot bigger than it needs to be. So, we can do two things here, firstly, disable ViewState where ever you can. On every WebControl there's an EnableViewState property, setting that to false will disable ViewState for that control. Secondly, we can store the ViewState on the server rather than sending it to the client. Whenever I do this, I usually store the ViewState on the Session object. There's a few articles that will tell you how to do this, personally, I'd suggest reading this one before you start coding anything though: http://www.hanselman.com/blog/MovingViewStateToTheSessionObjectAndMoreWrongheadedness.aspx
Client IDs
If you're using ASP.NET 4.0 then you have the option of changing the ClientIdMode. This is a new feature and essentially lets you specify the exact ID of server controls when they're rendered on the client. In previous versions, if you had a control with an id of "example", then assuming it was the only control on the page, it'd be rendered with the id of "ctl00_example". If you then start getting nested controls you'll get the id of "ctl00_parentId_childId_example", as you can imagine, in large systems these ids can get pretty large, pretty fast. In .NET 4.0, you can set the ClientIDMode property of the page to be "static". When this is done, all the IDs will be rendered on the client with the id that was actually set on the server. So, if we had a control with an id of "example", then no matter where it was rendered, it'd always have the id of "example". No extra characters to make it unique. Obviously, you have to be a bit more careful when deciding on the IDs of your controls, you don't want any conflicts but just by changing that property, you can save yourself a significant amount of space.
Disable EventValidation
I'm a little reluctant to suggest this one but I'll mention it anyway. Essentially, on the Page object, there's a property called "EnableEventValidation". When set to true (which is the default), it'll validate any postback and callback events for invalid data. So, for example, it'll ensure that the value sent back for a DropDownList is actually present within the list and it'll ensure you're not trying to postback a value for a control that isn't visible. To help it do this, it sends data to the client in the form of a hidden field called "__EVENTVALIDATION". If you check the HTML of your page, you should be able to see it. Obviously, this takes up a few extra bytes that aren't strictly needed so, if you disable it, this hidden field disappears and your page size gets a little smaller but if you do this, I seriously suggest you re-implement the validation on the server. For more information regarding event validation, check out the MSDN article about it.
Minification
External javascript files and CSS files are also sent across the wire and can affect how responsive your site seems. To help with this, most files can benefit from being "minified". By this I mean that you'll give a tool a script, it'll strip out all the white space. It'll rename all the local variables into a one letter equivalent and essentially, it'll get rid of absolutely everything that isn't necessary, leaving you with the smallest possible file. There are plenty of tools out there that'll do this for you, for free. A quick google search revealed a few: Microsoft Minifier, Minify CSS
Image Size
Images are by far and away, the biggest files that'll be requested by a client on any normal web page request and if you don't try and optimize these, it makes all the above points a little pointless. Most images are bloated, they contain a lot of information that simply isn't needed and can be removed with no loss what so ever to quality. Yahoo's Smush It! is an excellent tool for image optimization and I strongly suggest you use it. You simply give it an image, it takes it, strips out all the unnecessary stuff and returns the smaller image, with no loss to quality. It's an excellent tool and should be a bookmark on every web developers computer.
Well, that should get you started. If you follow the above then you should see a dramatic decrease in your page sizes and hopefully, an increase in performance. In my next blog I'm going to talk about implementing loss-less HTTP compression using GZip and Deflate. These will decrease your page size even further!
Sunday, 6 March 2011
Monday, 28 February 2011
Response.Filter and UpdatePanels
Carrying on from my last blog, have you ever tried to use a response filter with UpdatePanels? If so, then I would imagine you've come across this error message:
"The message received from the server could not be parsed. Common causes for this error are when the response is modified by calls to Response.Write(), response filters, HttpModules, or server trace is enabled."
The problem is, the JavaScript receiving the text from the server will expect it in a certain format, as discussed in a previous post. If the text it receives doesn't conform to that format, or doesn't validate correctly (say for example, the length of the text sent doesn't match the length registered for it) then you'll get the above error.
There is a solution to this though, as we now know the format of the UpdatePanel response AND we know how response filters work, we can combine our knowledge of the two to do the following:
Step 1 - Capture all the output using Response.Filter
Using Response.Filter, we need to capture all the output but we've got to remember that Response.Filter uses chunking. Essentially, the page output will be cut up into "chunks" of around about 16kb, however, to create our UpdatePanelResponse objects, we'll need the whole output. So, we need to grab each chunk as it comes and "cache" it, when we've grabbed the entire output we can put it all back together and then use it to create our objects. There's already a very good implementation out there implemented by Rick Strahl (http://west-wind.com/weblog/posts/72596.aspx) so I'm going to use his implementation with one minor modification, I'm going to make it implement the following interface:
public interface IFilterStream
{
event Func<String, String> TransformString;
}
You'll see why a bit later. The thing I like about Rick's implementation is that he exposes a few events that are useful but for this particular example, the TransformWriteString event is the most important. This event is raised when all the output has been captured then allows you to call a method which receives a string and returns a string. The returned value is then the value that's returned to the client so, we can simply grab his implementation, map a handler to the TransformWriteString that'll do our output transformation and we're good to go. Nice and simple.
"The message received from the server could not be parsed. Common causes for this error are when the response is modified by calls to Response.Write(), response filters, HttpModules, or server trace is enabled."
The problem is, the JavaScript receiving the text from the server will expect it in a certain format, as discussed in a previous post. If the text it receives doesn't conform to that format, or doesn't validate correctly (say for example, the length of the text sent doesn't match the length registered for it) then you'll get the above error.
There is a solution to this though, as we now know the format of the UpdatePanel response AND we know how response filters work, we can combine our knowledge of the two to do the following:
- Capture all the output using Response.Filter, taking into account "chunking".
- Create "UpdatePanelResponse" objects which will parse the output into objects that the JavaScript on the client expects.
- Transform the output.
- Output it to the client using our "UpdatePanelResponse" object to structure the output into the format we need.
Using Response.Filter, we need to capture all the output but we've got to remember that Response.Filter uses chunking. Essentially, the page output will be cut up into "chunks" of around about 16kb, however, to create our UpdatePanelResponse objects, we'll need the whole output. So, we need to grab each chunk as it comes and "cache" it, when we've grabbed the entire output we can put it all back together and then use it to create our objects. There's already a very good implementation out there implemented by Rick Strahl (http://west-wind.com/weblog/posts/72596.aspx) so I'm going to use his implementation with one minor modification, I'm going to make it implement the following interface:
public interface IFilterStream
{
event Func<String, String> TransformString;
}
You'll see why a bit later. The thing I like about Rick's implementation is that he exposes a few events that are useful but for this particular example, the TransformWriteString event is the most important. This event is raised when all the output has been captured then allows you to call a method which receives a string and returns a string. The returned value is then the value that's returned to the client so, we can simply grab his implementation, map a handler to the TransformWriteString that'll do our output transformation and we're good to go. Nice and simple.
Step 2 - Create "UpdatePanelResponse" objects
Ok, the plan here is to parse the output string into objects that represent the UpdatePanel response string (essentially, the diagram included in this post). Once we've created all the objects, we need a way of performing transformations on the text and then writing all of it back out again in the format that the ScriptManager on the client will understand.
So, first off, lets define the class that'll represent an UpdatePanel response string...
internal class UpdatePanelFormat
{
internal UpdatePanelFormat()
{
}
internal Func<String, String> TransformMethod
{
get;
set;
}
internal string Text
{
get;
set;
}
internal int Length
{
get
{
return this.Text.Length;
}
}
internal string Info
{
get;
set;
}
internal string Type
{
get;
set;
}
private bool _hasTransformed = false;
private void Transform()
{
if (!_hasTransformed)
{
this.Text = this.TransformMethod(this.Text);
_hasTransformed = true;
}
}
public override string ToString()
{
this.Transform();
return this.Length + "|" + this.Type + "|" + this.Info + "|" + this.Text + "|";
}
}
As you can see, this object defines the four components of an UpdatePanel response string. It also gives us a method to transform the data by defining a delegate which takes a string and also returns one. The idea being that the string coming into the method will be the original text and the string being returned will be the string that we've transformed. Finally, the ToString method returns the string in it's expected format.
So, now we have that, we need a class that will go through the entire output string and create these objects. So, here's my implementation of that. Bare in mind that the above class is defined as internal. The only other class within my implementation that is within the same assembly as that class is the one defined below.
public class UpdatePanelResponse
{
private static UpdatePanelResponse _instance;
public static UpdatePanelResponse Instance
{
get
{
if (_instance == null)
_instance = new UpdatePanelResponse();
return _instance;
}
}
public Func<String, String> Transform
{
get;
set;
}
public string GetTransformedText(string responseText)
{
List<UpdatePanelFormat> list = this.CreateIndividualFormat(responseText);
StringBuilder sb = new StringBuilder();
foreach (UpdatePanelFormat fmt in list)
{
sb.Append(fmt.ToString());
}
return sb.ToString();
}
private List<UpdatePanelFormat> CreateIndividualFormat(string text)
{
string[] components = text.Split('|');
List<UpdatePanelFormat> callbacks = new List<UpdatePanelFormat>();
for (int i = 0; i < components.Length - 1; i = i + 4)
{
UpdatePanelFormat cb = new UpdatePanelFormat();
cb.TransformMethod = Transform;
if (i + 1 < components.Length)
cb.Type = components[i + 1];
if (i + 2 < components.Length)
cb.Info = components[i + 2];
if (i + 3 < components.Length)
cb.Text = components[i + 3];
if (i + 4 < components.Length)
{
int j = i + 4;
StringBuilder sb = new StringBuilder(cb.Text);
while (true)
{
if (j >= components.Length -1)
break;
int t;
string v = components[j];
if (Int32.TryParse(v, out t))
break;
sb.Append("|" + v); // Add the | that we split by.
j++;
i++;
}
cb.Text = sb.ToString();
}
callbacks.Add(cb);
}
return callbacks;
}
}
Ok, so, again, we have our delegate which takes and returns a string. This is passed on to our UpdatePanelFormat class. Remember, this is our outward facing class (the other is defined as internal). Then we have our CreateIndividualFormat method. This essentially goes through the entire string, splitting by the | character which is the separator for each individual piece of information and then creating our UpdatePanelFormat objects with this information. Finally, we have our GetTransformedText method. This takes the entire output string, passes it to our CreateIndividualFormat method which will create all of our objects. It'll then go through each of these objects, appending the transformed string to a StringBuilder instance and will then, finally, return the entire transformed text.
Step 3 and 4
We now have a method of formatting our output string, now all we need to do is give our UpdatePanelResponse object a Transform method to work with and then to send it all back to the client. We do both of these things when we map everything together within the global.asax.cs. In any application I've ever dealt with, I have an UpdatePanel surround virtually everything and then other UpdatePanels within that, so, if a postback is ever made, it will always run through an UpdatePanel, with that knowledge, we can do something like this:
void Application_BeginRequest(object sender, EventArgs e)
{
HttpApplication app = sender as HttpApplication;
if (app.Request.FilePath.EndsWith(".aspx"))
{
IFilterStream s;
if (app.Request.UrlReferrer != null && app.Request.UrlReferrer.AbsolutePath == app.Request.Url.AbsolutePath)
{
s = new ResponsePostbackStream(app.Response.Filter);
s.TransformString += new Func<string, string>(p_TransformString);
}
else
{
s = new FilterStream(app.Response.Filter);
s.TransformString += new Func<string, string>(s_TransformString);
}
app.Response.Filter = (System.IO.Stream)s;
}
}
string p_TransformString(string arg)
{
UpdatePanelResponse.Instance.Transform = s_TransformString;
return UpdatePanelResponse.Instance.GetTransformedText(arg);
}
string s_TransformString(string arg)
{
return arg.Replace("/TestSite/", "/LiveEnv/");
}
As you can see, if we're NOT a postback, I use the same implementation as defined within http://clementscode.blogspot.com/2011/02/responsefilter-what-how-and-why.html except within the Write method, I raise the TransformString event and I also make the class implement the IFilterStream interface. This maps directly to s_TransformString which will replace all instances of /TestSite/ with /LiveEnv/. If we are a postback then we need to do a little bit more work, essentially, we map to p_TransformString which uses the UpdatePanelResponse object we've just created. It sets the Transform delegate to s_TranformWriteString so the exact same transformation that is taking place on normal requests, is also happening on postbacks. It then grabs and returns the transformed text which is output to the client.
And we're done! The JavaScript error will now longer appear.
Just bare in mind when using this that it'll obviously affect performance. How much will depend on your site, how big the pages are and what your transformations actually consist of so you should probably test it first to assess the impact. Equally, it could also have implications on your applications memory usage, the ResponseFilterStream implementation can use a fair amount of memory, as Rick describes in his blog post, again, you should monitor this and make sure it doesn't affect your application too much.
There's a fair amount of code there, some of which I may not have described very well so here's the source code zipped up.
Feel free to play around with it till your hearts content.
Sunday, 20 February 2011
Response.Filter - What? How? And Why?
Response.Filter allows you to change the HTML that is output to the client after .NET has processed everything. It essentially gives you a central place to make any last second changes.
So, what's the use of this? Well, there's actually quite a few uses. For example, say you've developed your web application for a test environment and so, all your hyperlinks look something like: http://www.example.com/Test/APage.aspx. Then, when you go to a live environment, you may want to change all of those links to http://www.example.com/Live/APage.aspx. Well, you could go through your entire application and change the links where necessary, or, you could just add a response filter and have that take care of it.
There's a few ways you can assign the filter but my personal favourite is to assign it within the Application_BeginRequest method within the global.asax file1. So, you end up with a method that looks like this:
void Application_BeginRequest(object sender, EventArgs e)
{
HttpApplication app = sender as HttpApplication;
if(app.Request.FilePath.EndsWith(".aspx"))
app.Response.Filter = new Filter(app.Response.Filter);
}
We check to make sure that the FilePath is an actual .aspx page as every request dealt with by the application will pass through this method. That means every request for an image, javascript file, css file or any other file you can think of, will pass through here and the chances are, you'll only want to apply your logic when requesting aspx pages.
Now, we need to create the Filter class. The purpose of this class is to take the HTML that the application will create and modify it to what we need, then pass that back out to the client, so in this example, we want to search for "/Test/" and change it to "/Live/".
The Filter class should inherit from Stream and so, a simplistic version would be the following:
public class Filter : Stream
{
Stream s;
public Filter(Stream stream)
{
this.s = stream;
}
public override bool CanRead
{
get { return s.CanRead; }
}
public override bool CanSeek
{
get { return s.CanSeek; }
}
public override bool CanWrite
{
get { return s.CanWrite; }
}
public override void Flush()
{
s.Flush();
}
public override long Length
{
get { return s.Length; }
}
public override long Position
{
get
{
return s.Position;
}
set
{
s.Position = value;
}
}
public override int Read(byte[] buffer, int offset, int count)
{
return s.Read(buffer, offset, count);
}
public override long Seek(long offset, System.IO.SeekOrigin origin)
{
return s.Seek(offset, origin);
}
public override void SetLength(long value)
{
s.SetLength(value);
}
public override void Write(byte[] buffer, int offset, int count)
{
String text = System.Text.Encoding.UTF8.GetString(buffer, offset, count);
text = text.Replace("/Test/", "/Live/");
byte[] buff = System.Text.Encoding.UTF8.GetBytes(text);
s.Write(buff, 0, buff.Length);
}
}
We've now ensured every "Test" link will change to a "Live" link, in a single, central location. This means that if in the future it needs to change again, it's a very simple change. As you can probably guess, this feature has the potential to make your life a whole lot easier and it opens up a few doors that previously, weren't available to us, well, not without a bit of work anyway. One big use that I've seen it used for is to reduce the amount of unnecessary white space that's sent down the wire, doing such a thing will decrease the amount data sent which should speed up download time.2
Now that you know how to use the Response.Filter, there's a couple of things that you should know before you start working with that property. These things have the potential to cause a few problems that are a little difficult to get around if you don't know what's going on, so I've listed the issues below.
1 For more information about global.asax, check out this MSDN article http://msdn.microsoft.com/en-us/library/2027ewzw.aspx
2 A good implementation of this can be found at http://www.codeproject.com/KB/aspnet/RemovingWhiteSpacesAspNet.aspx
So, what's the use of this? Well, there's actually quite a few uses. For example, say you've developed your web application for a test environment and so, all your hyperlinks look something like: http://www.example.com/Test/APage.aspx. Then, when you go to a live environment, you may want to change all of those links to http://www.example.com/Live/APage.aspx. Well, you could go through your entire application and change the links where necessary, or, you could just add a response filter and have that take care of it.
There's a few ways you can assign the filter but my personal favourite is to assign it within the Application_BeginRequest method within the global.asax file1. So, you end up with a method that looks like this:
void Application_BeginRequest(object sender, EventArgs e)
{
HttpApplication app = sender as HttpApplication;
if(app.Request.FilePath.EndsWith(".aspx"))
app.Response.Filter = new Filter(app.Response.Filter);
}
We check to make sure that the FilePath is an actual .aspx page as every request dealt with by the application will pass through this method. That means every request for an image, javascript file, css file or any other file you can think of, will pass through here and the chances are, you'll only want to apply your logic when requesting aspx pages.
Now, we need to create the Filter class. The purpose of this class is to take the HTML that the application will create and modify it to what we need, then pass that back out to the client, so in this example, we want to search for "/Test/" and change it to "/Live/".
The Filter class should inherit from Stream and so, a simplistic version would be the following:
public class Filter : Stream
{
Stream s;
public Filter(Stream stream)
{
this.s = stream;
}
public override bool CanRead
{
get { return s.CanRead; }
}
public override bool CanSeek
{
get { return s.CanSeek; }
}
public override bool CanWrite
{
get { return s.CanWrite; }
}
public override void Flush()
{
s.Flush();
}
public override long Length
{
get { return s.Length; }
}
public override long Position
{
get
{
return s.Position;
}
set
{
s.Position = value;
}
}
public override int Read(byte[] buffer, int offset, int count)
{
return s.Read(buffer, offset, count);
}
public override long Seek(long offset, System.IO.SeekOrigin origin)
{
return s.Seek(offset, origin);
}
public override void SetLength(long value)
{
s.SetLength(value);
}
public override void Write(byte[] buffer, int offset, int count)
{
String text = System.Text.Encoding.UTF8.GetString(buffer, offset, count);
text = text.Replace("/Test/", "/Live/");
byte[] buff = System.Text.Encoding.UTF8.GetBytes(text);
s.Write(buff, 0, buff.Length);
}
}
We've now ensured every "Test" link will change to a "Live" link, in a single, central location. This means that if in the future it needs to change again, it's a very simple change. As you can probably guess, this feature has the potential to make your life a whole lot easier and it opens up a few doors that previously, weren't available to us, well, not without a bit of work anyway. One big use that I've seen it used for is to reduce the amount of unnecessary white space that's sent down the wire, doing such a thing will decrease the amount data sent which should speed up download time.2
Now that you know how to use the Response.Filter, there's a couple of things that you should know before you start working with that property. These things have the potential to cause a few problems that are a little difficult to get around if you don't know what's going on, so I've listed the issues below.
- ASP.NET uses "chunking" to output pages. By this I mean, that the Write method of the Filter class defined above, will be called for every "chunk" of data, which I think is around 16kb. So, if the HTML that ASP.NET creates is bigger than 16kb, then you can sometimes see odd behaviour. Taking the above example, what if the "/Te" happened to be the last characters of one chunk and "st/" was the beginning of the next. Our simple replace wouldn't match and you'd still see the /Test/ link. If you do have pages bigger than 16kb then you really need to capture all the output, then perform your transformations, and then output everything, rather than outputting small pieces at a time. A fantastic implementation of this can be found here: http://west-wind.com/weblog/posts/72596.aspx.
- Using Response.Filter can cause serious problems with UpdatePanels unless you know what you're doing. In my previous blog post I showed you the format of the response text for an UpdatePanel callback, in it, it includes a Length property which indicates the length of the text being sent across the wire. If you then modify the text so it has more/less characters than it originally did, the actual length of the text won't match up with the value that indicated the length and the application will throw an error. I'll propose a method of fixing this problem in my next blog.
And that's about it, a very straight forward example of Response.Filter and some of it's uses. Enjoy!
2 A good implementation of this can be found at http://www.codeproject.com/KB/aspnet/RemovingWhiteSpacesAspNet.aspx
Wednesday, 26 January 2011
UpdatePanels - Response Text
So, I've covered the basics of UpdatePanels in a previous post but what exactly makes them so much faster than just normal postbacks? Well the answer is that when the server responds, it doesn't respond with the full HTML of the whole page, instead, it'll respond with only the HTML that it needs to be able to update the content of each UpdatePanel.
This is done using a special format that is read, understood and executed by JavaScript at the client. An example of this response text is here:
175|updatePanel|panel|
<input name="textbox" type="text" value="Foo" id="textbox" />
<input type="submit" name="button" value="Submit" id="button" />
|88|hiddenField|__VIEWSTATE|/wEPDwUKMTg0NjEyNDk1MQ9kFgICAw9kFgICAQ8PFgIeB1Zpc2libGVoZGRkcMaCINs5w9oczWUhLKPVgiBR3uk=|64|hiddenField|__EVENTVALIDATION|/wEWBAKc1c6BDgKsyt74CwLz9r7ABAKw3cLDCSDRvcYZVa7GBSZW2zBXlJJG8Vsa|0|asyncPostBackControlIDs|||0|postBackControlIDs|||16|updatePanelIDs||tpanel,tpanelTwo|0|childUpdatePanelIDs|||5|panelsToRefreshIDs||pa
nel|2|asyncPostBackTimeout||90|12|formAction||Default.aspx|13|pageTitle||Untitled Page|
Now this actually has a specific format, even if it doesn't seem so at first glance. Essentially, it's a concatenation of strings, the breakdown of each string is represented in the diagram below:
In the example above, you can tell roughly, what's going to happen to the page, just by looking at the response text. The content of the update panel whose id is "panel", will be changed to now contain the textbox and submit buttons as defined by the HTML in the response text. It includes the new ViewState value, the IDs of any other update panels contained within the page, the IDs of the update panels to be refreshed and then a few other page level properties like the form action and the window title.
Now we know the format of this response text, we can make use of that knowledge if we ever need to transform our output from the server to the client. For example, you may want to remove all unnecessary white space from the response, or, you may need to change a URL reference and rather than finding each and every instance, you could just change it at the very end of the page life cycle. I'll explain how to do all of this in a future post!
Labels:
ASP.NET,
format,
response,
update,
UpdatePanel
Saturday, 22 January 2011
The Beauty of UpdatePanels
So, my first ever technical blog post… here goes…
UpdatePanels are, in a word, fantastic. For those of you that haven’t had the pleasure of using them before, they give an ASP.NET WebForms developer a simple way of performing AJAX calls. This allows you to update parts of your web page, requiring no page load. Which means no white flicker as the page re-renders and, more importantly, it’s faster than a full page load!
So, how do you use one? Well, an UpdatePanel can get a little advanced, but the basics are as follows…
Firstly, you need a ScriptManager control. These things are pretty cool too, they provide the JavaScript functions needed for an UpdatePanel to work, they also allow you to use PageMethods (that’s a future blog topic) and make globalization a little easier.
Secondly, unsurprisingly, you need your UpdatePanel control. This needs to be wrapped around the content you want to update using an ajax call. So, as an example, take the following:
<asp:TextBox ID="tb" runat="server" Text="Hello" />
<asp:ScriptManager ID="scriptManager" runat="server">
</asp:ScriptManager>
<asp:UpdatePanel ID="panel" runat="server" UpdateMode="Conditional">
<ContentTemplate>
<asp:TextBox ID="textbox" runat="server" Text="Foo" />
<asp:Button ID="button" runat="server" Text="Submit" />
</ContentTemplate>
</asp:UpdatePanel>
So, now, any control that causes a postback that sits inside the UpdatePanel will cause a partial page postback, using an AJAX call, and only the content of the UpdatePanel will be updated. So, using that knowledge, I'm now going to place the following handler on the click event of the button:
void button_Click(object sender, EventArgs e)
{
this.textbox.Text += " Bar";
this.tb.Text += " World";
}
What do you think will happen?
Yep, you're right. The TextBox outside the panel will still read "Hello" but the TextBox inside will read "Foo Bar". You'll notice that the full page life cycle still takes place, it has to to ensure the controls object gets created, however, the data sent back to the client only includes the data used to update the UpdatePanel, this leads to a significant amount of time and bandwidth saved.
Now, at first, this sounds fantastic, but it can result in a few odd errors, one of which I'll show you now. The errors stem from the problem that when you change certain properties of controls outside of the update panel you're updating, then the server expects these controls to retain these properties on postback, however, the data it's sent back from the client is still holding the old properties and so, we have a problem.
Take this example, if we change the button_Click method to this:
void button_Click(object sender, EventArgs e)
{
this.tb.Visible = false;
}
Now, when you hit the button twice, you'll get a JavaScript error message (Assuming you have debugging turned on) which states the following:
Sys.WebForms.PageRequestManagerServerErrorException: Invalid postback or callback argument. Event validation is enabled using <pages enableEventValidation="true"/> in configuration or <%@ Page EnableEventValidation="true" %> in a page. For security purposes, this feature verifies that arguments to postback or callback events originate from the server control that originally rendered them. If the data is valid and expected, use the ClientScriptManager.RegisterForEventValidation method in order to register the postback or callback data for validation.
Cutting a long story short, the data sent from the client, to the server, includes a textbox that the server thinks shouldn't be there (you've just made it invisible after all.) and so, when it tries to validate the data, it fails. This can happen with other controls too, with different properties, for example, if you change the Items within a DropDownList and don't update then that can cause problems. It's just something you have to look out for. In complicated systems, it can become a bit of a pain.
Anyway, the last, very helpful feature of UpdatePanels is that you can force an update of any UpdatePanel, take this code for an example, we add an extra UpdatePanel to the code above:
<asp:UpdatePanel ID="panelTwo" runat="server" UpdateMode="Conditional">
<ContentTemplate>
<asp:TextBox ID="textboxTwo" runat="server" Text="Code" />
</ContentTemplate>
</asp:UpdatePanel>
Now, if we change the button_click method to this:
void button_Click(object sender, EventArgs e)
{
this.textbox.Text += " Bar";
this.label.Text += " World";
this.textboxTwo.Text += " Bye";
this.panelTwo.Update();
}
Both UpdatePanels will be updated. It's also possible to ensure that an UpdatePanel is updated on every postback by changing the UpdateMode to Always.
Ok, that's my brief overview. I've missed out A LOT of information, some of which I'll cover in detail in future blogs but hopefully that should be enough to get you started.
Let the AJAX fun begin!
For further reading, try some of these articles:
http://msdn.microsoft.com/en-us/library/bb386454.aspx
http://msdn.microsoft.com/en-us/magazine/cc163413.aspx#S1
http://www.asp.net/ajax/tutorials/understanding-asp-net-ajax-updatepanel-triggers
UpdatePanels are, in a word, fantastic. For those of you that haven’t had the pleasure of using them before, they give an ASP.NET WebForms developer a simple way of performing AJAX calls. This allows you to update parts of your web page, requiring no page load. Which means no white flicker as the page re-renders and, more importantly, it’s faster than a full page load!
So, how do you use one? Well, an UpdatePanel can get a little advanced, but the basics are as follows…
Firstly, you need a ScriptManager control. These things are pretty cool too, they provide the JavaScript functions needed for an UpdatePanel to work, they also allow you to use PageMethods (that’s a future blog topic) and make globalization a little easier.
Secondly, unsurprisingly, you need your UpdatePanel control. This needs to be wrapped around the content you want to update using an ajax call. So, as an example, take the following:
<asp:TextBox ID="tb" runat="server" Text="Hello" />
<asp:ScriptManager ID="scriptManager" runat="server">
</asp:ScriptManager>
<asp:UpdatePanel ID="panel" runat="server" UpdateMode="Conditional">
<ContentTemplate>
<asp:TextBox ID="textbox" runat="server" Text="Foo" />
<asp:Button ID="button" runat="server" Text="Submit" />
</ContentTemplate>
</asp:UpdatePanel>
So, now, any control that causes a postback that sits inside the UpdatePanel will cause a partial page postback, using an AJAX call, and only the content of the UpdatePanel will be updated. So, using that knowledge, I'm now going to place the following handler on the click event of the button:
void button_Click(object sender, EventArgs e)
{
this.textbox.Text += " Bar";
this.tb.Text += " World";
}
What do you think will happen?
Yep, you're right. The TextBox outside the panel will still read "Hello" but the TextBox inside will read "Foo Bar". You'll notice that the full page life cycle still takes place, it has to to ensure the controls object gets created, however, the data sent back to the client only includes the data used to update the UpdatePanel, this leads to a significant amount of time and bandwidth saved.
Now, at first, this sounds fantastic, but it can result in a few odd errors, one of which I'll show you now. The errors stem from the problem that when you change certain properties of controls outside of the update panel you're updating, then the server expects these controls to retain these properties on postback, however, the data it's sent back from the client is still holding the old properties and so, we have a problem.
Take this example, if we change the button_Click method to this:
void button_Click(object sender, EventArgs e)
{
this.tb.Visible = false;
}
Now, when you hit the button twice, you'll get a JavaScript error message (Assuming you have debugging turned on) which states the following:
Sys.WebForms.PageRequestManagerServerErrorException: Invalid postback or callback argument. Event validation is enabled using <pages enableEventValidation="true"/> in configuration or <%@ Page EnableEventValidation="true" %> in a page. For security purposes, this feature verifies that arguments to postback or callback events originate from the server control that originally rendered them. If the data is valid and expected, use the ClientScriptManager.RegisterForEventValidation method in order to register the postback or callback data for validation.
Cutting a long story short, the data sent from the client, to the server, includes a textbox that the server thinks shouldn't be there (you've just made it invisible after all.) and so, when it tries to validate the data, it fails. This can happen with other controls too, with different properties, for example, if you change the Items within a DropDownList and don't update then that can cause problems. It's just something you have to look out for. In complicated systems, it can become a bit of a pain.
Anyway, the last, very helpful feature of UpdatePanels is that you can force an update of any UpdatePanel, take this code for an example, we add an extra UpdatePanel to the code above:
<asp:UpdatePanel ID="panelTwo" runat="server" UpdateMode="Conditional">
<ContentTemplate>
<asp:TextBox ID="textboxTwo" runat="server" Text="Code" />
</ContentTemplate>
</asp:UpdatePanel>
Now, if we change the button_click method to this:
void button_Click(object sender, EventArgs e)
{
this.textbox.Text += " Bar";
this.label.Text += " World";
this.textboxTwo.Text += " Bye";
this.panelTwo.Update();
}
Both UpdatePanels will be updated. It's also possible to ensure that an UpdatePanel is updated on every postback by changing the UpdateMode to Always.
Ok, that's my brief overview. I've missed out A LOT of information, some of which I'll cover in detail in future blogs but hopefully that should be enough to get you started.
Let the AJAX fun begin!
For further reading, try some of these articles:
http://msdn.microsoft.com/en-us/library/bb386454.aspx
http://msdn.microsoft.com/en-us/magazine/cc163413.aspx#S1
http://www.asp.net/ajax/tutorials/understanding-asp-net-ajax-updatepanel-triggers
Tuesday, 18 January 2011
The Start of Something Beautiful?
So, I've finally got around to making my own blog. Why have I decided to do such a thing? Well, I thought it was about time I gave something back to the community.
Let me tell you a little about myself. I've always been interested in programming, ever since I've known what a computer was. Around the age of 14, our family invested in a computer that could actually connect to the internet, back then the greatest connection you could get was over a 56k modem, but none the less, I loved it. As soon as I could connect to the internet, I started to wonder.... how do hit counters work? How does the time get stamped on the Yahoo! website? Come to think of it, how on earth do you make a website? And so, I set out to learn such things, I looked up articles online, I bought books and over a period of time, I taught myself JavaScript, HTML, PERL and PHP. This knowledge would stand me in good stead for the future, the JavaScript and HTML proving to be invaluable in my current job
Well, I then and got a few A Levels and headed off to the University of Essex to study Computer Science where, I tried to study just about every programming course I could get my hands on but, due to my fascination with the Internet, I chose to specialize in Internet Technology where I had the pleasure of looking at C, C++, Ruby on Rails, Java (and the internet tech that uses Java e.g. servlets, JSP, applets) and finally, ASP.NET.
I graduated University with a first class degree with honours and set out into the big wide world where a company that specializes in property management software was kind enough to hire me as a software developer. When I first started, they were primarily using Oracle Forms, a technology so old, I hadn't even heard of it. Fortunately, plans had already been made to migrate from Oracle Forms to a web application using ASP.NET WebForms as the platform to develop for. I was fortunate enough to be involved in that project. That project started 2.5 years ago and is still on going, as I'm sure you can imagine, moving ASP.NET suddenly gave us a whole range of things that could be done which just wasn't possible on the old technology stack so, we started implementing cool new features. My internet technology background put me in a good position for this and so I've been involved in developing things that, if I'm honest, I didn't even know was possible 2 years ago. These mini projects have thrown up all sorts of issues, some of which, I think, would be beneficial to share with the community.
Maybe someone else will stumble across the same problem(s) and use this blog to find a solution, maybe someone can come up with a better solution, maybe someone can find ways to improve my solutions. These are the reasons I want to share my experiences with you and so, Clements Code (a name which I truly hate by the way) was born.
Let me tell you a little about myself. I've always been interested in programming, ever since I've known what a computer was. Around the age of 14, our family invested in a computer that could actually connect to the internet, back then the greatest connection you could get was over a 56k modem, but none the less, I loved it. As soon as I could connect to the internet, I started to wonder.... how do hit counters work? How does the time get stamped on the Yahoo! website? Come to think of it, how on earth do you make a website? And so, I set out to learn such things, I looked up articles online, I bought books and over a period of time, I taught myself JavaScript, HTML, PERL and PHP. This knowledge would stand me in good stead for the future, the JavaScript and HTML proving to be invaluable in my current job
Well, I then and got a few A Levels and headed off to the University of Essex to study Computer Science where, I tried to study just about every programming course I could get my hands on but, due to my fascination with the Internet, I chose to specialize in Internet Technology where I had the pleasure of looking at C, C++, Ruby on Rails, Java (and the internet tech that uses Java e.g. servlets, JSP, applets) and finally, ASP.NET.
I graduated University with a first class degree with honours and set out into the big wide world where a company that specializes in property management software was kind enough to hire me as a software developer. When I first started, they were primarily using Oracle Forms, a technology so old, I hadn't even heard of it. Fortunately, plans had already been made to migrate from Oracle Forms to a web application using ASP.NET WebForms as the platform to develop for. I was fortunate enough to be involved in that project. That project started 2.5 years ago and is still on going, as I'm sure you can imagine, moving ASP.NET suddenly gave us a whole range of things that could be done which just wasn't possible on the old technology stack so, we started implementing cool new features. My internet technology background put me in a good position for this and so I've been involved in developing things that, if I'm honest, I didn't even know was possible 2 years ago. These mini projects have thrown up all sorts of issues, some of which, I think, would be beneficial to share with the community.
Maybe someone else will stumble across the same problem(s) and use this blog to find a solution, maybe someone can come up with a better solution, maybe someone can find ways to improve my solutions. These are the reasons I want to share my experiences with you and so, Clements Code (a name which I truly hate by the way) was born.
Subscribe to:
Posts (Atom)