Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Asp Net 2.0 Security Membership And Role Management

.pdf
Скачиваний:
51
Добавлен:
17.08.2013
Размер:
12.33 Mб
Скачать

Chapter 9

data-related tasks including loading and storing data as well as extracting and applying personalization data. You can swap out different pieces of personalization functionality both in Web PartsPersonalization and lower down in the provider layer, yet the WebPartManager is unaware of such changes because it interacts only with a WebPartPersonalization instance.

Site Navigation — The static SiteMap class acts as the main entry point for this feature. It will automatically initialize configured providers on your behalf. In this sense, it is a weak façade implementation because you typically call SiteMap.CurrentNode, after which you start working with SiteMapNode and SiteMapProvider instances directly.

Session — You interact with the Session feature through an instance of HttpSessionState, usually through the Session property on the current context or on a page. From your point of view the Session State feature is basically a dictionary where you can add and remove objects. However, the HttpSessionState object and the associated SessionStateModule hide the large amount of complexity involved in managing session. Tasks such as serialization/deserialization, managing session concurrency, and managing session setup and expiration all happen automatically with the complexities hidden from view.

Core Provider Classes

You have seen a number of the different support classes that are common to providers. In this section, you walk through each of the core classes so that you can see in one place the different provider-related classes.

System.Configuration.Provider Classes

The core provider classes that define the base functionality for a provider are found in the System

.Configuration.Provider namespace. These classes are available for use both in ASP.NET and nonASP.NET applications.

ProviderBase

Of course, the most important provider class is the base class from which most providers derive:

System.Configuration.Provider.ProviderBase. The class signature is:

public abstract class ProviderBase {

public virtual string Name { get }; public virtual string Description {get };

public virtual void Initialize(string name, NameValueCollection config);

}

Feature-specific provider definitions derive from ProviderBase, and as a developer you write custom providers that in turn derive from a feature’s provider base class definition. It is unlikely that you would ever author a provider that directly derives from ProviderBase because ProviderBase exposes very little functionality.

ProviderBase is abstract because that forces you to derive from it and it also would make little sense to new() up ProviderBase. However, the functionality that is available on ProviderBase is all virtual because ProviderBase does supply basic functionality common to all providers. If you have looked at

342

The Provider Model

the configuration sections for ASP.NET 2.0 provider-based features you notice that “name” and “type” are always present. Although it isn’t immediately obvious, all ASP.NET providers also have a configurable “description” attribute as well.

The type attribute is not exposed by ProviderBase, because by the time you have a concrete provider in hand, you know its type. However, the “name” and “description” attributes are available on ProviderBase. The read-only Name property is important because this is how you index into provider collections for various features that support defining multiple providers. The read-only Description property is mainly intended for administrative applications where you may want to see a list of the providers currently configured for an application.

By default, the ASP.NET providers contain localized resource strings for the descriptions. This means that if you query the Description property in a French application, you get back French text for each provider description; while in an English application you get back an English description. However, if you explicitly configure the “description” attribute in your web.config, providers always return the configuration value from the Description property, regardless of locale. The default implementation of ProviderBase.Description returns the Name property if for some reason a provider implementer forgot to explicitly initialize the description.

The most important method on ProviderBase is the Initialize method. Normally, this method is called during a feature’s initialization. As described earlier in the section on the Factory Method pattern, static feature classes use the ProvidersHelper class to call Initialize on each configured provider. The name parameter is the value of the name attribute from configuration, while the config parameter is the Parameters property from the ProviderSettings configuration class: the list of name-value pairs from the <add /> provider element sans “name” and “type.”

The default implementation of Initialize performs the following work on your behalf:

1.The method checks to see whether the provider has been initialized before. If the provider has already been initialized, it throws an exception. This means that provider implementers should always call base.Initialize to gain protection against double-initialization.

2.The name parameter is stored internally and is thus available from the Name property.

3.If a key called “description” is available in the NameValueCollection passed via the config parameter, the value is stored internally and thus is available from the Description property. Note that if the “description” key is found, it is removed from the NameValueCollection and is no longer available from the collection when control passes back to the provider.

The general approach provider implementers should take when using ProviderBase.Initialize is:

1.If a “description” attribute is not available from configuration, add a key called “description” to the NameValueCollection that is passed to Initialize. For the value you can follow ASP.NET’s approach and insert a localized value, or for simplicity you can add a hard-coded description of the provider.

