Showing posts with label scalability. Show all posts
Showing posts with label scalability. Show all posts

Tuesday, 20 April 2010

Building A PageStatePersister With AppFabric

One of my favourite demos to do at user group sessions is to show off how you can use the SessionPageStatePersister to store page state information (Viewstate and Controlstate) on the server in Session state instead of round-tripping it to the client. But yesterday it occurred to me that you could do the same thing quite easily with AppFabric caching*.

ASP.NET has shipped with two page state persistence mechanisms since ASP.NET 2.0 was released - HiddenFieldPageStatePersister, which is the one used by default and produces those <input id="__VIEWSTATE" type="hidden" value="AnEnormousBase64EncodedTree" /> tags that we all hate so much, and SessionPageStatePersister, which instead stores the state on the web server in Session state and cuts out the roundtripping. To use the SessionPageStatePersister you need to write an Adapter class and a .browser file:

using System.Web.UI;
using System.Web.UI.Adapters;

namespace Adapter
{
    public class Adapter: System.Web.UI.Adapters.PageAdapter
    {
        public override PageStatePersister GetStatePersister()
        {
            return new SessionPageStatePersister(this.Page);
        }
    }

<browser refID="default">
    <controladapters>
        <adapter controltype="System.Web.UI.Page">
            adapterType="Adapter.Adapter" />
    </controladapters>
</browser>

SessionPageStatePersister is built into the .NET Framework, so our adapter can just new it up and return it in the GetStatePersister function. If we want to build a PageStatePersister with AppFabric, however, we've got a bit more work to do. We need a class that inherits from PageStatePersister so we can implement the Save and Load methods.

using System.Web.UI;
using System.Web.UI.WebControls;

namespace AppFabricPageStatePersister
{

    public class Persister : System.Web.UI.PageStatePersister
    {
        public Persister(Page page) : base(page)
        {
        }


        private const string HiddenFieldId = "StateIdHiddenField";


        public override void Load()
        {
            // Read page's incoming state Id from hidden field
            string stateIdString = Page.Request.Form[HiddenFieldId];


            Pair cachedState = (Pair) CacheHelper.Get(stateIdString);


            ViewState = cachedState.First;
            ControlState = cachedState.Second;
        }


        public override void Save()
        {
            Pair statePair;


            // Build a Pair from the Page's View- and ControlState
            statePair = new Pair(ViewState, ControlState);


            // Generate a new ID to use as the key for storing in the cache
            Guid stateId = Guid.NewGuid();


            // Put the Pair in the cache
            CacheHelper.Put(stateId.ToString(), statePair);


            // Write the key out into the page in a hidden field so we can get it back later
            Page.ClientScript.RegisterHiddenField(HiddenFieldId, stateId.ToString());
        }
    }
}

CacheHelper is an abstraction over AppFabric so my Persister class isn't cluttered with calls to the AppFabric objects:

using System.Web.Configuration
using Microsoft.ApplicationServer.Caching;

namespace AppFabricPageStatePersister
{
    class CacheHelper
    {
         public static object Get(string Key)
         {
            DataCacheFactory factory;
            DataCache cache;
            string cacheName;
            factory = new DataCacheFactory();
            cacheName = WebConfigurationManager.AppSettings["StatePersistenceCacheName"].ToString();
            cache = factory.GetCache(cacheName);
            return cache.Get(Key);
       }

       public static void Put(string Key, object Value)
       {
            DataCacheFactory factory;
            DataCache cache;
            string cacheName;

            factory = new DataCacheFactory();
            cacheName = WebConfigurationManager.AppSettings["StatePersistenceCacheName"].ToString();
            cache = factory.GetCache(cacheName);

            cache.Put(Key, Value);
        }
    }
}

The adapter is straightforward - like the SessionPageStatePersister adapter, all it needs to do is return a new instance of AppFabricPageStatePersister, and the .browser file just needs to point at AppFabricPageStatePersister.Adapter.

namespace AppFabricPageStatePersister

{
    public class Adapter : System.Web.UI.Adapters.PageAdapter
    {
        public override System.Web.UI.PageStatePersister GetStatePersister()
        {
            return new Persister(this.Page);
        }
    }
}

<browser refID="default">

    <controlAdapters>
        <adapter controlType="System.Web.UI.Page"
adapterType="AppFabricPageStatePersister.Adapter" />
    </controlAdapters>
</browser>
Simples.

Download this demo code: C# VB

* Yes, I know you could put your Session state in AppFabric and continue to use the SessionPageStatePersister but I'm assuming you can't/don't want to use Session state.

Wednesday, 24 June 2009

VBUG London

My thanks to VBUG London for allowing me to speak on Tuesday night, and also to Sam and Keith for organising it. I hope everyone found the session useful - I really enjoyed it. And the pizza was good too :-)

