About the author

J Sawyer is a developer based in Houston, TX and loves to write code, especially ASP.NET and other web-related stuff.

He also loves to ride his Kawasaki Ninja.

But he doesn't code and ride at the same time.

Calendar

<<  March 2010  >>
MoTuWeThFrSaSu
22232425262728
1234567
891011121314
15161718192021
22232425262728
2930311234

View posts in large calendar

Moving from dasBlog to BlogEngine.NET

April 22, 2009 9:54 AM

As I mentioned previously, I’ve moved from dasBlog to BlogEngine.NET for this blog. This, of course, involved reformatting and redesigning the look and feel of the site; that’s nothing unique to the migration and I’m not going to go into that at all. What I will do, however, is discuss the process of moving existing content over from dasBlog to BlogEngine, something that isn’t really hard but does have a few gotchas.

Moving the Content

That’s the first thing that needs to be done. In fact, I did this before I even started formatting the new site – I wanted to be sure that the existing content rendered relatively well in the new design. It was not quite as simple as described on Merill’s blog. All of his steps are valid, but there is actually a couple of other things that need to be done. You will definitely want to use the dasBlog to BlogML Converter that Merill posted on MSDN Code Gallery – dasBlog doesn’t do BlogML and, while BlogEngine will import RSS, RSS usually will not get all of your content. BlogML works much better.

There were two things with moving the content … how big a deal those are depend on how picky you are about the move. I was. First, the timestamp on the entry. dasBlog uses UTC (GMT) to store the time and that’s how it is imported into BlogML. BlogEngine uses the server time. Both have an offset to convert the saved time into blog local time, but dasBlog’s offset is from UTC (using standard time zones) and BlogEngine uses an offset from the server time. My server is on US Eastern Time and my local blog time is US Central Time, which means that, on import, I had to convert the time to US Eastern and then set my offset in BlogEngine to –1 (US Central is 1 hour “behind” US Eastern). To do this, I had to modify the code that imported the blog entries, which can be found at BlogEngine.Web\api\BlogImporter.asmx. Since the incoming BlogML only had a post date, not a DateCreated and a DateModified (as BlogEngine does), I also set both the create date and the modify date to the same value. Here’s the code snippet from AddPost:

Post post = new Post();
post.Title = import.Title;
post.Author = import.Author;
post.DateCreated = import.PostDate.AddHours(-5);
post.DateModified = import.PostDate.AddHours(-5);
post.Content = import.Content;
post.Description = import.Description;
post.IsPublished = import.Publish;

Once I set BlogEngine’s server time offset (in the admin section under “Settings”), all of the times were now correctly displayed as US Central.

The second thing relates to the tags … BE uses tags (and categories) while dasBlog only uses categories. In dasBlog, the “tag cloud” is generated from the categories and BE generates this from the actual post tags. I can’t say which method I like better yet or if I prefer some mish-mash of the two (generate the cloud from tags and categories … that may be an idea) but I did know that I didn’t want to lose my tag cloud. So, on import, I added tags for each post category to the imported post. Again, simple and again, in AddPost:

if (import.Tags.Count == 0)
{
    post.Tags.AddRange(import.Categories);
}
else
{
    post.Tags.AddRange(import.Tags);
}

From a performance standpoint, I couldn’t tell you if AddRange is faster than looping block to add each value individually (and it really doesn’t matter here), but it is simpler, cleaner and much easier to read … so I tend to prefer AddRange().

With these two “issues” resolved – and they aren’t issues with BE, to be sure, just a difference between the two – I was ready to move on.

Preserving links

Some of my entries have a pretty good page rank on various search engines and there is a non-trivial amount of traffic that is generated from these search engines. While I can go in and change things like the RSS source for my feed from FeedBurner to make the move transparent, that doesn’t help with the search engines. Therefore, I needed a way to ensure that existing links would continue to work without returning 404’s. Yes, I moved the old domain over to the new domain and added it as a host header on the new site, but that does not help prevent link breakage and BE and dasBlog have different formats for their links. I also did not, at this point in time, want to force a redirect as soon as a new person hit my site from a search engine; it’s just rude (IMHO) and doesn’t create a great user experience. Sure, maybe it wouldn’t be a big deal, but I didn’t like it. And besides, it gave me an excuse to write code. :-)

