Related Books
This column has been quiet for some time—sorry about that. I have a really, really good excuse, though: We've been busy working on the next version of the Microsoft® .NET Framework and Microsoft® Visual Studio® .NET, code-named "Whidbey."
Busy is actually an understatement. For Microsoft® ASP.NET, we've roughly doubled the number of class libraries! Is that a good thing? Yes it is, and in the coming months we'll explore some of these new ASP.NET features. However, before focusing all of our attention towards Whidbey, we'll spend the first several articles on ASP.NET 1.1 and relate it to what's to come in Whidbey.
Our first article is about the new Whidbey provider-model design pattern and how you can start using it in your ASP.NET 1.1 applications today.
There has been a lot of buzz recently surrounding the announcement of the next version of Microsoft® Visual Studio® .NET and the Microsoft® .NET Framework code-named "Whidbey." No, this isn't another Whidbey article, but it is an article that discusses an important new design pattern that is going to show up in Whidbey, and more importantly, a pattern you can start using in your applications today.
In the next article, we'll look at implementing this pattern in an ASP.NET 1.1 application. In this article, we'll discuss why the pattern is needed, and walk through the "Whidbey" provider specification. Let's look at some of the reasons why we need the provider design pattern first.
Fixed Choices
In published APIs you are usually limited to a fixed number of options. An example I commonly use is the Microsoft® ASP.NET Session State feature. ASP.NET Session State supports an "out-of-process" mode. Using either Microsoft® SQL Server or the ASP.NET State Server, you can store user's Session data in a process separate from the running application. There are many benefits: reliability, easy Web farm support, and scalability. Most people tend to lean towards the database solution, since it allows for replication and other benefits not offered by the State Server. One small problem: What if you're not a SQL Server customer?
Fixed Internal Behaviors
Here's another common situation, again using Session State. Say you want to change some internal behaviors of ASP.NET APIs, for example changing how ASP.NET Session sets or retrieves values from its out-of-process store. Today you're constrained by the design. It is all or nothing: either we fetch all your data, or we fetch none of your data. If you're storing 5 MB of data and only need 50 KB to satisfy the current request... You get the picture.
Fixed Data Schema
The final scenario relevant to this discussion concerns schema. A schema is the logical model of how data is stored. For example, if you ever used Microsoft® Site Server 3.0 Personalization, it forced you to move your data into the schema that it desired. That's all well and good, but typically the people who built that schema don't know a whole lot about your business. In other words, fixed schemas are typically built to be really good for everyone, but excellent for no one.
These are common problems that all technologies share: the inability to change core behavior or functionality of published APIs.
Who Cares, I'll Write My Own
You definitely can create your own solutions for all these problems, but there are a lot of benefits to using applications with published APIs. One of the main benefits is that many developers know and understand what these APIs are and how they work. If you need to augment your staff, for instance, you can hire a consultant that is already familiar with these well-documented APIs. If you write your own, guess what; you also get to pay that person to learn your proprietary system.
What we really want is a way for everyone to get the benefits of well-documented and familiar programming interfaces, such as Session State, but the ability to completely control the internal Business Logic Layer (BLL) and Data Access Layer (DAL) of these APIs.
Introducing the Provider Design Pattern
The Provider Design Pattern is a pattern we've used in several of our ASP.NET Starter Kits, formalized in ASP.NET Whidbey, and one that we'll hope you'll start using for your applications, too. The theory of the pattern is that it allows us to define a well-documented, easy-to-understand API, such as our new Whidbey Membership APIs, but at the same time give developers complete control over the internals of what occurs when those APIs are called.
The pattern was officially named in the summer of 2002 when we were designing the new Personalization feature of ASP.NET Whidbey. We were struggling to design the "right" schema that worked for everyone—the Personalization feature of ASP.NET Whidbey adds a new Profile class to which you can add strongly typed properties, for example, FirstName , whose values are stored in a database. The problem was that we needed a flexible design, but at the same time we wanted it to be easily extensible without everyone having to be a DBA; turns out this simple requirement is an incredibly challenging problem.
On the one hand, we could have designed the system to be entirely schema driven. This would have meant that we would have designed what I call, for lack of a better name, vertical tables in the database. Rather than using traditional columns, there would be a set of tables used to define the schema and a data table used to map data to a particular type. Each new item in the data table would correspond to a type in the schema table identifying what type of property it was. Below is a very simplistic data model of this design:

Figure 1. A simple model for personalization
Using this data model, we would see something like the data below in the Schema table:

Figure 2. Proposed schema table
And data in the PersonalizationData table similar to the data below:

Figure 3. PersonalizationData table
As you can see, this model maps the property type (such as FirstName ) to a property value in the PersonalizationData table, such as FirstName : Rob.
While being infinitely flexible, this model starts breaking down with a large number of users and properties. For example, while testing this design, we used the data from the ASP.NET Forums. At the time, this was 200,000 users, each with about 15 properties. This resulted in 3,000,000 rows in the data table! An important benefit that SQL can provide, namely normalized data, is lost. We also knew that people would want to use standard SQL syntax for selecting from the tables for reporting and maintenance—for example, try running a report on this type of design for all who live in a certain zip code using the vertical table design—not easy!
The obvious solution was to use normal tables, but we didn't want users to have to call a DBA to modify the tables for incremental changes. For example, to add a new property called ZipCode would require adding a new column to the Person table.
The conundrum was that we could choose either a very flexible schema-based design that had scale limitations, or a super-scalable design that might be too inflexible for some people. Furthermore, beyond the simple data schema problems, we also wanted partitioned data access, and we wanted the Personalization APIs to be extensible. We wanted a lot!
What did we decide? Well, we decided that each customer scenario would be unique. We knew that we would eventually want to implement it ourselves on www.asp.net, and would want control over the data model. To solve the problem, we borrowed a pattern we had been experimenting with in the ASP.NET Forums.
The Pattern
The pattern itself is exceedingly simple and is given the name "provider" since it provides the functionality for an API. Defined, a provider is simply a contract between an API and the Business Logic/Data Abstraction Layer. The provider is the implementation of the API separate from the API itself. For example, the new Whidbey Membership feature has a static method called Membership . ValidateUser() . The Membership class itself contains no business logic; instead it simply forwards this call to the configured provider. It is the responsibility of the provider class to contain the implementation for that method, calling whatever Business Logic Layer (BLL) or Data Access Layer (DAL) is necessary.
There are some rules for how a provider behaves. A provider implementation must derive from an abstract base class, which is used to define a contract for a particular feature. For example, to create a membership provider for Oracle, you create a new class OracleMembershipProvider , which derives from MembershipProviderBase . The feature base class, for example, MembershipProviderBase , in turn derives from a common ProviderBase base class. The ProviderBase class is used to mark implementers as a provider and forces the implementation of a required method and property common to all providers. Figure 4 gives an example of the inheritance chain.
![]()
Figure 4. A provider inheritance chain
The implementer must also define a configuration section entry used to load the provider. Below is an example of what this configuration entry looks like (borrowed from the ASP.NET Forums):
1<configuration>
2<forums>
3<forums defaultlanguage="en-en" defaultprovider="SqlForumsProvider">
4<providers>
5<clear></clear>
6<add connectionstring="[connection string]" databaseowner="dbo" name="SqlForumsProvider" type="AspNetForums.Data.SqlDataProvider,
7 AspNetForums.SqlDataProvider"></add>
8<add filelocation="~\data\AccessDataProvider\AspNetForums.mdb" name="AccessForumsProvider" type="AspNetForums.Data.AccessDataProvider,
9 AspNetForums.AccessDataProvider"></add>
10</providers>
11</forums>
12</forums>
13</configuration>
We'll see what this all means momentarily.
The beauty of this pattern is you can create a new class, for example, OracleForumsProvider , by deriving from the ForumsProvider base class and use Oracle as your data store. Or, if you prefer to continue using SQL Server, but wish to change the behavior of a single method, you can derive from SqlForumsProvider , override the method whose behavior you want to change, and then add that new class to the
1<providers> section of configuration. More simply put: the provider design allows developers to publish well-known APIs and also allows for a rich, enterprise-level extensibility model.
2
3The following information comes directly from the provider specification for ASP.NET Whidbey.
4
5## Common Behaviors/Characteristics
6
7Below is a listing of characteristics common to providers.
8
9#### Base class
10
11A base class should have as few methods and properties as possible. This is desirable to encourage developers to write providers.
12
13#### Threading
14
15Providers should be free-threaded/thread safe, with one instance per application domain. Any provider-specific objects created more frequently (for example, one per request) should be created through provider APIs.
16
17#### Factory methods
18
19The abstract base class should support factory methods to create new objects wherever appropriate, for example, **CreateUser()** .
20
21If a feature allows a provider to create a framework object, but does not allow the provider to extend the object, the framework class should be sealed.
22
23Complex objects created by a provider may keep track of the provider that created it, and expose it as a **Provider** property. This allows users of the feature to determine the provider that owns the data for the object. For example, when a new user is created with the Membership API, it may be useful for the developer to be aware of the provider that data for the object is stored in.
24
25#### Administration
26
27The object model for **ProviderCollection** should include APIs to add, remove, and clear the collection and to set parameters of individual providers in the collection.
28
29Every feature that has providers should have a **Provider** property that returns type **ProviderCollection** , for example, **Membershp.Providers** .
30
31#### Class naming and namespaces
32
33The specific provider base classes should be named [Feature]ProviderBase, for example, **MembershipProviderBase** .
34
35Implementations should be named [ImplementationType][Feature]Provider, for example, **SqlRoleManagerProvider** .
36
37Server controls that allow selection of a provider should have a property named **Provider** . This property should default to the value of the **defaultProvider** attribute in the related configuration section for the feature. For example, the new <asp:login></asp:login> server control's default provider is equivalent to the Membership default provider. The list of providers available to the login control is restricted by the providers defined for use in the <membership> section.
38
39#### Common naming patterns for provider classes
40
41Figure 5 below calls out some of the common names and casing that should be used for various data stores (where name is [Name][Feature]Provider).
42
43
44
45**Figure 5. Suggested prefixes for provider class names**
46
47For data stores that are not named above, the name value should be Pascal cased.
48
49#### Configuring the default provider
50
51All features that have providers must have a configuration section and should define a defaultProvider attribute. If a default provider is not specified, the first item in the collection is considered the default. For example:
52
53
54 <configuration>
55<system.web>
56<rolemanager ...="" defaultprovider="AspNetSqlProvider">
57<providers>
58<add description=" description here " name="AspNetWindowsProvider" type="System.Web.Security.WindowsTokenRoleProvider,
59 System.Web,
60 Version=1.2.3300.0, Culture=neutral,
61 PublicKeyToken=b03f5f7f11d50a3a"></add>
62<add connectionstringname="[name in <connectionStrings/>" description=" description here " name="AspNetSqlProvider" type="System.Web.Security.SqlRoleProvider, System.Web,
63 Version=1.2.3300.0, Culture=neutral,
64 PublicKeyToken=b03f5f7f11d50a3a"></add>
65</providers>
66</rolemanager>
67</system.web>
68</configuration>
69
70
71Specifying a defaultProvider attribute is not required, although it is highly recommended. All ASP.NET providers will specify a defaultProvider attribute.
72
73#### Provider-friendly name or dictionary key
74
75When defining a provider within configuration, it is required for the name attribute to be defined. Furthermore, it is recommended that provider names follow a pattern to easily distinguish who owns the providers. The suggested pattern, and the pattern followed by the ASP.NET team is: [Provider Creator][Data Store]Provider.
76
77For example, the Membership provider capable of storing data in SQL Server is:
78
79
80 <add connectionstringname="[name in <connectionStrings/>" description="ASP.NET SQL Server provider" name="AspNetSqlProvider" type="System.Web.Security.SqlRoleProvider, System.Web,
81 Version=1.2.3300.0, Culture=neutral,
82 PublicKeyToken=b03f5f7f11d50a3a"></add>
83
84
85It is important to note the friendly name of the provider when specified in configuration is distinct from the class name of the provider. The friendly name does not need to include the feature name, for example, Role, since the friendly name values are only usable within the context of the feature, for example, Roles.Providers["AspNetSqlProvider"]. The provider creator detail is added to distinguish between providers that may access similar data stores but pose differing behavior.
86
87#### Provider type
88
89When defining a provider within configuration, it is required for the type attribute to be defined. The type value must be a fully qualified type name following the format:
90
91
92 type="[namespace.class], [assembly name],
93 Version=[version], Culture=[culture],
94 PublicKeyToken=[public token]"
95
96
97For example:
98
99
100 type="System.Web.Security.SqlRoleProvider, System.Web,
101 Version=1.2.3300.0, Culture=neutral,
102 PublicKeyToken=b03f5f7f11d50a3a"
103
104
105The strongly typed name is desired, however, it is also legitimate to use the shorter style assembly type name:
106
107
108 type="System.Web.Security.SqlRoleProvider, System.Web"
109
110
111#### Expected APIs
112
113Features that derive from the various provider base classes should follow the common pattern of supporting a **Provider** property and **Providers collection** property that allow the developer to access the default configured provider as well as the other providers specified in configuration. For example:
114
115
116 SqlMembershipProvider p = (SqlMembershipProvider) Membership.Provider;
117
118 AccessMembershipProvider a =
119 (AccessMembershipProvider)Membership.Providers["AspNetAccessProvider"];
120
121
122The **Provider** property and Providers collection allow for runtime access to the underlying **Provider** class instance. The **Provider** property returns the currently configured default provider, while the Providers collection returns a collection of all the available providers defined within configuration.
123
124#### Use of <connectionstrings></connectionstrings>
125
126All providers that require a connection to a database or require the use of a connection string to locate their data store should use the <connectionstrings></connectionstrings> section of configuration. Additionally, the name of the property on the provider should be **connectionStringName** with the value being the named value in <connectionstrings></connectionstrings>.
127
128A connection string value is required; there cannot be an internal default.
129
130For all providers shipped by Microsoft, it should not be possible to store a connection string in the provider definition. Rather the <connectionstrings></connectionstrings> section should be used. This will allow us to encrypt the connection string, further securing the application.
131
132An exception will be thrown when a provider attempts to use a connection string obtained from <connectionstrings></connectionstrings> that either does not exist or is invalid.
133
134#### Description attribute
135
136Although not an explicit requirement for all providers, any providers created by Microsoft should have a description attribute. The description attribute should contain a brief, friendly description that can be displayed in the Web administration tool or other displays.
137
138The description attribute is optional and it is not an error for the description attribute to be missing; empty string is assumed.
139
140#### String comparisons
141
142When any string comparisons are done from with the configuration system for providers, a culture-invariant string comparison is performed. This rule applies to the name value defined for providers.
143
144## Conceptual Specification
145
146The conceptual specification section addresses common classes shared by all providers, the collection class used to manage providers, and rules for how providers are managed within a features configuration section.
147
148#### ProviderBase
149
150All providers derive from a common **ProviderBase** class. By supporting this common abstract base class, all providers additionally share a common base type.
151
152
153 namespace System.Configuration.Provider {
154 abstract class ProviderBase {
155
156 // Methods
157 public abstract void Initialize(string name,
158 NameValueCollection config);
159
160 // Properties
161 public abstract string Name { get; }
162 }
163 }
164
165
166When a new instance of a provider is created, internally the **Initialize** () method is called. It is assumed that all provider instances will add additional functionality to the provider beyond **ProviderBase** , for example, **MembershipProvider** . However, all providers should still derive from **ProviderBase** .
167
168
169
170**Figure 6 Initialize method for ProviderBase**
171
172
173
174**Figure 7. Name property of ProviderBase**
175
176
177
178**Figure 8. Add method of ProviderCollection**
179
180
181
182**Figure 9. Clear method of ProviderCollection**
183
184#### ProviderCollection
185
186The **ProviderCollection** is a utility collection class used to manage classes that derive from **ProviderBase** . Features that implement providers can use the **ProviderCollection** to add, remove, or clear all providers. Internally, instances are cast to the correct type when a typed provider is needed.
187
188
189 namespace System.Configuration.Provider {
190
191 public class ProviderCollection : IEnumerable {
192
193 // Methods
194 //
195 public void Add(ProviderBase provider);
196 public void Clear();
197 public void Remove(string name);
198 public void SetReadOnly(string name);
199
200 // Properties
201 //
202 public ProviderBase this(string name) { get; set; }
203 public int Count { get; set; }
204 }
205
206 }
207
208
209
210
211**Figure 10. Remove method of ProviderCollection**
212
213
214
215**Figure 11. SetReadOnly property of ProviderCollection**
216
217
218
219**Figure 12. Add method of ProviderCollection**
220
221For example, when **Membership.Providers["AspNetSqlProvider"]** is requested, internally the following code is assumed:
222
223
224 public MembershipProvider this [string name] {
225
226 get { return (MembershipProvider) providerCollection[name]; }
227
228 ...
229 }
230
231
232#### Configuration
233
234Features that make use of providers must implement a <providers> configuration section within their feature's configuration slot. It is within the <providers> section that providers may be added, inherited providers may be cleared, or individual providers may be removed.
235
236Providers are defined at the machine or application level. Providers cannot be defined in web.config files in application directories that are not application roots.
237
238#### Setting the default provider
239
240All features that have providers should have a **defaultProvider** attribute on the main configuration section. If a default provider is not specified, the first item in the collection is considered the default. For example:
241
242
243 <configuration>
244<system.web>
245<rolemanager ...="" defaultprovider="AspNetWindowsProvider">
246<providers>
247<add description=" description here " name="Windows" type="System.Web.Security.WindowsTokenRoleProvider,
248 System.Web,
249 Version=1.2.3300.0, Culture=neutral,
250 PublicKeyToken=b03f5f7f11d50a3a"></add>
251<add connectionstringname="[name in <connectionStrings/>" description=" description here " name="AspNetSqlProvider" type="System.Web.Security.SqlRoleProvider, System.Web,
252 Version=1.2.3300.0, Culture=neutral,
253 PublicKeyToken=b03f5f7f11d50a3a"></add>
254</providers>
255</rolemanager>
256</system.web>
257</configuration>
258
259
260#### Managing providers in configuration: <providers>
261
262The <providers> configuration section contains 0 or more <add>, <remove>, or <clear> elements. The following rules apply when processing these elements:
263
264 1. It is not an error to declare an empty <providers></providers> element.
265 2. Providers inherit items from parent application configuration <add> statements.
266 3. It is an error to redefine an item using <add> if the item already exists or is inherited.
267 4. It is an error to <remove> a non-existing item.
268 5. It is not an error to <add>, <remove>, and then <add> the same item again.
269 6. It is not an error to <add>, <clear>, and then <add> the same item again.
270
271
272
273<clear> removes all inherited items and items previously defined. For example, an <add> declared before a <clear> is removed, while an <add> declared after a <clear> is not removed.
274
275
276
277**Figure 13. Add element for configuration**
278
279New providers are added using the <add> element nested beneath <providers>.
280
281
282
283**Figure 14. Remove element for configuration**
284
285
286
287**Figure 15. Clear element for configuration**
288
289## Conclusion
290
291The provider model can help you design flexible and easily evolved APIs for your applications. In fact, today it's being used in the ASP.NET Forums (http://forums.asp.net) as well as DotNetNuke (http://www.dotnetnuke.com), both of which are ASP.NET 1.1 applications. In Part 2 of this article, we'll examine an implementation of this specification/pattern that you can begin using in your ASP.NET 1.1 applications.
292
293After we conclude with the next provider article, we'll look at how to build a scalable database cache invalidation system for ASP.NET 1.1—something that many have tried, but few have succeeded with (I even had a stab at this about 3 years ago). If you've got other articles you'd like to see, let me know. My contact information is below.</providers></add></clear></add></clear></add></clear></add></clear></add></add></remove></add></remove></add></add></clear></remove></add></providers></providers></providers></providers></membership></providers>