I've updated the slides and code samples at http://www.philippursglove.com/ScalableASPNET - the Velocity demo is now updated to Velocity CTP3. I re-ran the Velocity demo yesterday morning and it worked perfectly. I suspect that it failed on Tuesday as my laptop couldn't contact a domain controller to verify my admin credentials :-(

Saturday, 18 April 2009

ASP.NET Scalability at WebDD

Thanks to everyone who attended my session on ASP.NET scalability at WebDD today - I hope you all found it useful. I got to the end of the section on caching, which is more or less the first half, and realised I had ten minutes left for the entire second half, so apologies for having to race through the rest of the slides. If anyone has any questions please feel free to post them in the comments here and I'll answer them.

I had a few questions afterwards over coffee, which were:
Q) Output Caching. Can you VaryBy things other than elements in the QueryString?
A) Yes, there are a range of VaryBy options: VaryByContentEncoding, VaryByControl, VaryByHeader, and VaryByCustom. In each case ultimately it boils down to a string. There's a discussion of all these options on MSDN, but basically VaryByContentEncoding's probably not going to help you too much since this looks at what encodings your browser can accept e.g. compressed content (and remember that I mentioned all the current browsers (and previous generation browsers) can accept compressed content). VaryByHeader looks at a semi-colon seperated list of HTTP headers. VaryByControl looks at the controls declared inside a UserControl. VaryByCustom is perhaps the most interesting as it allows you to roll your own scheme by implementing GetVaryByCustomString in your Global.asax file, or if you set it to 'browser' it caches page instances based on the browser name and major version.

Q) Is there a reason not to use VaryByParam=*?
A) VaryByParam=* will cache pages on all combinations of elements in the QueryString - I can only tell you what it does, it's up to you to decide whether this facility is going to fit into your application or not.

Q) Can you cache objects for longer than 20 minutes?
A) Yes. If you use sliding expiration, it takes a System.TimeSpan object. TimeSpan has three constructors - I used the hours/minutes/seconds constructor, but there's nothing to stop you using the second constructor which adds a days parameter onto the constructor. Bear in mind, however, that doing that means you're sacrificing that much memory on your server for that length of time. As with so many things in scalability, it's a trade-off...

Q) Do you need command-line access to your server to enable a SQL database for SQLCacheDependencies?
A) No. There's two options here: one is to keep in mind that the ASPNET_REGSQL command-line tool takes a server name from the -S parameter - this can be any SQL server that is on your network, you don't have to run the tool locally to the server you're enabling.
The second option is to use the SqlCacheDependencyAdmin static class, which gives you the ability to programmatically enable and disable databases and tables for cache dependencies.

Thursday, 26 March 2009

Speaking at WebDD

I had an email from Phil Winstanley overnight to say that my talk on writing scalable ASP.NET has been accepted for WebDD at Reading on 18th April.

I'm looking forward to running it again, this'll be the third outing. Maybe Barry'll get to it this time...

Update: Delegate registration is now open. Book early to avoid disappointment.

Wednesday, 4 March 2009

Thanks to NxtGen Oxford

Thanks to everyone at NxtGen Oxford who came to see my ASP.NET scalability presentation last night. The code and slides are available for you to download at http://www.philippursglove.com/ScalableASPNET. I haven't had chance yet to work out why the SqlCacheDependency demo didn't behave, I'll look at this later on today and update this blog entry when I've worked it out! Feel free to email me at phil@philippursglove.com or comment here if there are things that need more explanation or you just want to discuss something.

Update: I just figured out why the aspnet_regsql command-line was failing when I tried to enable my Northwind database for caching. I hibernated my laptop when I left the office on Tuesday afternoon, on Tuesday evening when I switched it back on it couldn't contact our domain. So when I tried to run aspnet_regsql with the '-E' switch for integrated authentication, I couldn't be authenticated against our domain and consequently SQL Server's security (correctly) wouldn't let me do anything. If I'd instead used the '-U' and '-P' switches with an administrator username and password it would have worked.

Sunday, 23 November 2008

DDD7 - ASP.NET Scalability

I'd like to thank the organisers and attendees for giving me the opportunity to present 'This One Goes Up To 11, or How To Write Scalable ASP.NET' yesterday at DDD7 at Microsoft in Reading. It was my first time presenting at DDD, and I really enjoyed it. My session seemed to go quite well, there were a couple of demos I need to go through again to figure out why they didn't work, but overall I felt it was quite well received. All the sessions were videoed for Channel 9 , which is both quite exciting and means I'll get to see the sessions I couldn't get into :-)

A session I did get into was Phil and Dave's ASP.NET 4.0 runthrough. A lot of things are still under wraps, but one of the most useful things they demoed was the ability to set a control's ID and not have it munged with the control's container name when the html hits the browser. Which is nice. I feel validated in being unsure about MVC now I've heard that Phil's not keen on. It'll be interesting to see the new provider-based caching model when it arrives, especially as this will fit really well with Velocity. I'm not, however, at all sure about the new WPF-based Studio.

Edit: My slides and code samples are now available at www.philippursglove.com/ScalableASPNET.

Friday, 10 October 2008

How to Write Scalable ASP.NET

Like Barry, I got my email last night to tell me my talk on writing ASP.NET that scales sensibly (or indeed at all given the standard of code I've been reworking for the last two weeks) has been selected for inclusion at DDD7.

Suddenly I'm rather nervous and wondering if I can actually pull this off...