To keep the links intact, I decided that I would leave BlogEngine’s UrlRewriting intact; I didn’t want to make too many changes to the base source code as it would make it harder for me to move between versions/revisions. Rather, I wanted to sit on top of it and make sure that the links worked. So I used ASP.NET Url Routing to intercept the requests and send them to the right place (post.aspx). Before I go into the code, let’s first examine the (default) url structures for individual posts. In dasBlog, the post link is in the format yyyy/mm/dd/{CompressedTitle}. In BlogEngine, this would be post/{CompressedTitle} –or- (the permalink format) post.aspx?id={PostGUID}. While BE can have the date as a part of the post link, it still wouldn’t work; they compress their titles differently and, as mentioned before, dasBlog uses UTC internally and it’s used in the link as well.

For the routing, I created the route using "{Y}/{M}/{D}/{Title}" as the route url. From there, I needed to implement GetHttpHandler to do the work. Initially, I did the matching to title using a Linq query and it worked just fine. The problem with this is that every title in the posts would need to be converted to the dasBlog format (I copied over the dasBlog CompressTitle method as dasBlogCompressTitle), a process that seemed far from ideal. Once I understood how the dates worked, I was able to do the primary matching on the date and then, if necessary, match the titles for posts on the same date, minimizing the string manipulation that was required. Once I determined what the matching post was, all I needed to do was append the query string “?id={postGuid}” to the URL and then pass back the HttpHandler from post.aspx for the actually processing. If there was no match, then there would be no query string appended and post.aspx would show a 404. The code for this is below:

public System.Web.IHttpHandler GetHttpHandler(RequestContext requestContext)
{
    //Get the date from the route. 
    string dateString = string.Format(@"{0}/{1}/{2}",
        requestContext.RouteData.Values["M"],
        requestContext.RouteData.Values["D"],
        requestContext.RouteData.Values["Y"]);
    string titleString = ((string)requestContext.RouteData.Values["Title"]).Replace(".aspx", ""); 


    DateTime postDate = DateTime.MaxValue;
    Post selectedPost = null; 
    if (DateTime.TryParse(dateString, out postDate))
    {
        //Date is valid at least. 
        //Find posts with the same date.
        //Date in URL is in UTC. 
        var postsForTitle =
            from p in Post.Posts
            where p.DateCreated.ToUniversalTime().Date == postDate
            select p;
        if (postsForTitle.Count() == 1)
        {
            //There is only one posts for the date, so this must be it. 
            selectedPost = postsForTitle.First(); 
            
        }
        else
        {
            //differentiate on title. 
            foreach (var p in postsForTitle)
            {
                if (dasBlogCompressTitle(p.Title).Equals(titleString, StringComparison.InvariantCultureIgnoreCase))
                {
                    selectedPost = p;
                    break; 
                }
            }
        }
        if (selectedPost != null)
        {
            //Use UrlRewriting to put the id of the post in the query string. 
            requestContext.HttpContext.RewritePath(requestContext.HttpContext.Request.Path + "?id=" + selectedPost.Id.ToString(), false);
        }
    }

    return BuildManager.CreateInstanceFromVirtualPath(
        "~/post.aspx", typeof(System.Web.UI.Page)) as System.Web.IHttpHandler; 

}

Once I set it up on the web.config file and added the routes to the RouteTable, all was good and it worked fine.

Preserving the RSS Feed Url

The final step - and the thing that occurred to me last – was to make sure that the RSS feed url continued to work. While FeedBurner had no problem with changes the RSS url for my blog, there was the possibility (however remote it may have seemed) that someone was using the dasBlog’s RSS feed rather than FeedBurner. I’m not sure how remote a possibility this is but I didn’t use FeedBurner in the early days of the blog, so I figured that it might be an issue. And I certainly wouldn’t want to alienate the longest-time subscribers to my feed. This was incredibly simple and didn’t require any code at all, just two lines line in the web.config file to have SyndicationService.asmx (dasBlog’s RSS feed) handled by BlogEngine’s RSS feed, which is implemented as an HttpHandler and, by default, at syndication.axd. The first line is for IIS 6.0/IIS 7.0 Classic mode and is under the httpHandlers node of system.web:

<add verb="*" path="SyndicationService.asmx" 
type="BlogEngine.Core.Web.HttpHandlers.SyndicationHandler, BlogEngine.Core"
validate="false"/

