Scott Hanselman

A multi-request-safe ViewState Persister

June 23, '04 Comments [7] Posted in ASP.NET | ViewState
Sponsored By

Mark Miller has posted his code for a ViewStatePersister using the "common sense but not obvious" GUID technique that was outlined previously by Scott Mitchell and myself.

He stores a GUID in the ViewState hidden field, and sticks the bloated ViewState in a temp file on the server.  It doesn't solve the problem when running multiple web servers while using stateless balancing (meaning: NOT using sticky sessions/node affinity) but it's the most elegant and complete solution I've seen yet and should work great on a single web server. 

A few questions I have though:

  • When do the files get cleaned up and how often? Do you clean up old ones in a background thread within ASP.NET or a separate Windows Service?  Thought: I wonder if you could delete them after immediately after the Load?  You wouldn't be able to RE-post data, but it'd be cleaner, no?
  • GUID generation is very expensive, and can really slow you down under load.  I wonder if it would be faster/easier to have a single long and use InterlockedIncrement or  InterlockedIncrement64 to safely increase the value on each call until it overflows and you start again at 0.

Many thanks to Mark for sharing!

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web
Wednesday, June 23, 2004 8:16:39 PM UTC
Excellent idea using a long with InterlockedIncrement64. Guids are not only expensive to generate, but are suprisingly large when serialized. Guid was the low-hanging fruit for me.

We had a long discussion on the clean-up. We were already using a charting solution that generates alot of temp files, so we already had a mechanism that cleans up after that. We toyed with having the persister clean up the files, but ran into too many problems where we wanted to refresh. That was probably more due to us debugging than real users. We've also been very selective on which pages use this mechanism to keep disk usage low.
Thursday, June 24, 2004 5:15:44 AM UTC
Hi,

a combination of not cleaning up the files immediately and using sequentially incremented numbers can open up a security hole.

if i request a page and get a 'ViewState ID' of 5 - what would the webapp return if i manually set the ID to 4 or 6, e.g. ??? The ViewState of another user?

bye
dominick
Thursday, June 24, 2004 3:56:50 PM UTC
Presumably, but assuming there's no personal stuff there (cc#'s, etc) you'd get “unexpected” behavior, and probably viewstate from another page, so the load might fail. Icky, true, but unacceptable?
Scott Hanselman
Thursday, June 24, 2004 3:57:26 PM UTC
i just wanted to point it out (sorry, i'm a security guy blabla :)

when i do courses on web app security, i always teach the students to watch out for something that looks sequential, like

?userid=4

...

the idea is so reasonable that people might incorporate that and i wouldn't want to see a mail on securityfocus BugTraq

"predictable viewstate id vulnerability in xy" :)
Dominick Baier
Thursday, June 24, 2004 3:57:49 PM UTC
Valid point and I DO appreciate it! What about the Guid's being expensive to gen problem? What's a good balance between perf and security?
Scott Hanselman
Thursday, June 24, 2004 6:44:09 PM UTC
hmm - i did some tests - i also tried RNGCryptoServiceProvider (even more expensive)

but do you think that guid gen is _really_ that expensive (compared to all the other stuff going on in ASP.NET) ?

i am currently thinking about another way to achieve decent randomness....but nothing 'secure' comes to my mind....

well - if you want to have good random numbers, there's a price to pay i guess

or - what about generating a large random number once, store that in a session and increment this number...?

or - deleting the old viewstates immediately....

bye
dominick

thats what i did:

Test 1 (GUID)

using System;


public class MyClass
{
[System.Runtime.InteropServices.DllImport("KERNEL32")]
private static extern bool QueryPerformanceCounter(ref long lpPerformanceCount);
[System.Runtime.InteropServices.DllImport("KERNEL32")]
private static extern bool QueryPerformanceFrequency(ref long lpFrequency);


public static void RunTest()
{
for (int i = 0; i < 100000; i++)
{
string id = Guid.NewGuid().ToString();
}
}

public static void Main()
{
long frequency = 0;
QueryPerformanceFrequency(ref frequency);

long startTime = 0;
QueryPerformanceCounter(ref startTime);

RunTest();

long endTime = 0;
QueryPerformanceCounter(ref endTime);

float elapsed = (float)(endTime - startTime) / frequency;

Console.WriteLine("GUID {0:0000.000}ms ", elapsed);
Console.ReadLine();
}
}

Test 2 (RNG)

using System;
using System.Security.Cryptography;

public class MyClass
{
[System.Runtime.InteropServices.DllImport("KERNEL32")]
private static extern bool QueryPerformanceCounter(ref long lpPerformanceCount);
[System.Runtime.InteropServices.DllImport("KERNEL32")]
private static extern bool QueryPerformanceFrequency(ref long lpFrequency);


public static void RunTest()
{
byte[] rng = new byte[16];
for (int i = 0; i < 100000; i++)
{
new RNGCryptoServiceProvider().GetBytes(rng);
string id = Convert.ToBase64String(rng);
}
}

public static void Main()
{
long frequency = 0;
QueryPerformanceFrequency(ref frequency);

long startTime = 0;
QueryPerformanceCounter(ref startTime);

RunTest();

long endTime = 0;
QueryPerformanceCounter(ref endTime);

float elapsed = (float)(endTime - startTime) / frequency;

Console.WriteLine("RNG {0:0000.000}ms ", elapsed);
Console.ReadLine();
}
}
Dominick Baier
Wednesday, June 30, 2004 11:20:32 PM UTC
I don't really buy the "GUIDs are expensive" thing either. I think that may have been the case a long time ago when they were based on MAC addresses and that jazz, but GUIDs nowadays are randomly generated anyway. (Here's an article that covers the GUID format pretty well - http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetcomp/html/PPCGuidGen.asp )
Jason Mauer
Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.