9. Profiles and Custom Data Types
Using a custom class with
profiles is easy. You need to begin by creating the class that wraps the
information you need. In your class, you can use public member
variables or full-fledged property procedures. The latter choice, though
longer, is the preferred option because it ensures your class will
support data binding, and it gives you the flexibility to add property
procedure code later.
Here's a Address class
that ties together the same information you saw in the previous example,
using automatic properties to reduce the amount of code:
<Serializable()> _
Public Class Address
Public Property Name() As String
Public Property Street() As String
Public Property City() As String
Public Property ZipCode() As String
Public Property State() As String
Public Property Country() As String
Public Sub New(ByVal name As String, ByVal street As String, _
ByVal city As String, ByVal zipCode As String, _
ByVal state As String, ByVal country As String)
Me.Name = name
Me.Street = street
Me.City = city
Me.ZipCode = zipCode
Me.State = state
Me.Country = country
End Sub
Public Sub New()
End Sub
End Class
You can place this class in the App_Code directory. The final step is to add a property that uses it:
<properties>
<add name="Address" type="Address" />
</properties>
Now you can create a test page that uses the Address class. Figure 3 shows an example that simply allows you to load, change, and save the address information in a profile.
Here's the page class that makes this possible:
Public Partial Class ComplexTypes
Inherits System.Web.UI.Page
Protected Sub Page_Load(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Me.Load
If Not Page.IsPostBack Then
LoadProfile()
End If
End Sub
Protected Sub cmdGet_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles cmdGet.Click
LoadProfile()
End Sub
Private Sub LoadProfile()
txtName.Text = Profile.Address.Name
txtStreet.Text = Profile.Address.Street
txtCity.Text = Profile.Address.City
txtZip.Text = Profile.Address.ZipCode
txtState.Text = Profile.Address.State
txtCountry.Text = Profile.Address.Country
End Sub
Protected Sub cmdSave_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles cmdSave.Click
Profile.Address = new Address(txtName.Text, _
txtStreet.Text, txtCity.Text, txtZip.Text, _
txtState.Text, txtCountry.Text)
End Sub
End Class
9.1. Dissecting the Code . . .
When the page loads
(and when the user clicks the Get button), the profile information is
copied from the Profile.Address object into the various text boxes. A
private LoadProfile() method handles this task.
The
user can make changes to the address values in the text boxes. However,
the change isn't committed until the user clicks the Save button.
When
the Save button is clicked, a new Address object is created using the
constructor that accepts name, street, city, zip code, state, and
country information. This object is then assigned to the Profile.Address
property. Instead of using this approach, you could modify each
property of the current Profile.Address object to match the text values.
The content of the Profile object is saved to the database automatically when the request ends. No extra work is required.
9.2. Custom Type Serialization
You need to keep in mind a few
points, depending on how you decide to serialize your custom class. By
default, all custom data types use XML serialization with the
XmlSerializer. This class is relatively limited in its serialization
ability. It simply copies the value from every public property or member
variable into a straightforward XML format like this:
<Address>
<Name>...</Name>
<Street>...</Street>
<City>...</City>
<ZipCode>...</ZipCode>
<State>...</State>
<Country>...</Country>
</Address>
When deserializing your class,
the XmlSerializer needs to be able to find a parameterless public
constructor. In addition, none of your properties can be read-only. If
you violate either of these rules, the deserialization process will
fail.
If you decide to use binary serialization instead of XmlSerialization, .NET uses a completely different approach:
<add name="Address" type="Address" serializeAs="Binary"/>
In this case, ASP.NET enlists
the help of the BinaryFormatter. The BinaryFormatter can serialize the
full public and private contents of any class, provided the class is
decorated with the <Serializable()> attribute. Additionally, any
class it derives from or references must also be serializable.
9.3. Automatic Saves
The profiles feature isn't able
to detect changes in complex data types (anything other than strings,
simple numeric types, Boolean values, and so on). This means if your
profile includes complex data types, ASP.NET saves the complete profile
information at the end of every request that accesses the Profile
object.
This behavior
obviously adds unnecessary overhead. To optimize performance when
working with complex types, you have several choices. One option is to
set the corresponding profile property to be read-only in the web.config
file (if you know that property never changes). Another approach is to
disable the autosave behavior completely by adding the
automaticSaveEnabled attribute on the <profile> element and
setting it to false, as shown here:
<profile defaultProvider="SqlProvider" automaticSaveEnabled="false">...</profile>
If you choose this approach, it's
up to you to call Profile.Save() to explicitly commit changes.
Generally, this approach is the most convenient, because it's easy to
spot the places in your code where you modify the profile. Just add the
Profile.Save() call at the end:
Profile.Address = New Address(txtName.Text, txtStreet.Text, _
txtCity.Text, txtZip.Text, txtState.Text, txtCountry.Text)
Profile.Save()
For instance, you could modify the earlier example (shown in Figure 21-3)
to save address information only when it changes. The easiest way to do
this is to disable automatic saves, but call Profile.Save() when the
Save button is clicked. You could also handle the TextBox.TextChanged
event to determine when changes are made, and save the profile
immediately at this point.