The second goes in the corresponding location for IIS 7 Pipeline mode, in the handlers node of system.webServer:

<add name="dasBlogSyndication" verb="*" path="SyndicationService.asmx" 
type="BlogEngine.Core.Web.HttpHandlers.SyndicationHandler, BlogEngine.Core"
resourceType="Unspecified" requireAccess="Script" preCondition="integratedMode"/>
These were copied from the default nodes used by BlogEngine for it’s syndication.axd and then the relevant attributes were changed. Simple enough.

Tags: , , , ,

BlogEngine.NET | Web (and ASP.NET) Stuff

Url Routing in ASP.NET

January 30, 2009 9:16 PM

One of the new features in ASP.NET 3.5 SP1 is Url Routing … it’s the magic behind the pretty, clean url’s that you see with Dynamic Data and MVC. If you dig around a bit, almost all (if not all) of the material that’s out there focuses on using Routing with either MVC or Dynamic Data … I’ve found nada that actually talks about how it can be added to an existing ASP.NET WebForms application. In talks of .NET 3.5 SP1, Url Routing is even ignored some of the time … and if it’s not ignored, it’s barely mentioned in passing. And then there’s the documentation which is, IMHO, pretty lame and the “How To” articles on MSDN are only mildly better.

In spite of (or maybe because of) that, I found myself intrigued by the whole idea of Url Routing. Yes, I had seen it and tweaked it in MVC and Dynamic Data, but I knew that there had to be additional uses for it. So … I set about to build a demo for Houston Tech Fest that showed how to get started with Url Routing, adding it to an existing website that showed some data from Northwind. It’s not a pretty or even really functional app … that’s not the point … and has Url’s that are both plain vanilla and that require query strings. In addition, there was absolutely, positively no consistency between the pages or the query string parameters. I know that doesn’t happen in the real world! ;-)

There was one thing that I did do in the application that I don’t see done frequently; I added a static utility class that built and returned Url’s for the different pages in the app. Again, not something that I typically see, but it is definitely something that can make the maintenance of links and url’s easier and more elegant. Well, maybe not elegant but it sure beats “Find in project”. But then, url’s in the application never change, do they?

If you’re interested, you can find the PPT and the demos (as well as something resembling a script) on my SkyDrive. It’s the same place as the link that I posted the other day for my Houston TechFest presentations. However, I wanted to spend a little more time and explain what’s going on and how it all works.

StaticRoutingHandler

This is what does the work. All you have to do to create a routing handler is to implement the IRouteHandler interface. It’s pretty a simple interface – there’s only 1 method. This takes a url route and then transfers this to a static .aspx page that is configured for the route (1 route, 1 page). Since I needed to pass query strings to the pages, I also do some url rewriting in here. Technically, this is not necessary to pass information to the target page, but remember – I didn’t want to have to spend a lot of time making changes all over the app and changing the pages to no longer require query strings would be more work and get away from the point of the demo (i.e. making it easy). While you do create an instance of the page with Url Routing, the pages in this demo didn’t have any nice properties that encapsulated the query string parameters. No way to do that without url rewriting when the app is expecting to get query strings. It takes the data arguments specified in the route and turns them into query strings, using the name as the query string parameter. Here it is:

public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
    string finalPath = VirtualPath;
    if (requestContext.RouteData.Values.Count > 0)
    {
        List<string> values = new List<string>();
        //Add these to the virtual path as QS arguments
        foreach (var item in requestContext.RouteData.Values)
        {
            values.Add(item.Key + "=" + item.Value);
        }
        finalPath += "?" + String.Join("&", values.ToArray());

    }

    //Rewrite the path to pass the query string values. 
    HttpContext.Current.RewritePath(finalPath);

    var page = BuildManager.CreateInstanceFromVirtualPath(
        VirtualPath, typeof(Page)) as IHttpHandler;
    return page;
}

Configuration

This is where it could potentially get hairy. With MVC and Dynamic Data, it’s pretty easy to do it in the global.asax file since their paths and page names follow a clear and simple convention. Not so with the sample app. So each route/page combination needs to be registered separately because the page names have absolutely no consistency, not to mention the query string arguments. Ewwww … that’ll bloat your global.asax real quick. Since I didn’t like how that was going, I decided that I’d make it configuration-driven. This had the added benefit of allowing you to change the routes, arguments, etc. without redeploying code. I wrote a custom configuration section to handle this; this also makes the config read/write with the API which I thought might be a nice-to-have. So, the section looks like the following:

