Maarten Balliauw {blog}

ASP.NET, ASP.NET MVC, Windows Azure, PHP, ...

NAVIGATION - SEARCH

ASP.NET MVC framework preview to be released next week

Half the world has been focussing on the release of the new Visual Studio 2008 and .NET 3.5 last week. That is good, as .NET 3.5 offers lots of nice new features and improvements. In the blogosphere, I haven't read much about an extension I've been waiting for anxiously: the new ASP.NET MVC framework.

Luckily, Scott Guthrie posted some examples on it, and I can't wait for a preview to be released next week. I'll keep you informed!

ASP.NET load balancing and ASP.NET state server (aspnet_state)

At one of our clients, we used to have only one server for ASP.NET applications (including web services). Since this machine is actually business-critical and load is constantly growing, the need for a second machine is higher than ever.

This morning I was asked to set up a simple demo of a load-balanced ASP.NET environment. I already did this in PHP a couple of times, but in ASP.NET, this question was totally new to me. Things should not be very different, I thought. And this thought proved right!

A bit later, we had a load balancer in front of 2 web server machines. We got everything configured, fired up our webbrowser and saw a different page on each refresh (stating the server's hostname). Load balancing mission succeeded!

Next thing: session state. In our PHP environment, we chose to centralize all session data in a database. ASP.NET provides the same functionality, but we chose to use the ASP.NET state server for this demo. This proved to be a difficult yourney... But we managed to get things running! Here's how.

1. Set up the ASP.NET state service

Pick a server which will serve as the session state server. Fire up the services control panel (services.msc). Select the "ASP.NET State Service" item and make it start automatically. Great! Our state service is running.

Caveat 1: state server will not listen on any public IP address. So fire up your registry editor, change the following key and restart the ASP.NET state service:

HKLM\SYSTEM\CurrentControlSet\Services\aspnet_state\Parameters\AllowRemoteConnections

Eventually change the port on which the state server will be listening:

HKLM\SYSTEM\CurrentControlSet\Services\aspnet_state\Parameters\Port (default: 42424)

Caveat 2: after changing the AllowRemoteConnections directive, make sure the server's port 42424 is NOT open for the Internet, just for your web servers!

2. Make both ASP.NET servers use the state server

Every Web.config file contains a nice configuration directive named "sessionState". So open up your Web.config, and make it look like this:

[code:xml]

<?xml version="1.0"?>
<configuration>
    <system.web>
        <!-- ... -->
        <sessionState
            mode="StateServer"
            stateConnectionString="tcpip=your_server_ip:42424"
            cookieless="false"
            timeout="20" />
        <!-- ... -->
    </system.web>
</configuration>

[/code]

3. So you think you are finished...

...but that's not the case! Our load balancer did a great job, but both servers where returning different session data. We decided to take a look at the session ID in our cookie: it was the same for both machines. Strange!

Some research proved that it was ASP.NET's <machineKey> configuration which was the issue. Both web servers should have the same <machineKey> configuration. Let's edit Web.config one more time:

[code:xml]

<?xml version="1.0"?>
<configuration>
    <system.web>
        <machineKey
          validationKey="1234567890123456789012345678901234567890AAAAAAAAAA"
          decryptionKey="123456789012345678901234567890123456789012345678"
          validation="SHA1"
          decryption="Auto"
        />
        <!-- ... -->
        <sessionState
            mode="StateServer"
            stateConnectionString="tcpip=your_server_ip:42424"
            cookieless="false"
            timeout="20" />
        <!-- ... -->
    </system.web>
</configuration>

[/code]

(more on the machineKey element on MSDN)

Also check MS KB 325056, this was an issue we did not meet, but it might save your day.

4. Great success!

Our solution now works! Only problem left is that we have a new single point of failure (SPOF): the ASP.NET state service. But we might just set up 2 of those and fail over both session service machines.

UPDATE 2008-01-23: Also check out my blog post on Session State Partitioning!

kick it on DotNetKicks.com

PHP and OpenXML - New API project

It's been a while since I posted something related to PHP and OpenXML, but I fixed that glitch in this blog post.

Browsing CodePlex, I found a new PHP OpenXML project named OpenXML API. From what I can see, this project is just in the "Hello world" stage of creating Word documents in PHP, but not so long ago my PHPExcel project was in that stage too...

While I am talking about PHPExcel... Since today, the Subversion repository I host myself is synchronized 2 times a week with CodePlex's Team Foundation Server. If you want the latest source snapshot, please check PHPExcel's source code tab on CodePlex.

Advanced ASP.NET caching events

Currently, I'm giving an ASP.NET classroom training at our company, which actually is quite good for me: I needed to refresh all ASP.NET concepts, as those all fade away slowly when you don't use them for a while... Now, one of those refreshed concepts is ASP.NET's caching.

ASP.NET offers a flexible built-in caching mechanism, providing you with a global "Cache" object in which you can get and put data which needs to be cached for a while. One of the cool things about ASP.NET caching is that it actually listens to what you want: if you need the cache to expire after 10 minutes, it does so. Also, when memory is needed for other actions on the webserver, ASP.NET will gently clean the cache depeding on cache item priorities.

As for many things in this world, every good thing also has a downside... And here's the downside for ASP.NET's caching: when an item is removed from cache, you'll have to know and react to that. No problem, you say, as you can simply use an if-statement to fix things up. Here's a DataDet which will be cached to infinity (or untill memory is needed):

[code:c#]

if (Cache.Get("myDataSet") == null) {
    // Re-fetch data
    // ... DataSet ds = ....

    Cache.Insert(
        "myDataSet", ds, null, System.Web.Caching.Cache.NoAbsoluteExpiration, System.Web.Caching.Cache.NoSlidingExpiration
    );
}

[/code]

Great thing! But... What if I want to centralise cache creation? What if I want to log something everytime a cache item has been removed due to memory limits being reached? Luckily, ASP.NET provides an answer to that: the System.Web.Caching.CacheItemRemovedCallback delegate. This delegate can be used to ask ASP.NET to notigy you using a delegate of what is happening inside the cache when something is removed from it. Here's the delegate signature:

[code:c#]

void (string key, Object value, CacheItemRemovedReason reason);

[/code]

As you can see, you can get the key that's being removed, its current value, and the reason why the item is being deleted. These reasons can be: Expired, Removed, Underused, and DependencyChanged. I think these speak for themselves, no?

Now let's implement this: I'll create a CacheRetrievalManager which will update my cache whenever an item is removed from cache:

[code:c#]

using System;
using System.Web.Caching;

public class CacheRetrievalManager
{
    public void RemovedCacheItemHandler(string key, Object value, CacheItemRemovedReason reason)
    {
        switch (key)
        {
            case "myDataSet":
                // call method to re-fetch data and re-set cache
                // ...
                break;
        }
    }
}

[/code]

One thing left is to specify that this method should be called whenever a cache item is removed:

[code:c#]

// Insert in cache ONCE, recreation will be handled by CacheRetrievalManager
DataSet ds = ...;
Cache.Insert(
    "myDataSet", ds, null, System.Web.Caching.Cache.NoAbsoluteExpiration, System.Web.Caching.Cache.NoSlidingExpiration, CacheRetrievalManager.RemoveCacheItemHandler
);

[/code]

Now I know exactly why something is removed, and that I can even log when this happens. You can now further extend this into separate CacheRetrievalManagers for every object you which to cache, fetch data inside that manager, ...