Menu

ASP.NET

What it solves

Imagine you’re building an e-commerce site on Azure.

You need to generate order numbers, and they absolutely must be unique.

A few options come to mind initially:

Let SQL Azure generate the numbers for you. The downside to this approach is that you’re now serializing all of your writes down to a single thread, and throwing away all of the possible benefits from something like a queuing architecture. (Sidenote: on my current project we’re using a NoSQL graph DB with eventual consistency between nodes, so this wouldn’t work for us anyway.)

Use a GUID. These are far from human friendly. Seriously, can you imagine seeing an order form with a GUID on the top?

Prefix numbers with some form of machine specific identifier. This now requires some way to uniquely identify each node, which isn’t very cloud-like.

As you can see, this gets complex quickly.

SnowMaker is here to help.

What it does

SnowMaker generates unique ids for you in a highly distributed and highly performant way.

Ids are guaranteed to be unique, even if your web/worker role crashes.

How to use it

The only caveat not shown here is that you need to take responsibility for the lifecycle of the generator. You should only have one instance of the generator per app domain. This can easily be done via an IoC container or a basic singleton. (Multiple instances still won’t generate duplicates, you’ll just see wasted ids and reduced performance.) Don’t create a new instance every time you want an id.

Other interesting tidbits

The name is inspired by Twitter’s id generator, snowflake. (Theirs is more scalable because it is completely distributed, but in doing so it requires node-specific configuration.)

Typical id generation doesn’t even use any locks, let alone off-box communication. It will only lock and talk to blob storage when the id pool has been exhausted. You can control how often this happens by tweaking the batch size (a property on the generator). For example, if you are generating 200 order ids per server per second, set the batch size to 2000 and it’ll only lock every 10 seconds.

Node synchronisation is done via Azure blob storage. Other than that, it can run anywhere. You could quite easily use this library from AppHarbor or on premise hosting too, you’d just wear the cost of slightly higher latency when acquiring new ids batches.

The data persistence is swappable. Feel free to build your own against S3, Ninefold Storage, or any other blob storage API you can dream up.

Under the covers

SnowMaker allocates batches of ids to each running instance. Azure Blob Storage is used to coordinate these batches. It’s particularly good for this because it has optimistic concurrency checks supported via standard HTTP headers. At a persistence level, we just create a small text file for each id scope. (eg, the contents of /unique-ids/some-id-scope would just be “4”.)

One issue worth noting is that not all ids will always be used. Once a batch is checked out, none of the ids in it can ever be reallocated by SnowMaker. If a batch is checked out, only one id is used, then the process terminates, the remaining ids in that batch will be lost forever.

Here’s a sequence diagram for one client:

Here’s a more complex sequence diagram that shows two clients interacting with the store, each using a different batch size:

What it does

Think about a common user table. You probably have a GUID for each user, but you want to show their full name and maybe their email address in the header of each page. This commonly ends up being an extra DB hit (albeit hopefully cached).

There is a better way though! A little known gem of the forms authentication infrastructure in .NET is that it lets you embed your own arbitrary data in the ticket. Unfortunately, setting this is quite hard – upwards of 15 lines of rather undiscoverable code.

Things to Consider

Any information you store this way will live for as long as the ticket.

That can be quite a while if users are active on your application for long periods of time, or if you give out long-term persistent sessions.

Whenever one of the values stored in the ticket needs to change, all you need to do is call SetAuthCookie again with the new data and the cookie will be updated accordingly. In our user name / email address example, this is actually quite advantageous. If the user was to update their display name or email address, we’d just update the ticket with new values. This updated ticket would then be supplied for future requests. In web farm environments this is about as perfect as it gets – we don’t need to go back to the DB to load this information for each request, yet we don’t need to worry about invalidating the cache across machines. (Any form of shared, invalidatable cache in a web farm is generally bad.)

Size always matters.

The information you store this way is embedded in the forms ticket, which is then encrypted and sent back to the users browser. On every single request after this, that entire cookie gets sent back up the wire and decrypted. Storing any significant amount of data here is obviously going to be an issue. Keep it to absolutely no more than a few simple values.

Until now we’ve been pretty quiet about it all on our blogs because we were busy polishing off v1 and trying to get all the documentation in order. Nevertheless, the word has definitely started to spread as Scott Hanselman interviewed me about the library on this week’s Hanselminutes episode.

Following up from my last post about the ASP.NET MVC vs ASP.NET WebForms debate, we’ve had a second TechTalk posted, also from TechEd Australia. In this video, Michael Kordahi, Damian Edwards and I sat down to discuss building fast, public websites. It was a bit of a teaser for our breakout session at the conference, which will be available online as a screencast in the next week or two.

If you’re interested in learning more about building large public websites on ASP.NET, remember that the full video from our recent REMIX session is still available online too.

