Introduction
This article focuses on the Provider Model design pattern and describes how it can be used to solve certain problems. To help readers to better understand or appreciate this design pattern, I've chosen view state management as an example to demonstrate the usefulness and practicality of this design pattern. So, in this article, we will first understand what the inherent problems of view state are, I will then explain why Provider Model design pattern is important and how we can leverage it to solve the aforementioned problems.
Background
View State - a love-hate relationship with many ASP.NET developers. Developers love it because it does not require any server state resources and is simple to implement. Page and control state retains automatically across successive posts of the same page instance, which makes the magic of the Web Forms model possible. Furthermore, values in view state are hashed, compressed, and encoded for Unicode implementations, thus representing a higher state of security than hidden fields have. On the other hand, a couple of hot issues surround its use, primarily related to security and performance. For security, because view state is stored in a hidden field on the page. Although view state stores data in a hashed format, the information stored in the hidden field can be seen if the page output source is viewed directly, creating a potential security issue. For performance, because view state is stored in the page itself, storing large values can cause the page to slow down when browsers display or post it.
For those new to ASP.NET, there are a number of good resources for learning more about ASP.NET view state.
Fortunately, the Page
class can support alternative storage schemes for view state. The class contains a couple of protected virtual methods that the runtime uses to deserialize or serialize the view state. LoadPageStateFromPersistenceMedium
is used to restore the view state at the beginning of the page lifecycle. By contrast, a method named SavePageStateToPersistenceMedium
is used to persist the view state just before the page is rendered:
protected virtual void SavePageStateToPersistenceMedium (object viewState);
protected virtual object LoadPageStateFromPersistenceMedium();
By overriding both methods, you can manipulate the view state at your will. (Note that you cannot override only one method; you have to override both.) The typical solutions are:
- Compress the view state (Performance)
- Encrypt the view state (Security)
- Save view state in a server side file or database table and read/write the view state whenever that page is being processed. (Security and Performance)
Every proposed solution above has its pros and cons. As we all know, there is always a trade-off to choose between security and performance. The problem is how we can design our application in such a way that it can implement these solutions but selectively choose the best for a particular problem domain. For example, you might want to store the view state in a Microsoft SQL Server database for your development tasks and a different one (say Oracle database, which is requested by customer) in production. Choosing the right solution is therefore a matter of matching your needs with respect to how it can solve the problem effectively. Wouldn't it be great if you could implement these solutions on top of a repository that best suits your needs, while at the same time keep the application's architecture, design, and code independent of this choice?
Introducing the Provider Model Design Pattern
Provider Model design pattern derives from a number of widely accepted patterns, and was officially named in the summer of 2002. A "provider" is defined as a pluggable component that extends, or fully replaces, existing system functionality. In other words, the provider model allows you to unplug the default implementation of an API (for example, view state management, session state management, personalization, and so on) and plug your own in. By writing your own provider for a specific system feature that supports the model, you can change the underlying implementation, data formats, and storage medium, without disrupting the application design (keeping the top-level interface intact). More simply put: it allows developers to publish well-documented, easy to understand APIs (Object Model), but at the same time give developers complete control over the internals of what occurs when those APIs are called.
Features implementation that have providers must have a configuration section defined in the web.config (Web applications) or app.config (Windows applications) file. Its purpose is to register the available "providers" but choose one as the default provider. For example:
<!---->
<viewstate defaultProvider= "SqlViewStateProvider">
<add name="SqlViewStateProvider"
type="System.Web.Configuration.Providers.SqlViewStateProvider,
ViewStateProviders"
connectionString="SERVER=(local);DATABASE=(Database Name );USERID=sa;PWD=" />
<add name="CompressionViewStateProvider"
type="System.Web.Configuration.Providers.CompressionViewStateProvider,
ViewStateProviders"
connectionString="" />
</providers>
</viewstate>
Based on the configuration shown above, the following outlines a few important guidelines to be followed:
defaultProvider
Each feature configuration should specify the default provider, which instructs the system which of the listed providers to use. For example:
<viewstate defaultProvider="SqlViewStateProvider">
Provider-friendly name
When defining a provider within configuration, it is required for the name attribute to be defined. Furthermore, the provider names should follow a pattern to easily distinguish who owns the providers. The suggested pattern is: [Provider Creator][Data Store][Feature]Provider.
<addname="SqlViewStateProvider"
type="System.Web.Configuration.Providers.SqlViewStateProvider,
ViewStateProviders"
connectionString="SERVER=(local);DATABASE=(Database Name);USER ID=sa;PWD=" />
Table below calls out some of the common names and casing that should be used for various data stores (where name is [Name][Feature]Provider]. For example, SqlViewStateProvider
should be used to name the provider which uses SQL Server as the data store.
NAME |
Applies to |
Sql |
Any provider that uses SQL Server as the data store. |
Access |
Any provider that uses an Access/Jet database as the data store. |
Xml |
Any provider that uses an XML file as the data store. |
AD |
Any provider that uses Active Directory as the data store. |
File |
Any provider that uses a file as the data store. |
Memory |
Any provider that uses an in-memory data store. |
Provider type
When defining a provider within configuration, it is required for the type attribute to be defined. The type value must be a fully qualified name following the format:
type="[namespace.class], [assembly name], Version=[version],
Culture=[culture], PublicKeyToken=[public token]"
The strongly typed name is desired, however, it is also legitimate to use the shorter style assembly type name. For example:
type="System.Web.Configuration.Providers.SqlViewStateProvider, ViewStateProviders"
To learn more about the Provider Model design pattern, please read Provider Model Design Pattern and Specification, written by Rob Howard.
ViewState Provider Object Model
The model defines a set of classes to support the view state provider framework. The diagram shown below depicts the ViewState Provider Object Model and its interaction with Page.
ViewStateManager
- It exposes two static methods, LoadPageState
and SaveViewState
, which are used by the application (Page, in our context) to load or save its view state, respectively. It contains no business logic; instead it simply forwards these calls to the configured provider, say SqlViewStateProvider
. It is the responsibility of the SqlViewStateProvider
provider class to contain the implementation of these methods, calling whatever Business Logic Layer (BLL) or Data Access Layer (DAL) to complete the job.
public static System.Object LoadPageState(System.Web.UI.Control pControl)
public static void SavePageState (System.Web.UI.Control pControl,
System.Object viewState)
ProviderBase
- is used to mark implementers as a provider, and forces the implementation of a required method and property common to all providers.
ViewStateProviderBase
- it exposes a standard interface (well-known APIs) to the view state management service, and all the custom ViewState providers must inherit from this class. The well-known APIs are: public abstract System.Object LoadPageState(System.Web.UI.Control pControl);
public abstract void SavePageState(System.Web.UI.Control pControl,
System.Object viewState);
ViewStateConfigurationHandler
- it interprets and processes the settings defined in XML tags within the <viewstate>
section in the Web.config file and returns an appropriate configuration object based on the configuration settings.
ViewStateConfiguration
- it contains the settings information for all the ViewState providers defined in the Web.config file.
SqlViewStateProvider - The implementation
SqlViewStateProvider
provider inherits from ViewStateProviderBase
class, it uses SQL Server as the data store to store and retrieve the view state of pages during the page life cycle. The table schema designed for storing the view state is as simple as the diagram shown below:
Field Name |
Data Type |
Description |
vsKey |
NVARCHAR(100) |
An unique key to identify the view state of a particular page |
vsValue |
NTEXT |
The view state of a page. |
TimeStamp |
DATETIME |
The last visit timestamp of this view state |
Before a page renders its output, the SavePageState
method will be called by the framework to save the page's view state. Internally, it checks if a hidden field (a.k.a. HtmlInputHidden
control) named "__vsKey
" exists in the page's Controls
collection, the hidden field will be created if it doesn't exist. The method's code simply creates an instance of the LosFormatter
object and invokes its Serialize()
method, serializing the passed-in view state information to the StringWriter
writer. Following that, a globally unique identifier (GUID) will be generated and used as the key to save the view state of a particular page to a database table. Last, but not least, this GUID is stored in the hidden field of this page.
When the page posts back, LoadPageState
method will be called by the framework to retrieve the saved view state of a particular page. This is accomplished by using the GUID stored in the "__vsKey
" hidden field on the last visit to lookup the view state in the database table, and returning the deserialized object via the Derialize()
method of LosFormatter
.
There is one problem here, each time a user visits a different page, a new record holding that page's view state will be created in the database table. Over time, this will lead to millions of records, which may seriously affect the performance of the lookup process. Some sort of automated task would be needed to periodically clean out the view state records older than a certain date. I leave this as an exercise for the reader.
The diagrams below show the differences before and after using the SqlViewStateProvider
provider to store the view state in database instead of storing them in a hidden field named "__VIEWSTATE
" of the page.
View State stored in hidden field (Before)
View State stored in database (After)
Using the code
This article ships with two view state providers, available from the link at the top of this article. One of the providers store view state into a SQL Server database table, which is described throughout this article, and the other has the capability of compressing the view state. As noted earlier, if the default functionality of these providers do not meet your needs, you can create your own provider and plug it into the framework.
With ViewState Provider Framework in place, developing a custom view state provider is as easy as the few steps listed below:
- Create a new class and derive it from
ViewStateProviderBase
class. (Remember to follow the naming patterns for provider classes).
- Implement the
LoadPageState
and SavePageState
APIs.
- Add the new class to the
<providers>
section in the Web.config file.
- Set the
defaultProvider
attribute's value of <viewstate>
section in the web.config file to the newly created provider name.
Conclusion
The provider design pattern allows developers to have a flexible design and a rich, enterprise-level extensibility model. More importantly, you can very easily use one provider for your development tasks, and a different one for the application in production, simply by changing some configuration. Last, but not least, this is an important new design pattern in ASP.NET 2.0, which is extensively used in many common web application features like site membership, role management, personalization, etc. So, start using it in your applications today, and and you'll be ahead of the curve in understanding this design pattern in ASP.NET 2.0 tomorrow.