Professional ASP.NET 2.0 Security, Membership, and Role Management phần 7 ppsx

64 398 0
Professional ASP.NET 2.0 Security, Membership, and Role Management phần 7 ppsx

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

By this point, the Initialize method is able to complete without error, or it catches whatever exception occurred. For either case the feature marks itself as being initialized. In the error case, it also stores a refer- ence to the exception that caused initialization to fail. This is another point where the ASP.NET provider- based features are a little different than the sample feature. The ASP.NET provider-based features need to store the exception and rethrow it whenever their private initialization methods are called from their pub- lic properties and methods. However, the sample feature class shown previously instead relies on the Framework to do the heavy lifting. Because Initialize was called from the static constructor, the Framework will remember that the static constructor failed. This means if the Initialize method fails, subsequent attempts to call public properties or methods on the static feature class result in a System.TypeInitializationException being thrown. The InnerException property on this exception instance will represent the true excep- tion that was thrown from inside of the Initialize method. From a programming standpoint either the ASP.NET approach or the approach shown previously that relies on a static constructor is valid. The decision is up to you. Using the static constructor eliminates the need for funky lock logic, but you do need to drill into the TypeInitializationException to find the root cause of a failure. The ASP.NET approach means that you will always have the problematic exception being thrown from public APIs and properties. But you will need to use locking inside of your feature’s initialization logic and have each public property and method on your feature class call back to your initialization method to cause the initialization exception to be rethrown. At this point, let’s take a look at the feature’s configuration section class. You want a configuration class that provides strongly typed access for a configuration that looks like: <sampleFeature defaultProvider=”DefaultSampleFeatureProvider”> <providers> <add name=”DefaultSampleFeatureProvider” type=”SampleFeature.SampleFeatureProviderImplementation, SampleFeature” connectionStringName=”SomeConnectionString” color=”red” food=”burgers” description=”this came from config” /> </providers> </sampleFeature> The feature itself has its own configuration section as indicated by the <sampleFeature /> configuration element. The one allowable attribute on this element is the “ defaultProvider” attribute. Nested within a <sampleFeature /> is a <providers /> section allowing for one or more provider definitions. Aside from the “name” and “type” attributes, all of the other attributes are feature-specific. The configuration section class that models this configuration section is shown here: using System; using System.Configuration; namespace SampleFeature { public class SampleFeatureConfigurationSection : ConfigurationSection { 357 The Provider Model 12_596985 ch09.qxp 12/14/05 7:50 PM Page 357 public SampleFeatureConfigurationSection(){} [ConfigurationProperty(“providers”)] public ProviderSettingsCollection Providers { get { return (ProviderSettingsCollection)base[“providers”]; } } [ConfigurationProperty(“defaultProvider”, DefaultValue = “DefaultSampleFeatureProvider”)] [StringValidator(MinLength = 1)] public string DefaultProvider { get { return (string)base[“defaultProvider”]; } set { base[“defaultProvider”] = value; } } } } Inheriting from ConfigurationSection means that this class represents a configuration section in an application configuration file. The default constructor is used by the configuration system when it new()’s up configuration section classes while parsing configuration. The only custom code that you need to write in the configuration class are the custom properties that represent configuration attributes and nested configuration sections. The Providers property represents the nested <providers /> configuration section. The declarative attribute on the property causes the configuration system to parse the <providers /> section and its nested elements into an instance of a ProviderSettingsCollection. By using the ProviderSettingsCollection class, you automatically leverage the built-in behavior of the <providers /> configuration section without the need to write any additional code. The DefaultProvider property has two declarative attributes on it. The ConfigurationProperty attribute indicates that if a “defaultProvider” attribute is found within the <sampleFeature /> element that its value will be available via the DefaultProvider property. The ConfigurationProperty also has a default value indicating that the property should be set to “ DefaultSampleFeatureProvider” if the attribute is not found in the configuration file. Last, the StringValidator attribute tells the configuration system that if the attribute exists in configuration, the attribute must be a non-empty string. This type of declarative validation rule is automatically enforced when the configuration system attempts to parse the configuration. In the SampleFeatureMainEntryPoint.Initialize method, the following code is what triggers the parsing and loading of the configuration section: 358 Chapter 9 12_596985 ch09.qxp 12/14/05 7:50 PM Page 358 SampleFeatureConfigurationSection sc = (SampleFeatureConfigurationSection)ConfigurationManager.GetSection( “sampleFeature”); The configuration runtime knows to associate the <sampleFeature /> configuration section with the SampleFeatureConfigurationSection class once you add the following section definition to your application’s configuration file: <configSections> <section name=”sampleFeature” type=”SampleFeature.SampleFeatureConfigurationSection, SampleFeature” allowDefinition=”MachineToApplication” /> </configSections> A <section /> element is used to associate an XML element called sampleFeature to the custom configuration class you just saw. The type attribute tells the configuration system where to find the class; in this case the class is located in an unsigned assembly called SampleFeature.dll. Depend- ing on whether you are defining the custom section for a web application, you can also use the “ allowDefinition” attribute to control the inheritance behavior of the configuration section. Because provider-based features usually don’t allow redefinition in the level of individual subdirectories, the “ allowDefinition” attribute is set to limit the “sampleFeature” element to only machine.config, the root web.config or an application’s web.config file. At this point, the only piece left to implement for the sample feature is a concrete provider. The basic implementation of a concrete provider (less the initialization step) is: using System; using System.Configuration; using System.Configuration.Provider; namespace SampleFeature { public class SampleFeatureProviderImplementation : SampleFeatureProvider { private string color; private string food; private String connectionString; public override string Color { get { return color; } } public override string Food { get { return food; } } public override string GetMeAString(string andPutThisOnTheEndOfIt) { return “This string came from the “ + “ SampleFeatureProviderImplementation.\r\n” + “The provider description is: “ + Description + “\r\n” + “The provider color is: “ + Color + “\r\n” + 359 The Provider Model 12_596985 ch09.qxp 12/14/05 7:50 PM Page 359 “The provider food is: “ + Food + “\r\n” + andPutThisOnTheEndOfIt; } //Initialize method snipped out for now } } The concrete provider implementation inherits from SampleFeatureProvider and overrides the two abstract properties as well as the single abstract method defined on the provider base class. The value of the public properties is established when the provider is initialized, while the public method simply plays back the property values as well as some extra strings. Assuming that you configure an instance of SampleFeatureProviderImplementation as the default provider in configuration, a call to SampleFeatureMainEntryPoint.GetMeAString is simply forwarded to the method implementation shown previously. Remember that the forwarding code in the static feature class references the static Provider property, which contains a reference to the default provider defined in configuration: public static string GetMeAString(string someString) { return Provider.GetMeAString(someString); } This is the same approach used by most of the ASP.NET 2.0 provider-based features and explains why you can use static classes like Membership and these classes just work because their static methods internally forward their calls to the default feature provider. Of course, the concrete provider really can’t accomplish anything unless it is initialized first: public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config) { if ( (config == null) || (config.Count == 0) ) throw new ArgumentNullException( “You must supply a non-null, non-empty value for config.”); if (string.IsNullOrEmpty(config[“description”])) { config.Remove(“description”); config.Add(“description”, “This would be where you put a localized description for the provider.”); } //Let ProviderBase perform the basic initialization base.Initialize(name, config); //Perform feature-specific provider initialization here //Color if (string.IsNullOrEmpty(config[“color”])) { color = “The default color for the provider”; } else { color = config[“color”]; 360 Chapter 9 12_596985 ch09.qxp 12/14/05 7:50 PM Page 360 } config.Remove(“color”); //Food if (string.IsNullOrEmpty(config[“food”])) { food = “The default food for the provider”; } else { food = config[“food”]; } config.Remove(“food”); //Get the connection string string connectionStringName = config[“connectionStringName”]; if (String.IsNullOrEmpty(connectionStringName)) throw new ProviderException( “You must specify a connectionStringName attribute for the provider”); ConnectionStringsSection cs = (ConnectionStringsSection)ConfigurationManager.GetSection( “connectionStrings”); if (cs == null) throw new ProviderException( “The <connectionStrings/> configuration section was not defined.”); if (cs.ConnectionStrings[connectionStringName] == null) throw new ProviderException( “The connectionStringName could not be found “ + “in the <connectionStrings /> configuration section.”); else connectionString = cs.ConnectionStrings[connectionStringName].ConnectionString; if (String.IsNullOrEmpty(connectionString)) throw new ProviderException( “The specified connection string has an invalid value.”); config.Remove(“connectionStringName”); //Check to see if unexpected attributes were set in configuration if (config.Count > 0) { string extraAttribute = config.GetKey(0); if (!String.IsNullOrEmpty(extraAttribute)) throw new ProviderException(“The following unrecognized attribute was “ + “found in the “ + Name + “‘s configuration: ‘“ + extraAttribute + “‘“); else throw new ProviderException(“An unrecognized attribute was “ + “found in the provider’s configuration.”); } } 361 The Provider Model 12_596985 ch09.qxp 12/14/05 7:50 PM Page 361 The name parameter contains the “name” attribute from the provider’s <add /> configuration element, while the config parameter contains all of the other attributes that the configuration runtime found on the <add /> provider element. The provider first makes a sanity check to ensure that it was passed a valid collection of configuration attributes. When a provider is initialized via a static feature provider that in turn uses a configuration class, this sanity check is redundant. However, as mentioned earlier, there isn’t anything that prevents a developer from attempting to new() up a provider and manually initialize it — hence the sanity check. If a “description” attribute was not supplied in the provider’s <add /> element, or if it was the empty string, then the provider supplies a default description instead. Although the sample doesn’t show it here, this is the point at which the ASP.NET 2.0 providers will fallback and return a localized description for a provider if you did not supply a “description” in configuration. With the “name” and “description” attributes squared away, the provider calls the Initialize implementation on ProviderBase. ProviderBase will automatically hook up these two attributes to the Name and Description proper- ties defined on ProviderBase. After the base class performs its initialization tasks, the next pieces of code transfer the “color” and “food” attributes from configuration and hook them up to the provider’s Color and Food properties. Notice that the provider treats these attributes as optional and automatically supplies default values if they were not specified in configuration. Because the configuration class for providers treats all attributes other than “name” and “type” as optional, you need to implement code in your custom providers to either enforce additional required attributes or supply reasonable defaults, as shown in the sample provider. Also notice how after each configuration attribute is used, the attribute is removed from the configuration collection with a call to the Remove method. The next block of logic deals with handling a connection string attribute. The sample feature obviously doesn’t use any type of connection string, but I included the code for handling connection strings because it is pretty likely that many of you writing custom providers will need to deal with connection strings at some point. The sample provider requires a “ connectionStringName” attribute on each provider <add /> element. If it doesn’t find the attribute in the attribute collection passed to Initialize, the provider throws an exception. Assuming that the attribute was defined, the provider goes through the following series of steps to get the actual connection string: 1. The provider gets a reference to the strongly typed configuration class for the <connection Strings /> configuration section. Remember that this is a new section in the 2.0 Framework and is intended to be the place for storing database connection strings (as opposed to <appSettings />). 2. The provider looks for the connection string defined by “connectionStringName” in the <connectionStrings /> section. If there is no such connection string with that name, the provider throws an exception. 3. The provider gets the value of the specified connection string and performs a basic verification to ensure it was not set to the empty string. If the connection string’s value was set to the empty string, the provider throws an exception. 4. The provider stores the connection string internally and then removes the “ connectionStringName” attribute from the configuration attribute collection. 362 Chapter 9 12_596985 ch09.qxp 12/14/05 7:50 PM Page 362 By this point, the provider and ProviderBase have processed all of the configuration attributes that are known to the two classes. As a final verification, the provider checks to see if there are any remaining attributes in the configuration attribute collection. If there are remaining attributes, the provider throws an exception because it doesn’t know what to do with them. This is an important design point because all of the ASP.NET 2.0 providers perform similar processing with their configuration attributes. For example, if you were to supply additional attributes when configuring a SqlMembershipProvider, the provider would fail with a similar exception. One subtle point with the way the Initialize method is coded is that it is possible for the provider to fail initialization and end up in a sort of “zombie” state; the provider exists in memory, but it hasn’t completed enough of its initialization to be of any use. Theoretically, if you could get a reference to a zombie provider, you could call properties and methods on it, and depending on when the provider ini- tialization failed, you would get different results. It turns out that the ASP.NET 2.0 providers also have the same small loophole. The ASP.NET providers don’t have extra protections that throw exceptions from public properties or methods because these protections already exist in the static feature classes. Assuming that you aren’t trying to create and initialize providers manually, the static feature classes will fail initialization when one of the configured providers throws an exception from an Initialize call. This, in turn, means that if you attempt to get a reference to a configured provider via a call to either the Provider or Providers properties on the static feature class, you will also get an exception. This behavior holds true for the sample feature as well. If a provider fails initialization, attempting to call SampleFeatureMainEntryPoint.Provider (or Providers) will return a TypeInitialization Exception , and you won’t actually be able to get a reference to a “zombie” provider. Of course, you could still attempt to manually create and initialize a provider, but this approach is outside the intended usage boundaries of provider-based features. You can certainly implement additional protections in your providers to cover this case, but because a developer cannot “accidentally” misuse a provider when going through a static feature class, this design loophole was not addressed in the 2.0 Framework. Now that you have the end-to-end sample feature coded up (finally!), let’s actually try it out in a few scenarios. You can compile all of the previous code into a standalone assembly. Then reference the assembly from a console application that has the following configuration: <configuration> <configSections> <section name=”sampleFeature” type=”SampleFeature.SampleFeatureConfigurationSection, SampleFeature” allowDefinition=”MachineToApplication” /> </configSections> <sampleFeature > <providers> <add name=”DefaultSampleFeatureProvider” type=”SampleFeature.SampleFeatureProviderImplementation, SampleFeature” connectionStringName=”SomeConnectionString” color=”red” food=”burgers” description=”this came from config” /> <add name=”SecondSampleFeatureProvider” type=”SampleFeature.SampleFeatureProviderImplementation, SampleFeature” 363 The Provider Model 12_596985 ch09.qxp 12/14/05 7:50 PM Page 363 connectionStringName=”SomeConnectionString” color=”green” food=”milk-shake” /> <add name=”ThirdSampleFeatureProvider” type=”SampleFeature.SampleFeatureProviderImplementation, SampleFeature” connectionStringName=”SomeConnectionString” /> </providers> </sampleFeature> <connectionStrings> <add name=”SomeConnectionString” connectionString=”the connection string value” /> </connectionStrings> </configuration> The test application’s configuration includes the <section /> that tells the configuration system how to parse the <sampleFeature /> configuration element. There are three providers defined for the sample feature. Notice how the “ defaultProvider” is not defined on the <sampleFeature /> element while there is a provider <add /> element using the default value for this attribute of “ DefaultSampleFeatureProvider.” The second and third provider definitions do not include a “description,” whereas the third provider definition defines the bare minimum number of required attributes (that is, “ name,” “type,” and “connectionStringName”). Last, there is a <connection Strings /> section that all of the provider definitions reference. You can use the feature with the following sample test console application: using System; using SampleFeature; namespace SampleFeatureConsoleTest { class Program { static void Main(string[] args) { try { Console.WriteLine( SampleFeatureMainEntryPoint.GetMeAString(“console app”)); } catch(Exception ex) { } SampleFeatureProvider sp = SampleFeatureMainEntryPoint.Providers[“SecondSampleFeatureProvider”]; string anotherString = sp.GetMeAString(“Using the second provider.”); Console.WriteLine(anotherString); SampleFeatureProvider sp2 = SampleFeatureMainEntryPoint.Providers[“ThirdSampleFeatureProvider”]; 364 Chapter 9 12_596985 ch09.qxp 12/14/05 7:50 PM Page 364 string anotherString2 = sp2.GetMeAString( “This provider had no config attributes defined.”); Console.WriteLine(anotherString2); } } } The sample application works just as you would expect any other provider-based feature to work. With just the provider definition in configuration, it calls the static feature class to output a string. Internally, this results in a call to the default provider. The other two code blocks demonstrate accessing the two nondefault providers and then calling methods directly on them. The sample output is: This string came from the SampleFeatureProviderImplementation. The provider description is: this came from config The provider color is: red The provider food is: burgers console app This string came from the SampleFeatureProviderImplementation. The provider description is: This would be where you put a localized description for the provider. The provider color is: green The provider food is: milk-shake Using the second provider. This string came from the SampleFeatureProviderImplementation. The provider description is: This would be where you put a localized description for the provider. The provider color is: The default color for the provider The provider food is: The default food for the provider This provider had no config attributes defined. You can see how the description varies between the providers, with the second and third providers relying on the default description defined inside of the provider’s Initialize method. The output from the third provider also demonstrates how the provider can fallback to reasonable defaults when option feature-specific attributes are not defined in the provider’s configuration. If you run the sample console application along with the sample provider code in a debugger, you can play around with intentionally creating bad configurations. Then you can see how the exception behavior inside of the static feature class’s Initialize method causes the second and third attempts to call into the feature to fail (this is why the test app eats all exceptions from the first attempt to use the feature). Just for grins, you can take the sample feature and drop it into the “ /bin” directory of a web application. Take the configuration section shown for the sample console application and drop it into the web .config for a sample web application. Then create a test page with roughly the same code as shown above for the console application and have it write out the results to a web page. You will get the exact same feature behavior as was demonstrated for the console application. 365 The Provider Model 12_596985 ch09.qxp 12/14/05 7:50 PM Page 365 Summary The 2.0 Framework introduces a new design concept with provider-based features. Rather than creating features and services where the internal implementations are “black boxes,” the new provider-based fea- tures allow you to author custom implementations of business logic and data access logic. You can then swap these custom implementations into place with a few simple configuration settings. The core design pattern used by provider-based features is the Strategy pattern. The Strategy pattern is a design approach that allows you to plug in different implementations for the core logic of a feature. In the case of the 2.0 Framework and ASP.NET 2.0, the providers are the implementation of the Strategy design pattern. A number of support classes exist in System.Configuration, System.Configuration.Providers and System.Web.Configuration to make it easier to write provider-based features yourself. You can use the existing provider base class in conjunction with provider-specific configuration classes to build the basic underpinnings of a provider-based feature. Overall the sample provider-based feature that was shown had roughly 200 lines for code (and that includes the braces!). Approximately half of the code is boilerplate implementation of things like the provider collection and the configuration class. However, with around only 100 lines of actual initialization code (and again the basics of initialization are the same regardless of feature), you can create a custom provider-based feature that you can use across the spectrum of fat client and web-based applications. 366 Chapter 9 12_596985 ch09.qxp 12/14/05 7:50 PM Page 366 [...]... to the other public methods on the MembershipUser class 379 Chapter 10 Among the properties that are public, Email, Comment, and IsApproved are pretty easy to understand Email and Comment are just data fields, while IsApproved can be toggled between true and false — with a value of false causing ValidateUser to fail even if the correct username and password are supplied to the method LastActivityDate... makes use of this property, and it is safe to say that future releases of the Membership feature will also leave this property alone Although you can go overboard and implement entire universes of functionality with this property, it comes in handy when you need to store just a few extra pieces of information and need a convenient place to put them, perhaps those pesky first name and last name properties!...Membership One of the unique aspects of ASP.NET 2.0 is that it introduces a number of powerful new application services that are built using the provider model Membership is one of the new services and addresses the common need that websites have for creating and managing users and their credentials Although the Membership feature ships with a great deal of... values are usually created and compared on the web server, and then transmitted and stored on a database server In any web farm with more than one server, this means that no single master server is responsible for generating date-time values You could definitely end up with one web server logging a failed login attempt (and hence updating the date-time related failure data) and a different server loading... reversible encryption and you want to give your users the ability to retrieve their old passwords On one hand, if a custom provider requires a password answer prior to retrieving a password, and if the provider also keeps track of bad password answer attempts, it should increment tracking counters from inside of this method and lock out users as necessary Providers need to also handle the case where... numberOfNonAlphanumericCharacters) One mode of self-service password reset automatically generates a random password when a user has forgotten his or her password Because generating a string random password is a pain to get correct, it is actually a handy utility to have around The method generates a random string of characters based on the length parameter Furthermore, it will ensure that at least a... you could implement a custom feature that updates the user’s LastActivityDate each time user-specific data is retrieved The ASP.NET SQL providers actually do this for Profile and Web Parts Personalization However the ASP.NET SQL providers all use a common schema, so the Profile and Personalization providers perform the update from inside of the database The LastActivityDate property allows for similar... Framework traditionally returned machine local times from all public APIs To handle this behavior while still handling UTC times internally, the Membership feature assumes that all date-time parameters passed in to public properties and methods to be in local time Furthermore, whenever date-time data is returned from public properties and methods, data is always converted back to machine local time Internally... feature was through the similarly named Membership class This class is defined as a public static class, and the style of programming you use with it is meant to parallel other common ASP.NET classes such as Request, Response, and so on Rather than having to muddle around trying to figure out how to get up and running with the feature, the idea is that after developers know of the Membership class, they... just choose not to implement the ability to create and update users The properties related to user creation and user updates mostly deal with the user’s password ❑ MinRequiredPasswordLength — On one hand, if a provider supports enforcing password strengths, it should return the minimum length of passwords allowed when using the provider On the other hand, if a provider does not enforce any kind of password . Last, the method ensures that each randomly generated character won’t trigger a false positive from 3 70 Chapter 10 13_596985 ch 10. qxp 12/ 14 /05 7: 50 PM Page 3 70 ASP. NET s request validation functionality the spectrum of fat client and web-based applications. 366 Chapter 9 12_ 596985 ch09.qxp 12/ 14 /05 7: 50 PM Page 366 Membership One of the unique aspects of ASP. NET 2. 0 is that it introduces a number. providers should internally store the value in UTC time. 3 72 Chapter 10 13_596985 ch 10. qxp 12/ 14 /05 7: 50 PM Page 3 72 ❑ LastLoginDate — The last date and time a successful call to ValidateUser occurred.

Ngày đăng: 12/08/2014, 23:21

Từ khóa liên quan

Tài liệu cùng người dùng

Tài liệu liên quan