We officially rolled out a release of the “My” Mimeo.com web application this weekend. While we actually added some really great customer features, one of the biggest things in this release development wise was a full upgrade to use several features of ASP.NET 2.0 that were previously solved by a bunch of proprietary code that I wrote and the famous Wilson MasterPages. Here’s a rundown of features we are now leveraging as opposed to relying on third party:
- Master Pages — we used Wilson MasterPages, they served us well 🙂
- Validation Groups — I had written a method called ValidateContainer which would walk the entire hierarchy of a container (like a Panel) that was passed in as a parameter, find each validator, execute it and return a boolean indicating if any of them had failed.
- ClientScriptManager — we were of course using the Page level method that were the only thing available in .NET 1.1, since the methods were basically the same on ClientScriptManager this was pretty much a straight forward conversion. For most people the thing that ClientScriptManager provides is guarenteed order of output. Since we had already solved this problem by ensuring that we do things in the client in window::onload we didn’t really gain anything from that. That said, it’s nice to know that we can count on it now. Then again we’re moving to Atlas and using the ScriptManager now, so we’ll probably be off of the ClientScriptManager soon.
- HtmlHead Control — we already had a <head runat=”server”> in our MasterPage, so what this did is prevent us from having to call FindControl to get it. Now we just use Page.Head and we’re all set.
- Resourcing — This was the biggest change for us. I knew Microsoft was working on this for ASP.NET 2.0, so I implemented our proprietary version using the same approach so that conversion would be easy when we were finally ready to convert… and it was! We kept our .resx files in directories called LocalResource and GlobalResources (MS later changed to add the App_ prefix, but that was no problem). I had a set of methods on our base Page and Control classes that resolved the correct resource file name based on the ASPX or ASCX page type that was calling it. I loaded the resources via ResourceManagers which I kept in the cache.
The biggest problem with this approach was that we still had no good way to declaratively get these resources assigned to the controls which needed them. So, not wanting to force these calls to be made in the code-behind, we did some perf testing and made the decision that the easiest approach would be to use databinding expressions (the <%# %> syntax) in the code in front and just call Page::DataBind() all the time. Now, this wasn’t all that bad, but it did cause us problems with some of our real databinding scenarios. We don’t use viewstate very much because of the amount of data that would be pushed out to the clients, so we pretty much always databind our controls anyway. However, we don’t always want to databind all the controls on the screen because they may not even be visible! The new Expression API/syntax, which resourcing is based on, solves this problem because code is generated at compile time to retrieve the resource when the page is loaded. The expression API is definitely one of the coolest “lower level” features of ASP.NET 2.0.
Long story short, we basically renamed our directories to have the “App_” prefix and did a giant find and replace on all the <%# base.GetResourceString(“someControlId.SomeProperty”) %> to now be <%$ Resources: someControlId.SomeProperty %> and everything worked like a charm. We then went back to the places where we were doing databinding “for real” and cleaned them up to make sure we only called DataBind when it was necessary and that our pages/custom controls only actually databound what they needed to.
The one thing we did not use in ASP.NET 2.0 and have stuck with a proprietary implementation of is Theming. I could be missing something, but I just don’t think the themeing implementation is very good. It focuses too much on theming server controls and not enough on how content is actually delivered to the client. For example, as far as I know (please correct me if I’m wrong!) theming in ASP.NET 2.0 just takes all the CSS files in a Theme directory and pushes them out via <link> tags. Well… I have way too many CSS files for that to be scalable. Sure we have a base stylesheet that that makes sense for, but each one of our pages usually has some specific styles, not to mention our controls, each of those has a separate stylesheet of their own. So at any given time theres maybe five to ten diff. style sheets loaded for a given page1. If I put them all in a dir and they were included each time it would be horrible for the end user. That said, I did take advantage of the expression APIs that I talked about before to implement our custom approach to theming now because, just like I metioned about our proprietary resourcing approach, we were relying on databinding expressions for theme URL generation that was bad. With the new expression, we just did a find and replace of <%# base.ResolveThemedUrl(“MyImage.gif”) %> with <% ThemedUrl: MyImage.gif %>.
Performance wise, while I realize we were taking a hit with the call to Page::DataBind every time and now we’re not, I am still extremely impressed with the overall performance improvements in ASP.NET 2.0. I haven’t had enough time to gather an extensive set of performance data, but our average page execution times were roughly somewhere in the 300ms range on average. Now we’re in the 150ms range! 50%, not entirely for free, but for the two weeks of upgrading and testing it took… that’s a hell of an improvement!
So, needless to say, I’m extremely happy with ASP.NET 2.0 on all fronts. Feature wise they’ve added everything I had to write myself in 1.0. Productivity wise, the designer support for ASP.NET is vastly improved. We still don’t use it entirely due to some problems it has with our custom controls, but we’re looking to use it more. Performance wise, well… I’m still gathering more data on the live system since it just went up, but things are looking good with an average 50% boost for our application across the board. Granted, a lot of that had to do with the databinding fixes we made, but based on the testing I originally did when we decided on that approach it really didn’t cost us more than 10-15%. So the other 35-40% is most certainly due to the other features and all around performance tuning they did on the CLR and ASP.NET 2.0 runtime. Kudos to Microsoft for such fantastic work!
1I realize people may be like “holy crap you’re loading way too many files!!!”. Well don’t worry… I’m a stickler for performance on all fronts. 😉 First, we use GZip compression so initial page loads aren’t so bad, but there’s definitely a noticeable delay when you first hit the site. Nothing you can do about that unless you stuff everyting into a single file, but then you’re stuck trying to manage all that at development time. From that point on we rely on HTTP caching to do it’s job. We send out all the standard HTTP cache headers for all our static content (CSS, Images, etc.) that tell the browser not to check again for back for a day. Then, when they eventually do have to check back, it’s just a HEAD request that they have to pay the price for.