A multi-request-safe ViewState Persister
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.
About Newsletter
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
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" :)
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();
}
}
Comments are closed.
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.