<StaticRoutes>
  <Routes>
    <add name="AXD" routeUrl="{resource}.axd/{*pathInfo}"></add>
    <add name="CategoryList" routeUrl ="CategoryList" virtualPath="~/CategoryList.aspx"/>
    <add name="ProductList" routeUrl="Category/Products/{C}" virtualPath="~/ProductList.aspx">
      <Restrictions>
        <add name="C" value="\d"/>
      </Restrictions>
    </add>
    <add name="ViewProduct" routeUrl="Product/{Id}" virtualPath="~/ViewProduct.aspx">
      <Restrictions>
        <add name="Id" value="\d"></add>
      </Restrictions>
      <Defaults>
        <add name="Id" value="1"/>
      </Defaults>
    </add>
    <add name="CustomerOrders" routeUrl="Customers/Orders/{Cu}" virtualPath="~/ListOrders.aspx">
    </add>
    <add name="CustomerList" routeUrl="Customers" virtualPath="~/Customers.aspx">
    </add>
    <add name="OrderDetails" routeUrl="Customers/Orders/{Id}" virtualPath="~/OrderDetails.aspx"/>
  </Routes>
</StaticRoutes>

It’s got all of the information that we need to create our routes. Restrictions and Defaults are optional – not every route needs them. You’ll also notice that the “AXD” route doesn’t have any virtual path listed … when there is no virtualPath specified, the StaticRoutingHandler.Configure method will add a StopRoutingHandler rather than the StaticRoutingHandler. The StopRoutingHandler is the only handler (that I could find) that is in the API itself (MVC and Dynamic Data each have their own RoutingHandlers). It tells Routing to simply ignore the request and send it along it’s merry way as if there was no routing configured. The order of the routes in the config file does matter, but that has nothing to do with my code; when the ASP.NET routing framework looks for the handler that a particular url matches, it grabs the first that it finds on the list. So … that’s how you prioritize your routes. The query string parameters are surrounded with curly braces … so “{Cu}” in the CustomerOrders route above would get put in as a query string named “Cu” with the value that appears in that place in the url.

With the configuration, RegisterRoutes, rather than being a mess that looks more like my office desk than code, is clean, simple and sweet. We just need to call a static method on the StaticRoutingHandler class to read the configuration and add the routes.

public static void RegisterRoutes(RouteCollection routes)
{
    routes.Clear();

    StaticRouting.StaticRoutingHandler.Configure(routes);

}

The names (which are required) also allow us to build the Url using the Routing API, rather than having Url’s hardcoded in the application. It’s pretty simple and straightforward; below is one of the more “complex” examples as it builds the RouteValueDictionary with the value for the query string.

public static string GetProductsForCategory(int categoryId)
{

    var values = new RouteValueDictionary();
    values.Add("C", categoryId);

    var path = RouteTable.Routes.GetVirtualPath(
        null,
        "ProductList",
        values);
    return path.VirtualPath;

}

I got a question when I did this presentation about wildcard parameters. I knew about them and how they worked, but somehow didn’t think to play with them in this sample app. First, you can do this (and I mentioned it in the presentation) by adding a wildcard parameter to the route. In our configuration, it would look like the following:

<add name="Wildcard" routeUrl ="Wildcard/{id}/{* params}" virtualPath="~/Customers.aspx"></add> 

It doesn’t have to have a set parameter (id in this case) in the routeUrl; I just put that there as a comparison. Everything else after that goes into a single item called (in this case) “params”. The “id” is exactly as we expect it to be. However, the wildcard doesn’t translate well into query string parameters. Yes, it is named “params”, but the value is everything in the path in the wildcard’s place, including the slashes in the path. So, with the url http://localhost/Wildcard/3/sasdf/asdf/as, the value of “params” is sasdf/asdf/as. Yes, the routing handler will very happily pass this along when it rewrites the url, but doesn’t really seem to make sense in this context. In this case, I’d say that you put each possible query string/route combination in as a different route to make sure that the page gets the parameters it expects the way that it expects them. I might, some time in the future, put some more thought into this and come up with something else, but for right now, I’m happy punting on it for now and just adding a route for each combination.



Tags: ,

Web (and ASP.NET) Stuff