2.Immediately after any logic for “description,” make a call to base.Initialize. This protects against double-initialization before your provider does anything substantial.

3.After the call to base.Initialize, your provider should carry out feature-specific initialization tasks.

343

Chapter 9

ProviderException

Sometimes when an error occurs within a provider, the built-in Framework exception classes don’t have anything that maps nicely to the problem. Furthermore, you may not want to create a plethora of custom exception classes for comparatively rare or obscure error conditions. The System.Configuration

.Provider.ProviderException class is intended as a convenient exception class for these cases. For example, the Membership providers throw a ProviderException if the password format is incorrect. Rather than creating a “PasswordFormatException” that would rarely occur, a ProviderException was used.

Realistically, whether you use ProviderException is more of a philosophical decision. The ASP.NET team didn’t want to spam the System.Web namespace with dozens of exception classes for one-off or rare error conditions. However, there is nothing wrong if you disagree with that approach and instead create a rich and detailed set of exceptions for your applications.

The class signature for ProviderException is very simple. It just derives from System.Exception:

[Serializable]

public class ProviderException : Exception

{

public ProviderException();

public ProviderException( string message );

public ProviderException( string message, Exception innerException );

protected ProviderException( SerializationInfo info, StreamingContext context );

}

There is no custom logic inside of ProviderException. Each of the nondefault constructor overloads simply calls the base constructor implementations in Exception.

ProviderCollection

As you saw in the Factory Method section, provider-based features usually deal with multiple providers. The approach used by various features is to have a feature-specific provider collection that in turn derives from System.Configuration.Provider.ProviderCollection. The ProvidersHelper class can then work with the common ProviderCollection class, while individual features can expose strongly typed collection classes. From a configuration standpoint, all the <add /> provider elements in your web.config eventually end up as concrete providers that can be referenced from a

ProviderCollection-derived class.

For example, in the Membership feature the Membership.Providers property returns a reference to a MembershipProviderCollection containing a reference to every provider defined within the <membership /> configuration section. The advantage to working with MembershipProviderCollection as opposed to ProviderCollection is that you know any provider returned from the collection indexer derives from MembershipProvider. The collection also validates that any providers added to it derives from MembershipProvider.

The definition for ProviderCollection is simple, and it exposes the common collection based functionality you would expect:

344

The Provider Model

public class ProviderCollection : IEnumerable, ICollection

{

public ProviderCollection();

public virtual void Add(ProviderBase provider); public void Remove(string name);

public ProviderBase this[string name] { get };

public IEnumerator GetEnumerator();

public void SetReadOnly(); public void Clear();

public int

Count

{ get

};

public bool

IsSynchronized

{ get

};

public

object

SyncRoot

{ get

};

public

void

CopyTo(ProviderBase[]

array, int index);

void ICollection.CopyTo(Array array, int index);

}

I won’t cover every method and property, because you are probably already familiar with quite a number of collection classes. The two pieces of important functionality that ProviderCollection delivers are validation for read-only collections and a common type for ProvidersHelper to use when it creates multiple providers inside of the ProvidersHelper.InstantiateProviders method.

Usually after a feature has completed initialization, the feature will call SetReadOnly on its ProviderCollection. This ensures that the set of providers available through the feature exactly mirrors the set of providers defined in configuration. After a call to SetReadOnly the ProvidersCollection class enforces the read-only nature of the collection. Attempts to call Add or Remove will fail with an exception.

The usual implementation model is for a feature-specific provider collection to derive from ProviderCollection and at least override the Add method. For ease of use, features also commonly implement a feature-specific indexer that supplements the default indexer on ProviderCollection as well as a feature-specific implementation of CopyTo. In other words, any portion of the ProviderCollection type signature that deals with a parameter of type ProviderBase is either overridden or supplemented by fea- ture-specific provider collections.

You can actually see that ProviderBase itself follows a similar approach because its implementation of ICollection.CopyTo requires an explicit interface cast. If instead you call CopyTo directly on ProviderBase, you will be using the method that accepts an array of ProviderBase instances, as opposed to just an array of object. The general idea is to specialize the portion of the collection that deals with common types by adding methods or overriding methods so that you can deal with a more specific type.