At the recent TechEd Australia conference, Paul Glavich, Damian Edwards and myself sat down to discuss what we thought about the current MVC vs WebForms debate. Our TechTalk has now been posted on the TechEd Online site, and available for anyone to watch.

If you’ve read my previous post explaining a common pitfall with view state, I’d hope you’re preparing all your controls in the Init event of the page/control lifecycle.

Even if I’m not reusing them through my application much, I like to factor elements like a drop down list of countries into their own control. This centralizes their logic and allows us to write clear, succinct markup like this:

Once you start using this encapsulation technique, it won’t be long until you want to pass in a parameter that affects the data you load. Before we do, we need to be aware that the Init event is fired in reverse order. That is, the child controls have their Init event fired before that event is fired at the parent. As such, the Page.Init event is too late for us to set any properties on the controls.

The natural solution is to try and use the Page.PreInit event, however when you do you’ll often find that your control references are all null. This happens when your page is implemented using a master page, and it relates to how master pages are implemented. The <asp:ContentPlaceHolder /> controls in a master page use the ITemplate interface to build their contents. This content (child controls) is not usually prepared until the Init event is called, which means the control references are not available. For us, this represents a problem.

The Solution

The fix is remarkably simple; all we need to do is touch the Master property on our Page and it will cause the controls to become available. If we are using nested master pages, we need to touch each master page in the chain.

I often create a file called PageExtensions.cs in my web project and add this code:

public static class PageExtensions { /// <summary> /// Can be called during the Page.PreInit stage to make child controls available. /// Needed when a master page is applied. /// </summary> /// <remarks> /// This is needed to fire the getter on the top level Master property, which in turn /// causes the ITemplates to be instantiated for the content placeholders, which /// in turn makes our controls accessible so that we can make the calls below. /// </remarks> public static void PrepareChildControlsDuringPreInit(this Page page) { // Walk up the master page chain and tickle the getter on each one MasterPage master = page.Master; while (master != null) master = master.Master; } }

This adds an extension method to the Page class, which then allows us to write code like the following:

The problem here is that we’re clogging our page’s view state. Think of view state as one of a page’s core arteries, then think of data like cholesterol. A little bit is all right, but too much is crippling.

To understand the problem, lets investigate the lifecycle that’s in play here:

The Page.Init event is being fired, however we are not subscribed to that.

Immediately after the Init event has fired, view state starts tracking. This means that any changes me make from now on will be saved down to the browser and re-uploaded on the next post back.

The Page.Load event is being fired in which we are setting the contents of the drop down list. Because we are doing this after the view state has started tracking, every single entry in the drop down is being written to both the HTML and the view state.

There’s yet another problem here as well. By the time the Page.Load event is fired, all of the post back data has been loaded and processed.

To investigate the second problem, let’s investigate the lifecycle that’s in play during a post back of this same page:

The user triggers the post back from their browser and all of the post back data and view state is uploaded to the server.

The Page.Init event is fired, however we are not subscribed to that.

Immediately after the Init event has fired, view state starts tracking. This means that any changes me make from now on will be saved down to the browser and re-uploaded on the next post back.

The view state data is loaded for all controls. For our drop down list example, this means the Items collection is refilled using the view state that was uploaded from the browser.

Post back data is processed. In our example, this means the selected item is set on the drop down list.

The Page.Load event is fired however nothing happens because the developer is checking the Page.IsPostBack property. Usually, they say this is a “performance improvement” however it is also required in this scenario otherwise we would lose the selected item when we rebound the list.

The contents of the drop down list are once again written to both the HTML and the view state.

How do we do this better? Removing the IsPostBack check and placing the binding code into the Init event is all we need to do:

We are filling the contents of the drop down before the Init event is fired; therefore a redundant copy of its contents is not written to the view state.

We are filling the contents of the drop down before the postback data is processed, so our item selection is successfully loaded without it being overridden later.

We have significantly reduced the size of the page’s view state.

This simple change is something that all ASP.NET developers need to be aware of. Unfortunately so many developers jumped in and wrote their first ASP.NET page using the Page_Load event (including myself). I think this is largely because it’s the one and only event handler presented to us when we create a new ASPX page in Visual Studio. While this makes the platform appear to work straight away, it produces appalling results.

Post navigation

About Me

Hi, I'm Tatham Oddie.

Based in Melbourne, Australia, I'm the CIO at Readify. I also have some startup interests, such as Mining Hive.

I am a passionate member of the technical community, and a regular presenter throughout Australia, New Zealand, Europe and the US. For this involvement, I am a seven-time awarded Microsoft Most Valuable Professional.

Contact Details

If you're at a conference and think I might be there too, or just want to talk about something I might be interested in, drop me a line.