Profile Providers
Introduction to the Provider Model
Membership Providers
Role Providers
Site Map Providers
Session State Providers
**Profile Providers
**Web Event Providers
Web Parts Personalization Providers
Custom Provider-Based Services
Hands-on Custom Providers: The Contoso Times
Profile providers provide the interface between ASP.NET's profile service and profile data sources. The two most common reasons for writing a custom profile provider are:
- You wish to store profile data in a data source that is not supported by the profile providers included with the .NET Framework, such as an Oracle database.
- You wish to store profile data in a SQL Server database whose schema differs from that of the database used by System.Web.Profile.SqlProfileProvider.
The fundamental job of a profile provider is to write profile property values supplied by ASP.NET to persistent profile data sources, and to read the property values back from the data source when requested by ASP.NET. Profile providers also implement methods that allows consumers to manage profile data sources-for example, to delete profiles that haven't been accessed since a specified date.
The ProfileProvider Class
Developers writing custom profile providers begin by deriving from System.Web.Profile.ProfileProvider.ProfileProvider derives from System.Configuration.SettingsProvider, which in turn derives from ProviderBase. Together, SettingsProvider and ProfileProvider define the abstract class methods and properties that a derived class must implement in order to serve as an intermediary between the profile service and profile data sources. ProfileProvider is prototyped as follows:
public abstract class ProfileProvider : SettingsProvider { public abstract int DeleteProfiles (ProfileInfoCollection profiles); public abstract int DeleteProfiles (string[] usernames); public abstract int DeleteInactiveProfiles (ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate); public abstract int GetNumberOfInactiveProfiles (ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate); public abstract ProfileInfoCollection GetAllProfiles (ProfileAuthenticationOption authenticationOption, int pageIndex, int pageSize, out int totalRecords); public abstract ProfileInfoCollection GetAllInactiveProfiles (ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords); public abstract ProfileInfoCollection FindProfilesByUserName (ProfileAuthenticationOption authenticationOption, string usernameToMatch, int pageIndex, int pageSize, out int totalRecords); public abstract ProfileInfoCollection FindInactiveProfilesByUserName (ProfileAuthenticationOption authenticationOption, string usernameToMatch, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords); }
A ProfileProvider-derived class must also implement the abstract methods and properties defined in System.Configuration.SettingsProvider, which is prototyped as follows:
public abstract class SettingsProvider : ProviderBase { // Properties public abstract string ApplicationName { get; set; } // Methods public abstract SettingsPropertyValueCollection GetPropertyValues (SettingsContext context, SettingsPropertyCollection properties); public abstract void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection properties); }
The following table describes ProfileProvider's methods and properties and provides helpful notes regarding their implementation:
Method or Property | Description |
---|---|
ApplicationName | The name of the application using the profile provider. ApplicationName is used to scope profile data so that applications can choose whether to share profile data with other applications. This property can be read and written. |
GetPropertyValues | Reads profile property values from the data source and returns them in a SettingsPropertyValueCollection. See GetPropertyValues for details. |
SetPropertyValues | Writes profile property values to the data source. The values are provided by ASP.NET in a SettingsPropertyValueCollection. See SetPropertyValues for details. |
DeleteProfiles (ProfileInfoCollection) | Deletes the specified profiles from the data source. |
DeleteProfiles (string[]) | Deletes the specified users' profiles from the data source. |
DeleteInactiveProfiles | Deletes all inactive profiles-profiles that haven't been accessed since the specified date-from the data source. |
GetNumberOfInactiveProfiles | Returns the number of profiles that haven't been accessed since the specified date. |
GetAllProfiles | Returns a collection of ProfileInfo objects containing administrative information about all profiles, including user names and last activity dates. |
GetAllInactiveProfiles | Returns a collection of ProfileInfo objects containing administrative information regarding profiles that haven't been accessed since the specified date. |
FindProfilesByUserName | Returns a collection of ProfileInfo objects containing administrative information regarding profiles whose user names match a specified pattern. |
FindInactiveProfilesByUserName | Returns a collection of ProfileInfo objects containing administrative information regarding profiles whose user names match a specified pattern and that haven't been accessed since the specified date. |
Scoping of Profile Data
Profile data is inherently scoped by user name so that profile data can be maintained independently for each user. When storing profile data, a provider must take care to key the data by user name so it can be retrieved using the same key later. For anonymous users, profile providers use anonymous user IDs rather than user names to key profile properties. The user names passed to profile provider methods are in fact anonymous user IDs for users who are not authenticated.
In addition, all profile providers inherit from SettingsProvider a property named ApplicationName whose purpose it to scope the data managed by the provider. Applications that specify the same ApplicationName when configuring the profile service share profile data; applications that specify unique ApplicationNames do not. In addition to associating profiles with user names or anonymous user IDs (profiles are, after all, a means for storing per-user data), profile-provider implementations must associate profiles with application names so operations performed on profile data sources can be scoped accordingly.
As an example, a provider that stores profile data in a SQL database might use a command similar to the following to retrieve profile data for the user named "Jeff" and the application named "Contoso:"
SELECT * FROM Profiles WHERE UserName='Jeff' AND ApplicationName='Contoso'
The AND in the WHERE clause ensures that other applications containing profiles keyed by the same user name don't conflict with the "Contoso" application.
GetPropertyValues
The two most important methods in a profile provider are the GetPropertyValues and SetPropertyValues methods inherited from SettingsProvider. These methods are called by ASP.NET to read property values from the data source and write them back. Other profile provider methods play a lesser role by performing administrative functions such as enumerating and deleting profiles.
When code that executes within a request reads a profile property, ASP.NET calls the default profile provider's GetPropertyValues method. The context parameter passed to GetPropertyValues is a dictionary of key/value pairs containing information about the context in which GetPropertyValues was called. It contains the following keys:
- UserName—User name or user ID of the profile to read
- IsAuthenticated—Indicates whether the requestor is authenticated
The properties parameter contains a collection of SettingsProperty objects representing the property values ASP.NET is requesting. Each object in the collection represents one of the properties defined in the <profile> configuration section. GetPropertyValues' job is to return a SettingsPropertyValuesCollection supplying values for the properties in the SettingsPropertyCollection. If the property values have been persisted before, then GetPropertyValues can retrieve the values from the data source. Otherwise, it can return a SettingsPropertyValuesCollection that instructs ASP.NET to assign default values.
As an example, suppose the <profile> configuration section is defined this way:
<profile> <properties> <add name="Greeting" type="String" /> <add name="Count" type="Int32" defaultValue="0" /> </properties> </profile>
Each time GetPropertyValues is called, the SettingsPropertyCollection passed to it contains two SettingsProperty objects: one representing the Greeting property, the other representing the Count property. The first time GetPropertyValues is called, the provider can simply do this since the property values haven't yet been persisted in the data source:
SettingsPropertyValueCollection settings = new SettingsPropertyValueCollection (); foreach (SettingsProperty property in properties) settings.Add (new SettingsPropertyValue (property)); return settings;
The returned SettingsPropertyValueCollection contains two SettingsPropertyValues: one representing the Greeting property's property value, and the other representing the Count property's property value. Moreover, because PropertyValue and SerializedValue are set to null in the SettingsPropertyValue objects and Deserialized is set to false, ASP.NET assigns each property a default value (which come from the properties' defaultValue attributes if present.)
The second time GetPropertyValues is called, it retrieves the property values from the data source (assuming the properties were persisted there in the call to SetPropertyValues that followed the previous call to GetPropertyValues). Once more, its job is to return a SettingsPropertyValueCollection containing property values. This time, however, GetPropertyValues has a choice of ways to communicate property values to ASP.NET:
- It can set the corresponding SettingsPropertyValue object's PropertyValue property equal to the actual property value and the object's Deserialized property to true. ASP.NET will retrieve the property value from PropertyValue. This is useful for primitive types that do not require serialization. It's also useful for explicitly assigning null values to reference types by setting PropertyValue to null and Deserialized to true.
- It can set the corresponding SettingsPropertyValue object's SerializedValue property equal to the serialized property value and the object's Deserialized property to false. ASP.NET will deserialize SerializedValue to obtain the actual property value, using the serialization type specified in the SettingsProperty object's SerializeAs property. This is useful for complex types that require serialization. The provider typically doesn't do the serialization itself; rather, it reads the serialized property value that was persisted in the data source by SetPropertyValues.
Inside the ASP.NET Team |
---|
Another reason for providing serialized data to ASP.NET via the SerializedValue property is that there is no guarantee the calling code that triggered the call to GetPropertyValues is actually interested in all the profile properties. Providing data through SerializedValue allows for lazy deserialization by SettingsBase. If you have ten properties being retrieved by the profile provider, and the calling code on a page only uses one of these properties, then nine of properties don't have to be deserialized, resulting in a potentially significant performance win. |
Thus, GetPropertyValues might perform its duties this way the second time around:
SettingsPropertyValueCollection settings = new SettingsPropertyValueCollection (); foreach (SettingsProperty property in properties) { // Create a SettingsPropertyValue SettingsPropertyValue pp = new SettingsPropertyValue (property); // Read a persisted property value from the data source object val = GetPropertyValueFromDataSource (property.Name); // If val is null, set the property value to null if (val == null) { pp.PropertyValue = null; pp.Deserialized = true; pp.IsDirty = false; } // If val is not null, set the property value to a non-null value else { // TODO: Set pp.PropertyValue to the property value and // pp.Deserialized to true, or set pp.SerializedValue to // the serialized property value and Deserialized to false. // Which strategy you choose depends on which was written // to the data source: PropertyValue or SerializedValue. } // Add the SettingsPropertyValue to the collection settings.Add (pp); } // Return the collection return settings;
SetPropertyValues
SetPropertyValues is the counterpart to GetPropertyValues. It's called by ASP.NET to persist property values in the profile data source. Like GetPropertyValues, it's passed a SettingsContext object containing a user name (or ID) and a Boolean indicating whether the user is authenticated. It's also passed a SettingsPropertyValueCollection containing the property values to be persisted. The format in which the data is persisted-and the physical storage medium that it's persisted in-is up to the provider. Obviously, the format in which SetPropertyValues persists profile data must be understood by the provider's GetProfileProperties method.
SetPropertyValues' job is to iterate through the supplied SettingsPropertyValue objects and write each property value to the data source where GetPropertyValues can retrieve it later on. Where SetPropertyValues obtains the property values from depends on the Deserialized properties of the corresponding SettingsPropertyValue objects:
- If Deserialized is true, SetPropertyValues can obtain the property value directly from the SettingsPropertyValue object's PropertyValue property.
- If Deserialized is false, SetPropertyValues can obtain the property value, in serialized form, from the SettingsPropertyValue object's SerializedValue property. There's no need for the provider to attempt to deserialize the serialized property value; it can treat the serialized property value as an opaque entity and write it to the data source. Later, GetPropertyValues can fetch the serialized property value from the data source and return it to ASP.NET in a SettingsPropertyValue object whose SerializedValue property holds the serialized property value and whose Deserialized property is false.
A profile provider's SetPropertyValues method might therefore be structured like this:
foreach (SettingsPropertyValue property in properties) { // Get information about the user who owns the profile string username = (string) context["UserName"]; bool authenticated = (bool) context["IsAuthenticated"]; // Ignore this property if the user is anonymous and // the property's AllowAnonymous property is false if (!authenticated && !(bool) property.Property.Attributes["AllowAnonymous"]) continue; // Otherwise persist the property value if (property.Deserialized) { // TODO: Write property.PropertyValue to the data source } else { // TODO: Write property.SerializedValue to the data source } }
Alternatively, SetPropertyValues could ignore PropertyValue and simply write SerializedValue to the data source, regardless of whether Deserialized is true or false. The GetPropertyValues implementation would read SerializedValue from the data source and return it in a SettingsPropertyValue object's SerializedValue property with Deserialized set to false. ASP.NET would then compute the actual property value. This is the approach taken by ASP.NET's SqlProfileProvider provider, which only stores serialized property values in the profile database.
The example above doesn't persist a property value if the user isn't authenticated and the property isn't attributed to allow anonymous users. It assumes that if the property appears in a SettingsPropertyCollection passed to GetPropertyValues, GetPropertyValues will see that the property value isn't in the data source and allow ASP.NET to assign a default value. Similarly, SetPropertyValues may choose not to write to the data source properties whose UsingDefaultValue property is true, because such values are easily recreated when GetPropertyValues is called. If a profile provider only persists property values that have changed since they were loaded, it could even ignore properties whose IsDirty property is false.
Inside the ASP.NET Team |
---|
ASP.NET's SqlProfileProvider writes property values to the database even if IsDirty is false. (The TextFileProfileProvider class presented in the next section does the same.) That's because each time SqlProfileProvider records profile property values in the database, it overwrites existing values. It does, however, refrain from saving values whose AllowAnonymous property is false if the user is unauthenticated, and properties with IsDirty equal to false and UsingDefaultValue equal to true. A custom profile provider that stores property values in individual fields in the data source-fields that can be individually updated without affecting other fields-could be more efficient in its SetPropertyValues method by checking the properties' IsDirty values and only updating the ones that are dirty. |
TextFileProfileProvider
Figure 6-1 contains the source code for a ProfileProvider-derivative named TextFileProfileProvider that demonstrates the minimum functionality required of a profile provider. It implements the two key ProfileProvider methods-GetPropertyValues and SetPropertyValues-but provides trivial implementations of the others. Despite its simplicity, TextFileProfileProvider is fully capable of reading and writing data generated from any profile defined in the <profile> configuration section.
TextFileProfileProvider stores profile data in text files named Username_Profile.txt in the application's ~/App_Data/Profile_Data directory. Each file contains the profile data for a specific user and consists of a set of three strings (described later in this section). You must create the ~/App_Data/Profile_Data directory before using the provider; the provider doesn't attempt to create the directory if it doesn't exist. In addition, the provider must have read/write access to the ~/App_Data/Profile_Data directory.
Listing 1. TextFileProfileProvider
using System; using System.Configuration; using System.Configuration.Provider; using System.Collections.Specialized; using System.Security.Permissions; using System.Web; using System.Web.Profile; using System.Web.Hosting; using System.Globalization; using System.IO; using System.Text; [SecurityPermission(SecurityAction.Assert, Flags=SecurityPermissionFlag.SerializationFormatter)] public class TextFileProfileProvider : ProfileProvider { public override string ApplicationName { get { throw new NotSupportedException(); } set { throw new NotSupportedException(); } } public override void Initialize(string name, NameValueCollection config) { // Verify that config isn't null if (config == null) throw new ArgumentNullException("config"); // Assign the provider a default name if it doesn't have one if (String.IsNullOrEmpty(name)) name = "TextFileProfileProvider"; // Add a default "description" attribute to config if the // attribute doesn't exist or is empty if (string.IsNullOrEmpty(config["description"])) { config.Remove("description"); config.Add("description", "Text file profile provider"); } // Call the base class's Initialize method base.Initialize(name, config); // Throw an exception if unrecognized attributes remain if (config.Count > 0) { string attr = config.GetKey(0); if (!String.IsNullOrEmpty(attr)) throw new ProviderException ("Unrecognized attribute: " + attr); } // Make sure we can read and write files // in the ~/App_Data/Profile_Data directory FileIOPermission permission = new FileIOPermission (FileIOPermissionAccess.AllAccess, HttpContext.Current.Server.MapPath( "~/App_Data/Profile_Data")); permission.Demand(); } public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection properties) { SettingsPropertyValueCollection settings = new SettingsPropertyValueCollection(); // Do nothing if there are no properties to retrieve if (properties.Count == 0) return settings; // For properties lacking an explicit SerializeAs setting, set // SerializeAs to String for strings and primitives, and XML // for everything else foreach (SettingsProperty property in properties) { if (property.SerializeAs == SettingsSerializeAs.ProviderSpecific) if (property.PropertyType.IsPrimitive || property.PropertyType == typeof(String)) property.SerializeAs = SettingsSerializeAs.String; else property.SerializeAs = SettingsSerializeAs.Xml; settings.Add(new SettingsPropertyValue(property)); } // Get the user name or anonymous user ID string username = (string)context["UserName"]; // NOTE: Consider validating the user name here to prevent // malicious user names such as "../Foo" from targeting // directories other than ~/App_Data/Profile_Data // Load the profile if (!String.IsNullOrEmpty(username)) { StreamReader reader = null; string[] names; string values; byte[] buf = null; try { // Open the file containing the profile data try { string path = String.Format( "~/App_Data/Profile_Data/{0}_Profile.txt", username.Replace('\\', '_')); reader = new StreamReader (HttpContext.Current.Server.MapPath(path)); } catch (IOException) { // Not an error if file doesn't exist return settings; } // Read names, values, and buf from the file names = reader.ReadLine().Split (':'); values = reader.ReadLine(); if (!string.IsNullOrEmpty(values)) { UnicodeEncoding encoding = new UnicodeEncoding(); values = encoding.GetString (Convert.FromBase64String(values)); } string temp = reader.ReadLine(); if (!String.IsNullOrEmpty(temp)) { buf = Convert.FromBase64String(temp); } else buf = new byte[0]; } finally { if (reader != null) reader.Close(); } // Decode names, values, and buf and initialize the // SettingsPropertyValueCollection returned to the caller DecodeProfileData(names, values, buf, settings); } return settings; } public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection properties) { // Get information about the user who owns the profile string username = (string) context["UserName"]; bool authenticated = (bool) context["IsAuthenticated"]; // NOTE: Consider validating the user name here to prevent // malicious user names such as "../Foo" from targeting // directories other than ~/App_Data/Profile_Data // Do nothing if there is no user name or no properties if (String.IsNullOrEmpty (username) || properties.Count == 0) return; // Format the profile data for saving string names = String.Empty; string values = String.Empty; byte[] buf = null; EncodeProfileData(ref names, ref values, ref buf, properties, authenticated); // Do nothing if no properties need saving if (names == String.Empty) return; // Save the profile data StreamWriter writer = null; try { string path = String.Format( "~/App_Data/Profile_Data/{0}_Profile.txt", username.Replace('\\', '_')); writer = new StreamWriter (HttpContext.Current.Server.MapPath(path), false); writer.WriteLine(names); if (!String.IsNullOrEmpty(values)) { UnicodeEncoding encoding = new UnicodeEncoding(); writer.WriteLine(Convert.ToBase64String (encoding.GetBytes(values))); } else writer.WriteLine(); if (buf != null && buf.Length > 0) writer.WriteLine(Convert.ToBase64String(buf)); else writer.WriteLine(); } finally { if (writer != null) writer.Close(); } } public override int DeleteInactiveProfiles (ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate) { throw new NotSupportedException(); } public override int DeleteProfiles(string[] usernames) { throw new NotSupportedException(); } public override int DeleteProfiles(ProfileInfoCollection profiles) { throw new NotSupportedException(); } public override ProfileInfoCollection FindInactiveProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords) { throw new NotSupportedException(); } public override ProfileInfoCollection FindProfilesByUserName (ProfileAuthenticationOption authenticationOption, string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) { throw new NotSupportedException(); } public override ProfileInfoCollection GetAllInactiveProfiles (ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords) { throw new NotSupportedException(); } public override ProfileInfoCollection GetAllProfiles (ProfileAuthenticationOption authenticationOption, int pageIndex, int pageSize, out int totalRecords) { throw new NotSupportedException(); } public override int GetNumberOfInactiveProfiles (ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate) { throw new NotSupportedException(); } // Helper methods private void DecodeProfileData(string[] names, string values, byte[] buf, SettingsPropertyValueCollection properties) { if (names == null || values == null || buf == null || properties == null) return; for (int i=0; i<names.Length; i+=4) { // Read the next property name from "names" and retrieve // the corresponding SettingsPropertyValue from // "properties" string name = names[i]; SettingsPropertyValue pp = properties[name]; if (pp == null) continue; // Get the length and index of the persisted property value int pos = Int32.Parse(names[i + 2], CultureInfo.InvariantCulture); int len = Int32.Parse(names[i + 3], CultureInfo.InvariantCulture); // If the length is -1 and the property is a reference // type, then the property value is null if (len == -1 && !pp.Property.PropertyType.IsValueType) { pp.PropertyValue = null; pp.IsDirty = false; pp.Deserialized = true; } // If the property value was peristed as a string, // restore it from "values" else if (names[i + 1] == "S" && pos >= 0 && len > 0 && values.Length >= pos + len) pp.SerializedValue = values.Substring(pos, len); // If the property value was peristed as a byte array, // restore it from "buf" else if (names[i + 1] == "B" && pos >= 0 && len > 0 && buf.Length >= pos + len) { byte[] buf2 = new byte[len]; Buffer.BlockCopy(buf, pos, buf2, 0, len); pp.SerializedValue = buf2; } } } private void EncodeProfileData(ref string allNames, ref string allValues, ref byte[] buf, SettingsPropertyValueCollection properties, bool userIsAuthenticated) { StringBuilder names = new StringBuilder(); StringBuilder values = new StringBuilder(); MemoryStream stream = new MemoryStream(); try { foreach (SettingsPropertyValue pp in properties) { // Ignore this property if the user is anonymous and // the property's AllowAnonymous property is false if (!userIsAuthenticated && !(bool)pp.Property.Attributes["AllowAnonymous"]) continue; // Ignore this property if it's not dirty and is // currently assigned its default value if (!pp.IsDirty && pp.UsingDefaultValue) continue; int len = 0, pos = 0; string propValue = null; // If Deserialized is true and PropertyValue is null, // then the property's current value is null (which // we'll represent by setting len to -1) if (pp.Deserialized && pp.PropertyValue == null) len = -1; // Otherwise get the property value from // SerializedValue else { object sVal = pp.SerializedValue; // If SerializedValue is null, then the property's // current value is null if (sVal == null) len = -1; // If sVal is a string, then encode it as a string else if (sVal is string) { propValue = (string)sVal; len = propValue.Length; pos = values.Length; } // If sVal is binary, then encode it as a byte // array else { byte[] b2 = (byte[])sVal; pos = (int)stream.Position; stream.Write(b2, 0, b2.Length); stream.Position = pos + b2.Length; len = b2.Length; } } // Add a string conforming to the following format // to "names:" // // "name:B|S:pos:len" // ^ ^ ^ ^ // | | | | // | | | +--- Length of data // | | +------- Offset of data // | +----------- Location (B="buf", S="values") // +--------------- Property name names.Append(pp.Name + ":" + ((propValue != null) ? "S" : "B") + ":" + pos.ToString(CultureInfo.InvariantCulture) + ":" + len.ToString(CultureInfo.InvariantCulture) + ":"); // If the propery value is encoded as a string, add the // string to "values" if (propValue != null) values.Append(propValue); } // Copy the binary property values written to the // stream to "buf" buf = stream.ToArray(); } finally { if (stream != null) stream.Close(); } allNames = names.ToString(); allValues = values.ToString(); } }
TextFileProfileProvider stores profile data in exactly the same format as ASP.NET's SqlProfileProvider, with some extra base-64 encoding thrown in to allow binary data and XML data to be stored in a single line of text. Its EncodeProfileData and DecodeProfileData methods, which do the encoding and decoding, are based on similar methods-methods which are internal and therefore can't be called from user code-in ASP.NET's ProfileModule class.
EncodeProfileData packs all the property values passed to it into three values:
A string variable named names that encodes each property value in the following format:
Name:B|S:StartPos:Length
Name is the property value's name. The second parameter, which is either B (for "binary") or S (for "string"), indicates whether the corresponding property value is stored in the string variable named values (S) or the byte[] variable named buf (B). StartPos and Length indicate the starting position (0-based) within values or buf and the length of the data, respectively. A length of -1 indicates that the property is a reference type and that its value is null.
A string variable named values that stores string and XML property values. Before writing values to a text file, TextFileProfileProvider base-64 encodes it so that XML data spanning multiple lines can be packed into a single line of text.
A byte[] variable named buf that stores binary property values. Before writing buf to a text file, TextFileProfileProvider base-64 encodes it so that binary data can be packed into a line of text.
DecodeProfileData reverses the encoding, converting names, values, and buf back into property values and applying them to the members of the supplied SettingsPropertyValueCollection. Note that profile providers are not required to persist data in this format or any other format. The format in which profile data is stored is left to the discretion of the implementor.
Listing 2 demonstrates how to make TextFileProfileProvider the default profile provider. It assumes that TextFileProfileProvider is implemented in an assembly named CustomProviders.
Listing 2. Web.config file making TextFileProfileProvider the default profile provider
<configuration> <system.web> <profile defaultProvider="TextFileProfileProvider"> <properties> ... </properties> <providers> <add name="TextFileProfileProvider" type="TextFileProfileProvider, CustomProviders" description="Text file profile provider" /> </providers> </profile> </system.web> </configuration>
For simplicity, TextFileProfileProvider does not honor the ApplicationName property. Because TextFileProfileProvider stores profile data in text files in a subdirectory of the application root, all data that it manages is inherently application-scoped. A full-featured profile provider must support ApplicationName so profile consumers can choose whether to keep profile data private or share it with other applications.
Click here to continue on to part 6, Web Event Providers