|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionView State is a mechanism employed by ASP.NET web pages to persist the state of the page itself, individual controls, objects, and data that are housed on that particular ASP.NET web page. View State is a double edged sword in that using it properly allows the developer to build full and robust web applications that seem to overcome the stateless nature of the web. As the complexity of our web applications grow, more and more controls get added to the screen, more and more data needs to be persisted... View State grows. There's no way to avoid it. Even if we are diligent in our efforts in only making sure that those things that need View State are the only ones with it turned on, View State can grow to several kilobytes in size. It's not uncommon to have View State sizes that run into the 50K to 100K range on custom intranet/extranet applications. BackgroundScott Mitchell wrote an excellent article on View State some years ago that was published on MSDN. I won't go into the details on View State that he does (since he's already done an excellent job), but I will mention a few things:
Requirements
Control Adapters to the RescueSince the ASP.NET MSDN defines control adapters thusly: Control adapters are components that override certain I discovered this rather excellent article by Robert Boedigheimer on using Server Side View State in ASP.NET using the My solution actually contains two parts. The first part is the control adapter PageStateAdapterTo solve my requirements #4 and #5, I decided to go with a simple attribute scheme, where basically, the developer can decide to use a different View State persistence scheme simply by putting an attribute at the top of the page's class definition. The attribute class and supporting enum are both defined in the public enum StateStorageTypes { Default, Cache, Session, InPage }
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class PageViewStateStorageAttribute : Attribute
{
private readonly StateStorageTypes storageType = StateStorageTypes.Default;
public PageViewStateStorageAttribute(StateStorageTypes stateStorageType)
{
storageType = stateStorageType;
}
internal StateStorageTypes StorageType
{
get { return storageType; }
}
}
Now, all the public override PageStatePersister GetStatePersister()
{
PageViewStateStorageAttribute psa =
Attribute.GetCustomAttribute(Page.GetType(),
typeof(PageViewStateStorageAttribute), true) as
PageViewStateStorageAttribute ??
new PageViewStateStorageAttribute(StateStorageTypes.Default);
PageStatePersister psp;
switch (psa.StorageType)
{
case StateStorageTypes.Session:
psp = new SessionPageStatePersister(Page);
break;
case StateStorageTypes.InPage:
psp = new HiddenFieldPageStatePersister(Page);
break;
default:
psp = new CachePageStatePersister(Page);
break;
}
return psp;
}
If a developer wishes to override the (now) default View State persistence method of [PageStateAdapter.PageViewStateStorage(PageStateAdapter.StateStorageTypes.InPage)]
public partial class ViewStateInPage : System.Web.UI.Page
CachePageStatePersisterThe public override void Save()
{
if (ViewState != null || ControlState != null)
{
if (Page.Session == null)
throw new InvalidOperationException(
"Session is required for CachePageStatePersister (SessionID -> Key)");
string vsKey;
string cacheFile;
// create a unique cache file and key based on this user's
// session and page instance (time)
if (!Page.IsPostBack)
{
string sessionId = Page.Session.SessionID;
string pageUrl = Page.Request.Path;
vsKey = string.Format("{0}{1}_{2}_{3}", VSPREFIX, pageUrl, sessionId,
DateTime.Now.Ticks);
string cachePath = Page.MapPath(CACHEFOLDER);
if (!Directory.Exists(cachePath))
Directory.CreateDirectory(cachePath);
cacheFile = Path.Combine(cachePath, BuildFileName());
}
// get our vs key from the page, re use it, and the cache
// file (pulled from page.cache)
else
{
vsKey = Page.Request.Form[VSKEY];
if (string.IsNullOrEmpty(vsKey)) throw new ViewStateException();
cacheFile = Page.Cache[vsKey] as string;
if (string.IsNullOrEmpty(cacheFile)) throw new ViewStateException();
}
IStateFormatter frmt = StateFormatter;
string state = frmt.Serialize(new Pair(ViewState, ControlState));
using (StreamWriter sw = File.CreateText(cacheFile))
sw.Write(state);
Page.Cache.Add(vsKey, cacheFile, null, DateTime.Now.AddMinutes(
Page.Session.Timeout),
Cache.NoSlidingExpiration, CacheItemPriority.Low,
ViewStateCacheRemoveCallback);
Page.ClientScript.RegisterHiddenField(VSKEY, vsKey);
}
}
In our If the page is requested from a So, we are mindful of finite server resources by only storing the file path in cache, we are mindful of download page size by only storing the unique key in the page (instead of the entire View State), and by using the public static void ViewStateCacheRemoveCallback(string key,
object value, CacheItemRemovedReason reason)
{
string cacheFile = value as string;
if (!string.IsNullOrEmpty(cacheFile))
if (File.Exists(cacheFile))
File.Delete(cacheFile);
}
When the cache makes the callback, it passes on what that cache object contained, which we know is the file path to the persisted View State object. All we have to do when we receive the callback is to delete the physical file. The public override void Load()
{
if (!Page.IsPostBack) return;
// We don't want to load up anything if this is an inital request
string vsKey = Page.Request.Form[VSKEY];
// Sanity Checks
if (string.IsNullOrEmpty(vsKey)) throw new ViewStateException();
if (!vsKey.StartsWith(VSPREFIX)) throw new ViewStateException();
IStateFormatter frmt = StateFormatter;
string state = string.Empty;
string fileName = Page.Cache[vsKey] as string;
if (!string.IsNullOrEmpty(fileName))
if (File.Exists(fileName))
using (StreamReader sr = File.OpenText(fileName))
state = sr.ReadToEnd();
if (string.IsNullOrEmpty(state)) return;
Pair statePair = frmt.Deserialize(state) as Pair;
if (statePair == null) return;
ViewState = statePair.First;
ControlState = statePair.Second;
}
Some points of interest here are... if the page is not working as a post back, we don't want to load our View State from persistence. This solves the problem of a user loading stale data. The The Browsers FileThe last point of order is to wire up the <browsers>
<browser refID="Default">
<controlAdapters>
<adapter controlType="System.Web.UI.Page" adapterType="PageStateAdapter" />
</controlAdapters>
</browser>
</browsers>
ConclusionView State, while a powerful tool, can very quickly become the demise of your users in experiencing your website to the fullest. By making use of built-in ASP.NET technologies like Cache and Control Adapters, we can effectively and accurately persist View State on the server instead of streaming it down to the user. Using the outlined In our code base, we've also added to the global Points of InterestWhen we first started down the path of persisting View State on the server, we were stuffing the entire thing into History
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||