A feature-specific provider collection performs type-specific validation in an override of the Add method (that is, are you adding the correct provider type to the collection?). A feature-specific provider also performs the necessary type casts inside of its additional CopyTo and default indexer implementations. For example, if you work with a MembershipProviderCollection and if you use the default indexer,

345

Chapter 9

you know that the return value from its default indexer is already a MembershipProvider. If, instead, you worked with a MembershipProviderCollection instance as a ProviderCollection reference, you would have to perform a cast on the return value from the default indexer on

ProviderCollection.

You may be wondering why the provider-based features didn’t simply use the new generics functionality in the 2.0 Framework. Certainly, from an elegance standpoint, you wouldn’t have to muck around with collection hierarchies and the minutia of which methods to override or reimplement if ProviderCollection was instead defined as a generic type. The simple answer is that the provider model was developed very early on in the lifecycle of the 2.0 Framework. A substantial number of provider-based features were pretty well-fleshed out by the time that Framework generics had stabilized. (Remember that building one piece of the framework that is in turn dependent on another core piece of the framework gets pretty “interesting” at times!).

Once generics had stabilized though, there hadn’t been a decision yet on whether generics would be considered CLS-compliant — that is, would a public API that exposed generics be reusable across many different compilers that targeted the .NET Framework? Eventually, the decision was made in late 2004 to define generics as being CLS-compliant. By that point though, the development teams were pretty much in ship mode for Beta 2, which was way too late for folks to rummage through all of the provider-based features and swap out old-style 1.1 collections for 2.0 generics (sometimes making what would appear to be a common-sense design change in a large product like the .NET Framework turns out to be akin to standing a 747 on its wing and pulling a 9G turn; it would be nice if it worked, but it’s more likely that various pieces will come flying off). Hopefully, in a future release the use of generics for provider collections will come to pass!

System.Web.Configuration Classes

Because most of the concrete provider implementations in the 2.0 Framework exist within ASP.NET 2.0, the helper class for creating providers ended up in the System.Web.Configuration namespace. If you implement a provider-based feature or if you plan to use an existing provider-based feature outside of ASP.NET 2.0, you can still reference this namespace though and make use of the helper class.

The System.Web.Configuration.ProvidersHelper class provides two convenient helper methods for instantiating providers. The class is typically used by features during feature initialization as mentioned earlier, although you can certainly instantiate providers manually using the helper class, there are usually other feature-specific dependencies that end up breaking when you use such an approach.

I won’t cover the helper class again here, because the previous section on the Factory Method went into detail on how to use the class as well how it acts as a provider factory for the Framework. The class signature is:

public static class ProvidersHelper {

public static ProviderBase InstantiateProvider(

ProviderSettings providerSettings, Type providerType)

public static void InstantiateProviders( ProviderSettingsCollection configProviders, ProviderCollection providers, Type providerType)

}

346

The Provider Model

System.Configuration Classes

One of the important points for provider-based features is that you can swap out providers through configuration. The configuration-driven nature of provider-based features means that you can write code that uses a feature without hard-coding any compile-time dependencies on a specific provider implementation.

To support this functionality two configuration classes represent provider configuration data.

ProviderSettings

The System.Configuration.ProviderSettings class is the programmatic representation of a provider <add /> element in configuration. The ProviderSettings class exposes properties for some of the common configuration attributes found in a provider <add /> element, while still retaining the flexibility for feature providers to define their own custom set of configuration (and this runtime) attributes.

The class signature for ProviderSettings (less configuration class–specific internals) is shown here:

public sealed class ProviderSettings : ConfigurationElement

{

public ProviderSettings();

public ProviderSettings(String name, String type);

//ConfigurationElement specific methods snipped out for brevity

[ConfigurationProperty(“name”, RequiredValue = true, IsCollectionKey=true)] public String Name { get; set; }

[ConfigurationProperty(“type”, RequiredValue = true)] public String Type {get; set;}

public NameValueCollection Parameters { get; }

}

As you can see from the type signature, the only configuration attributes that are common across all providers are the “name” and “type” configuration attributes, which map respectively to the Name and Type properties. All other provider properties that you see when looking in machine.config or web

.config are considered to be feature-specific provider attributes. The declarative ConfigurationProperty attributes on the Name and Type properties are interpreted by the configuration system at runtime. These attributes are what “tell” the configuration system how to translate an Xml attribute to a property on the ProviderSettings class.