CSK 3.0 CTP1

November 3, 2008 6:36 PM

I know, I know, it’s a little late. I said Nov 1st and it’s now Nov 3rd. Here’s what happened – I was all on track to have it ready for the 1st, but then I got sick. I felt like a bowl of cold oatmeal, which is not very good. Kids … I’m tellin’ ya … they’re germ factories. I wound up spending the weekend in bed, resting. I could have down a drop on Friday or Saturday regardless, but it wasn’t at the point that I wanted it to be. Now that I’m feeling better, I got back down to it and got it to the point that I wanted in about an hour or so worth of work (told you I was close).

[EDIT] You can download it from CodePlex. (thanks Alan!)

Release Notes

This is a Tech Preview. Read: nowhere near final. It’s not even up to what I’d call a beta. I’m putting this up primarily for feedback from the community and (hopefully) to generate some additional interest. It does show the direction that I’m going with this and I hope you’ll notice that, while the underlying functionality is pretty complex, it’s easy to work with and easy to change the UI. I have avoided duplicated any code as much as possible, though I may have missed something.

User Interface elements:

Most of the user interface is encapsulated in User Controls, which you will find in the Controls folder. I think that the names should be self-explanatory. For simplicity, data binding is used whenever feasible. Yes, it is not the most attractive. If there is anyone with better design skills, I’d be happy to hear from you.

Implemented functionality:

It’s the very base functionality; you have catalog display and navigation, cart functionality and checkout functionality. Checkout does not, at this time, call any of the payment processing components but the code will be (for the most part) the same when that does get added in.

Installation:

Unzip the file to a folder on your system. In the Data folder, you will find a backup of the Sql Server database that is used for the site. If you restore this to “.\SqlExpress” in a database named “CommerceDb”, you will not need to make any changes at all to the connection strings. If, however, your database is different, change the connection strings that are in the web.config.

Coming next:

Added actual payment processing. Would like to get login implemented as well; we’ll see about that.



Tags: , ,

CSK | Open Source | Web (and ASP.NET) Stuff

Commerce Starter Kit 3.0 &ndash; CSK Reprise

October 24, 2008 5:59 PM

I am happy to announce a new Commerce Starter Kit. We’re calling it CSK 3.0, but it’s not based on the previous (CSK 2.0) codebase at all; it is a complete re-write from the ground up. Some of you probably remember when I first got involved in the CSK … that was WAAAAAY back in the days when ASP.NET 2.0 was all new and shiny. ASP.NET has, of course, moved forward quite a bit since then. We won’t be just upgrading the old CSK code base, but building a brand-spanking-new version, even though we’ll be calling it CSK 3.0. And … it will be in the same spirit as CSK - “shared source” and freely available to all. I will be reporting progress on this regularly, so you will want to subscribe if you want all the latest updates and news on the project.

This all started with a chat or two with my old buddy Rob Conery, the creator of the original Commerce Starter Kit. He’s here at Microsoft now and working on the MVC Storefront, a sample application that uses the ASP.NET MVC Framework (as the name implies). At the end of these chats, we thought we’d revive the CSK name and build a new version … with components and business logic based on the same components and business logic used for MVC Storefront but with a traditional WebForms UI. That’s right folks … it’s gonna share code with the site that Rob’s working on. Not only does it make life much easier, it also shows how the same core logic and functionality can be skinned with either MVC or WebForms.

Here’s the core stuff that is on the plate for this version:

Updates to the latest coolness: CSK 3.0 will be targeting ASP.NET 3.5 with SP1, so we’ll have all of the latest toys, including (of course!) Linq. :-)

Migration to common e-Commerce libraries: I mentioned this above; CSK will be using the same core business libraries and database as MVC Storefront, allowing you to easily switch between the two.

Localization support: Oh boy, I remember lots of folks asking for this with CSK 1.x and 2.0. The new CSK will be localizable out of the box. All of the product and category text and descriptions will be localizable. The goal is to also have most, if not all, of the static text on the pages localizable as well. That’ll be fun with resource files.

ASP.NET Dynamic Data Administration: The admin site will now be a separate web application so that there’s better isolation from the main web site. Of course, this could be installed as a virtual directory application under the main site but that won’t be necessary at all. You’ll easily be able to deploy it as a completely separate web site.

ASP.NET Web Application: This is the “old” (as in 1.x) model for web applications. While it’s not as easily changed while running (since you actually have to do a build), it does provide better start-up performance, something that became a complaint with CSK 2.0. Yes, yes, you could pre-compile the site (and I often suggested that folks do this) but with the web application model, that won’t be a separate step.

ASP.NET Ajax: This is all a part of the cool new toys, but I thought I’d highlight it separately. We’ll be using ASP.NET Ajax where it makes sense as well as the ASP.NET Ajax Control Toolkit.

Better composability: One of the things that I thought we could have done better with CSK 2.0 was to have a better composition model for the UI … for example, there were 2 or 3 different places to change a product list display … category list, search list, etc. This made it more difficult to maintain as fields were added or changes were made to certain core UI elements. These components will be separated into a series of User Controls, each with distinct, composable functionality. You want to change the view of a product in a list? Change it once and you’ll see it everywhere … search results, category listing, cart, etc.

Coupons: This was only partially implemented in CSK 2. Rob and I spent a couple of hours, both on the phone and over IM, and forth on how this would be implemented and I think it’s settled. The coupon system is going to be very extensible so that you can create any type of coupon that your heart desires. We’ll be including a couple of simple ones to get you started.

One of my key goals with this rev is simplicity. I want the UI (at least) to be as simple as possible … and I want it to be simple to re-skin the CSK to your own look and feel. To that end, I am using quite a bit more controls and data binding than were used in version 2.0 … everything, in fact, is in some sort of control or another. Additionally, I do want to highlight, as much as possible, the infrastructure of ASP.NET as well as the extensibility of this infrastructure. For example, for the category listing, I’m using a SiteMapProvider that builds a site map from the categories in the database. Believe it or not, this is actually quite easy to do and provides things like the SiteMapPath, which I do plan on getting in there.

I will need some help with this – in particular, I’d really like help with the following:

  • Functionality – what features are essential and/or cool? What do you want to see in there? (No guarantees that it’ll make it, but if you offer to do it, it’s a lot more likely!)
  • Design – Right now, I’m just stealing the design and layout from the MVC Storefront, converting it to use WebForms (so no MVC calls), etc. etc. etc. I call it the CASE methodology – Copy Always, Steal Everything. Now, I have the design skills of a dead toad, so redoing the design is simply out of the question. BUT … I would like to see it begin to diverge from MVC Storefront’s look. So any help that you might want to provide to do that would be very nice.
  • Testing – Yes, we’ll have automated tests to catch a bunch of stuff, but manual testing and feedback is also needed, particularly when it comes to user experience.
  • Migration – From CSK 2. I really don’t know if this is going to actually be possible and it’s certainly not something that I have on the list of “stuff to do” but if someone wants to volunteer to write a migration utility, that’d be cool with me.

I’m targeting to get the first, alpha “preview” version out in the next week or so and I’ll announce that here as well.



Tags: , ,

CSK | Open Source | Web (and ASP.NET) Stuff

ASP.NET Async Page Model

October 1, 2008 7:07 PM

I just did a Code Clinic for the Second Life .NET User’s Group on using the ASP.NET async page model and it occurred to me that it’d be a good idea to do a little blog post about it as well. I’ve noticed that a lot of developers don’t know about this little feature and therefore don’t use it. It doesn’t help that the situations where this technique helps aren’t readily apparent with functional testing on the developer’s workstation or even on a separate test server. It only rears its head if you do load testing … something that few actually do (I won’t go there right now).

So, let me get one thing straight from the get-go here: I’m not going to be talking about ASP.NET AJAX. No way, no how. I’m going to be talking about a technique that was in the original release of ASP.NET 2.0 and, of course, it’s still there. There are some big-time differences between the async model and AJAX. First, the async model has nothing at all to do with improving the client experience (at least not directly, though it will tend to). Second, the async model doesn’t have any client-side goo; it’s all server-side code. And finally, there is no magic control that you just drop on your page to make it work … it’s all code that you write in the code-behind page. I do want to make sure that this clear ‘cuz these days when folks see “async” in relation to web pages, they automatically think AJAX. AJAX is really a client-side technique, not server side. It does little to nothing to help your server actually scale … it can, in some cases, actually have a negative impact. This would happen when you make additional round trips with AJAX that you might not normally do without AJAX, placing additional load on the server. Now, I’m not saying that you shouldn’t use AJAX … it’s all goodness … but I just want to clarify that this isn’t AJAX. Now, you can potentially this this for AJAX requests that are being processed asynchronously from the client.