Feature-specific provider attributes are parsed by the configuration system and added as name-value pairs to the NameValueCollection available from the Parameters property. As a result the process by which configuration settings in web.config eventually end up in a provider is:

347

Chapter 9

1.At runtime a feature class, such as the static Membership class, makes a call into the configuration system asking for its configuration section to be parsed and loaded.

2.After the configuration file has been parsed, the values are returned back to the feature class as one or more configuration objects. In the case of the provider <add /> elements, each configured provider results in an instance of ProviderSettings. All attributes other than “name” and “type” end up in the ProviderSettings.Parameters property.

3.The feature class calls ProvidersHelper.InstantiateProviders and passes the

ProviderSettings to the helper class (to be precise an instance of ProviderSettings Collection containing one or more ProviderSettings is passed to the helper class).

4.The ProvidersHelper class uses ProviderSettings.Type to determine the correct type that needs to be instantiated.

5.Once the provider has been instantiated, the ProviderBase.Initialize method is called. The name parameter for this method comes from ProviderSettings.Name, whereas the config parameter comes from ProviderSettings.Parameters.

6.The provider internally calls base.Initialize to set the Name of the provider and optionally the Description. Feature-specific providers then use the remainder of the name-value pairs from ProviderSettings.Parameters for feature-specific initialization logic.

If you look in the Framework, you won’t find any feature specific configuration classes that derive from ProviderSetting; in fact, ProviderSettings is sealed, so in the 2.0 Framework you cannot write fea- ture-specific ProviderSettings classes even if you wanted to.

As a result, when you are working with configuration files at design time, the IntelliSense in the design environment is only able to validate the “name” and “type” attributes. If you are configuring a MembershipProvider, for example, you won’t get any IntelliSense for the SQL or the Active Directory/Active Directory Application Mode (AD/ADAM) provider properties. Instead, you are left to the documentation to determine which additional key-value pairs are allowed in the provider <add /> element within the <membership /> configuration element.

For the 2.0 Framework, this behavior was chosen to avoid having to engineer feature-specific settings classes along with an accompanying XSD schema for IntelliSense validation. The design problem with having feature-specific ProviderSettings classes is that for many features you cannot completely define the feature-specific attributes with a single configuration class. For example, within Membership the allowable attributes on the SQL provider only partially overlap with the allowable attributes on the AD/ADAM provider. Both the SQL and the AD/ADAM providers have implementation-specific attributes in addition to common Membership attributes.

This problem is common to all providers because the whole point of providers is to allow you to write your own custom implementations, which usually results in custom provider attributes. If each feature had a more strongly typed definition of ProviderSettings, you would still need a property like the ProviderSettings.Parameters property to allow for extensibility.

There is also an issue with XSD-based IntelliSense validation. It becomes problematic because <add /> was chosen as the common way for configuring a provider. However, because <add /> elements vary by their attributes, you can’t define an XSD validation rule that says “allow <add /> with the attribute set A or allow <add /> with the attribute set B, but don’t allow an <add /> element with a mixture of attribute sets A and B.” Furthermore, the existing <add /> element has a common XSD definition that is

348

The Provider Model

used in every feature-specific configuration section. The same <add /> element is used within <membership />, <profile />, <sitemap />, and so on To really support strongly-typed provider configuration sections and classes, you would need:

A different configuration approach that was element-driven as opposed attribute driven. Something like a <membershipProvider /> configuration element, a <roleManagerProvider /> configuration element, and so on. This would allow for feature-specific XSD schemas.

Feature-specific configuration classes that derive from ProviderSettings. This work would at least be pretty easy to accomplish.

Some type of extensibility mechanism that would allow you to tell the Framework about new provider types and to supply provider-specific XSD extensions. This would enable IntelliSense to validate both the core set of feature-specific configuration information as well as your custom provider configuration information. Again though, this extensibility mechanism would probably need to be element-based as opposed to attribute-based.

The nice thing about the current design though is that when you author a custom provider, you don’t have to author a custom configuration section and a related custom configuration class. The existing ProviderSettings class and the <add /> configuration element are flexible enough that you don’t need to write any special configuration code to plug in your own custom providers.

ProviderSettingsCollection

Because most provider-based features support configuring multiple providers, the System

.Configuration.ProviderSettingsCollection class is used to hold all of the ProviderSettings that resulted from parsing a configuration file.

The class definition, less configuration class–specific methods, is shown here:

[ConfigurationCollection(typeof(ProviderSettings))]

public sealed class ProviderSettingsCollection : ConfigurationElementCollection

{

public ProviderSettingsCollection();

public ProviderSettingsCollection Providers { get; }

public void Add(ProviderSettings provider); public void Remove(String name);

public void Clear();

public ProviderSettings this[object key] { get; } public ProviderSettings this[int index] { get; set; }

//Other configuration class specific methods removed for brevity

}

The second code sample in the earlier section on the Factory Method showed how you could manually construct a ProviderSettingsCollection, populate it with multiple ProviderSettings instances, and then pass the collection to ProvidersHelper.InstantiateProviders. From an application development perspective though, you probably won’t ever deal with a ProviderSettingsCollection. Instead, you may use a ProviderSettingsCollection class for administrative purposes to programmatically read and modify a configuration file.

349

Chapter 9

If you do author a provider-based feature, and you create a configuration section class for that feature, the configuration system will automatically convert the provider <add /> elements into an instance of ProviderSettingsCollection on your configuration section class. You don’t need to manually call Add, Remove, and similar methods from inside your custom configuration class. Instead, you would simply add a property on your configuration class of type ProviderSettingsCollection and attribute it appropriately.

Using the MembershipSection class as an example, it has a public property for its <provider /> section as shown here:

[ConfigurationProperty(“providers”)]

public ProviderSettingsCollection Providers { get; }

So, when the configuration system is parsing a configuration file, and it is processing a <providers /> element like:

<providers>

<add name=”foo” type=”bar” ... /> </providers>

The configuration system knows that the results of parsing everything underneath <providers /> results in a collection of information represented by ProviderSettingsCollection. Because a ProviderSettingsCollection is as an Add-Remove-Clear ( — )collection, the configuration system also knows to expect the Xml elements <add />, <remove /> and <clear /> underneath the <providers /> configuration element.

As the configuration system encounters each of these elements, it converts them into a method call to the

Add, Remove and Clear methods on the ProviderSettingsCollection class. Because ProviderSettingsCollection is attributed with the ConfigurationCollection attribute, and this attribute indicates that the collection contains instances of ProviderSettings, the configuration system will look at the declarative attributes on the ProviderSettings class when it processes the contents of the <providers /> section.

Because ProviderSettings has two properties adorned with the ConfigurationProperty attribute, the configuration system knows that when it parses a “name” or “type” attribute it needs to assign these to the Name and Type properties respectively on the ProviderSettings instance. Because the

ConfigurationProperty attribute on ProviderSettings.Name also includes IsCollectionKey = true, the configuration system will treat the “name” attribute as the key value when calling various methods on ProviderSettingsCollection. For example, a <remove name=”foo” /> configuration element is interpreted as a call to ProviderSettingsCollection.Remove with the value foo”being used as a parameter to the method.

As mentioned earlier, from your perspective all of this complexity is transparent to you. As long as you have a property of type ProviderSettingsCollection with the requisite ConfigurationProperty attribute, the configuration system will automatically parse your provider definitions for you.

350

The Provider Model

Building a Provider-Based Feature

Now that you have seen the rationale and architecture behind provider-based features, walking through the basic steps of writing a simple provider-based feature along with a custom provider will help you tie together the previous concepts to the provider support classes in the Framework. In this section, you will walk through the steps of building a provider-based feature, as shown in Figure 9-1

Figure 9-1

Because the intent of this section is to concentrate on creating a provider-based feature, the feature used for the sample will define and implement only one method that simply requests a string from its default provider. The sample provider base class definition is:

using System;

using System.Configuration.Provider;

namespace SampleFeature

{

public abstract class SampleFeatureProvider : ProviderBase

{

//Properties

public abstract string Color { get; } public abstract string Food { get; }

//Methods

public abstract string GetMeAString(string andPutThisOnTheEndOfIt);

}

}

A provider implementation for the sample feature is required to implement the GetMeAString method as well as the two abstract properties. The general convention for handling feature-specific configuration settings in a provider-based feature is to define abstract property getters on the provider base class. With this abstract class definition, the configuration settings for a “color” attribute and a “food” attribute will be available through their corresponding properties on the feature’s providers. This approach allows developers to access configuration settings at runtime without having to use any of the configuration classes.

Because the sample feature will allow you to configure multiple instances of a provider, a corresponding provider collection class is also defined.

351