Now that we have that out of the way, let me, for a moment, talk about what it is. First, it’s a really excellent way to help your site scale, especially when you have long-running, blocking requests somewhere in the site (and many sites do have at least a couple of these). Pages that take a few seconds or more to load may be good candidates. Processes like making web services calls (for example, to do credit card processing and order placement on an eCommerce site) are excellent candidates as well.

Why is this such goodness? It has to do with the way ASP.NET and IIS do page processing. ASP.NET creates a pool of threads to actually do the processing of the pages and there is a finite number of threads that will be added to the pool. These processing threads are created as they are needed … so creating additional threads will incur some overhead and there is, of course, overhead involved with the threads themselves even after creation. Now, when a page is requested, a thread is assigned to the page from the pool and that thread is then tied to processing that page and that page alone … until the page is done executing. Requests that cannot be serviced at the time of the request are then queued for processing as a thread becomes available. So … it then (logically) follows that pages that take a long time and consume a processing thread for extended periods will affect the scalability of the site. More pages will wind up in the queue and will therefore take longer since they are waiting for a free thread to execute the page. Of course, once the execution starts, it’ll have no difference on the performance … it’s all in the waiting for a thread to actually process the page. The end result is that you cannot services as many simultaneous requests and users.

The async page model fixes this. What happens is that the long running task is executed in the background. Once the task is kicked off, the thread processing the thread is then free to process additional requests. This results in a smaller queue and less time that a request waits to be serviced. This means more pages can actually be handled at the same time more efficiently … better scalability. You can see some test results of this on Fritz Onion’s blog. It’s pretty impressive. I’ve not done my own scalability testing on one of my test servers here, but I think, shortly, I will. Once I do, I’ll post the results here.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

How do you do this? To get started is actually quite easy, simple in fact. You need to add a page directive to your page. This is required regardless of which method you use (there are two). ASP.NET will then implement IAsyncHttpHandler for you behind the scenes. It looks like this:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" Async="True" %>

Simple enough, right? Let me just add a couple of things that you need to make sure you have in place. You will need to follow the .NET asynchronous pattern for this to work … a Begin method that returns IAsyncResult and an end method that takes this result. It’s typically easiest to do this with API’s that already have this implemented for you (you just return their IAsyncResult object). There’s a ton of them and they cover most of the situations where this technique helps.

Now, to actually do this. Like I said, there’s two different ways to use this. The first is pretty easy to wireup and you can add multiple requests (I misstated this during the Code Clinic), but all of the async requests run one at a time, not in parallel. You simply call Page.AddOnPreRenderCompleteAsync and away you go. There are two overloads for this method, as follows:

void AddOnPreRenderCompleteAsync(BeginEventHandler b, EndEventHandler e)

 

 

 

 

 

 

 

 

 

 

 

 

void AddOnPreRenderCompleteAsync(BeginEventHandler b, EndEventHandler e, object state)

The handlers look like the following:

IAsyncResult BeginAsyncRequest(object sender, EventArgs e, AsyncCallback cb, object state)
void EndAsyncRequest(IAsyncResult ar)

 

 

 

 

 

 

The state parameter can be used to pass any additional information/object/etc. that you would like to the begin and the end methods (it’s a member if the IAsyncResult interface), so that can be pretty handy.

The code behind for such a page would look like the following:

    protected void Page_Load(object sender, EventArgs e)
    {
        LoadThread.Text = 
            Thread.CurrentThread.ManagedThreadId.ToString(); 
        AddOnPreRenderCompleteAsync(new BeginEventHandler(BeginGetMSDN),
            new EndEventHandler(EndAsyncOperation)); 

    }

    public IAsyncResult BeginGetMSDN(object sender, EventArgs e, AsyncCallback cb, object state)
    {
        BeginThread.Text =
            Thread.CurrentThread.ManagedThreadId.ToString(); 
        HttpWebRequest  _request = 
            (HttpWebRequest)WebRequest.Create(@"http://msdn.microsoft.com");
        return _request.BeginGetResponse(cb, _request);
    }

    void EndAsyncOperation(IAsyncResult ar)
    {
        EndThread.Text =
            Thread.CurrentThread.ManagedThreadId.ToString(); 
        string text;
        HttpWebRequest _request = (HttpWebRequest)ar.AsyncState;
        using (WebResponse response = _request.EndGetResponse(ar))
        {
            using (StreamReader reader = new StreamReader(response.GetResponseStream()))
            {
                text = reader.ReadToEnd();
            }
        }

        Regex regex = new Regex("href\\s*=\\s*\"([^\"]*)\"", RegexOptions.IgnoreCase);
        MatchCollection matches = regex.Matches(text);

        StringBuilder builder = new StringBuilder(1024);
        foreach (Match match in matches)
        {
            builder.Append(match.Groups[1]);
            builder.Append("<br/>");
        }

        Output.Text = builder.ToString();
    }

}

If you run this (on a page with the proper controls, of course), you will notice that Page_Load and BeginGetMSDN both run on the same thread while EndAsyncOperation runs on a different thread.

The other method uses a class called PageAsyncTask to register an async task with the page. Now, with this one, you can actually execute multiple tasks in parallel so, in some cases, this may actually improve the performance of an individual page. You have two constructors for this class:

 

 

public PageAsyncTask(
        BeginEventHandler beginHandler,
        EndEventHandler endHandler,
        EndEventHandler timeoutHandler,
        Object state)

and

public PageAsyncTask(
    BeginEventHandler beginHandler,
    EndEventHandler endHandler,
    EndEventHandler timeoutHandler,
    Object state,
    bool executeInParallel){}

 

The only difference between the two is that one little argument … ExecuteInParallel. The default for this is false, so if you want your tasks to execute in parallel, you need to use the second constructor. The delegates have identical signatures to the delegates for AddOnPreRenderComplete. The new handler timeoutHandler, is called when the operations times out and has the same signature to the end handler. So … it’s actually trivial to switch between the two (I did it to the sample listing above in about a minute.) I, personally, like this method better for two reasons. One, the cleaner handling of the timeout. That’s all goodness to me. Second, the option to have them execute in parallel. The same page as above, now using PageAsyncTask looks like to following:

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        LoadThread.Text = 
            Thread.CurrentThread.ManagedThreadId.ToString();
        
        PageAsyncTask t = new PageAsyncTask(
            BeginGetMSDN,
            EndAsyncOperation,
            AsyncOperationTimeout,
            false);
    }

    public IAsyncResult BeginGetMSDN(object sender, EventArgs e, AsyncCallback cb, object state)
    {
        BeginThread.Text =
            Thread.CurrentThread.ManagedThreadId.ToString(); 
        HttpWebRequest  _request = 
            (HttpWebRequest)WebRequest.Create(@"http://msdn.microsoft.com");
        return _request.BeginGetResponse(cb, _request);
    }

    void EndAsyncOperation(IAsyncResult ar)
    {
        EndThread.Text =
            Thread.CurrentThread.ManagedThreadId.ToString(); 
        string text;
        HttpWebRequest _request = (HttpWebRequest)ar.AsyncState;
        using (WebResponse response = _request.EndGetResponse(ar))
        {
            using (StreamReader reader = new StreamReader(response.GetResponseStream()))
            {
                text = reader.ReadToEnd();
            }
        }

        Regex regex = new Regex("href\\s*=\\s*\"([^\"]*)\"", RegexOptions.IgnoreCase);
        MatchCollection matches = regex.Matches(text);

        StringBuilder builder = new StringBuilder(1024);
        foreach (Match match in matches)
        {
            builder.Append(match.Groups[1]);
            builder.Append("<br/>");
        }

        Output.Text = builder.ToString();
    }

    void AsyncOperationTimeout(IAsyncResult ar)
    {
        EndThread.Text = Thread.CurrentThread.ManagedThreadId.ToString(); 
        Output.Text = "The data is not currently available. Please try again later."
    }

}

Not much difference there. We have 1 additional method for the timeout and the registration is a little different. By the way, you can pass null in for the timeout handler if you don’t care about it. I don’t recommend doing that, personally, but that’s up to you.

There you have it … a quick tour through the ASP.NET asynchronous page model. It’s clean, it’s easy, it’s MUCH better than spinning up your own threads and messing with synchronization primitives (this is mucho-bad-mojo, just say NO) and it’s got some pretty significant benefits for scalability.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

With that, I’m outta here. Happy coding!