Migrating to ASP.NET 2 - Part 5 - It's the Little Things - Or Why I Love ASP.NET 2.0

This is the fifth and final part of a (long running) article series about migrating my ASP.NET 1.x Web site to ASP.NET 2. This installment focuses on the many little improvements in the .NET Framework in general and ASP.NET 2.0 and Visual Web Developer in particular.

Read Part 1 - Introduction
Read Part 2 - Setup and Master Pages
Read Part 3 - Business Logic, Content Pages and User Controls
Read Part 4 - Implementing Custom Providers for Membership, Role Management and Profiles
Read Part 5 - It's the little things...

The Move to ASP.NET 2.0

As you may know, my site has been running on ASP.NET 2.0 for quite some time now. The move to the new platform was pretty painless, although it took quite some work. Since I upgraded each page manually, it took some time before the entire site was done. In the mean-time, Microsoft released "Web Application Projects", an add-in for Visual Studio (not for Visual Web Developer) that makes upgrading existing 1.x sites to ASP.NET 2.0 a lot easier. Had the tool been around when I made the switch, I surely would have used it.

Anyway, the migration is done and the site runs fine. It's now much easier to add new functionality to my site, especially because I also cleaned up some of the mess created in the past during the migration process. In this final part of the article series, I'll describe some of the new features in ASP.NET 2.0 that make building web sites so much easier....

What Else Is New....

A couple of days ago I was trying to explain to a friend why ASP.NET 2.0 was so much better than previous releases or other server side technologies. It sometimes seems hard to decide where to start; there are so many new features that it's pretty difficult to list them all. In this article I'll list some of the new and less well-known features that may bring joy in a developer's life. I'll give you a brief recap of the major new features first and then spend the rest of the article discussing the little things that have changed, but that'll make your life as a developer much easier.

Major Changes

My latest book, ASP.NET 2.0 Instant Results is all about the major new features: Master Pages, Data Source Controls, the whole provider model for membership, role management, profiles and personalization, the new data controls like the GridView, DetailsView and FormView, login controls, navigation controls, themes, database cache invalidation, data provider independent data access and much more. The phrase "I could write a book about it" turned out to be quite true... ;-)

I am sure you have no problem finding out more information about these new features. Google for "ASP.NET 2.0 what's new" and you'll find loads of information, tutorials, books, walkthroughs and so on explaining how to put all this new stuff to good use. But in between all these major items, you'll find a lot of small changes and enhancements that can really speed up your productivity or let you do things you couldn't do before.

Small Changes

Not a day goes by without discovering something new in .NET or ASP.NET. I often find myself murmuring stuff like "hey, that's cool", "Wow, they finally fixed that", or "Hmmm, interesting...". Below you find a list with some of these findings that I found helpful. I am sure it won't be complete, not will it ever be, but it may help you in learning things about .NET you didn't know before.

Working With Controls

Although many of the ASP.NET 1.x controls still exist, a lot of them have been enhanced to provide more functionality. This section lists a number of these enhancements.

Images

The Image control now has an GenerateEmptyAlternateText property that works as a counter part of the AlternateText property. Previously, when you left the AlternateText attribute empty, you would get no alt attribute on your image tag. Now, with this new property, ASP.NET will render something like this:

<img id="Image1" src="SomeImage.gif" alt="" style="border-width:0px;" />

Useful when you want a valid XHTML page, but don't want tooltips appearing on, say, your layout images.

DropDownList and other List controls

The DropDownList control and other list controls now have a very convenient AppendDataBoundItems property. When set to true, items that are added through databinding (by an ObjectDataSource control, for example, or through code in the code behind), the fixed <asp:ListItem> objects you added in the markup will no longer be deleted. This means you no longer have to add an item like "Please make a selection" programmatically in the code behind.

The Label control

This control now has a AssociatedControlID property that allows you to hook it up to another form control. For example, with this markup:

<asp:Label ID="Label1" runat="server" Text="Your Name" 
            AssociatedControlID="txtName"></asp:Label>
<asp:TextBox ID="txtName" runat="server"></asp:TextBox>

you get the following HTML in the browser:

<label for="txtName" id="Label1">Your Name</label>
<input name="txtName" type="text" id="txtName" />

You can now click the label, and the associated text box will get the focus automatically. This applies to other controls as well; for instance, when you associate a label with a check box, it will switch its checked state whenever you click the label.

This may not seem like a big deal to you, but it's great for improving the accessibility of your site. Some people may have a hard time trying to hit something as small as a checkbox with their mouse. With this simple trick, you enlarged the "hit spot" by a factor 10 or so.

When I first discovered this property, I Googled a bit for its intended use. I then found out this property was added in SP1 of the .NET 1 framework. So, technically it's not new in .NET 2; I had simply not discovered it before.

ValidationGroups

ValidationGroups are a very welcome addition to the whole validation architecture. In ASP.NET 1.x, validation meant an all or nothing scenario. Either the entire page was validated, or nothing was. This was problematic if, for example, you had a simple Login box and a Search box on the same page. Obviously, you'd want to make sure a user typed in a user name and password before they could log in. However, with a Search button with validation on the same page, trying to log in would also cause the Search box to be validated.
Now, in ASP.NET 2, you can fix this problem simply by adding a ValidationGroup to the relevant controls:

<asp:TextBox ID="TextBox1" runat="server" ValidationGroup="Group1" />

<asp:Button ID="Button1" runat="server" Text="Button"
             ValidationGroup="Group1" />

<asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" 
        ControlToValidate="TextBox1" ValidationGroup="Group1" 
        ErrorMessage="Text 1 is required" />


<asp:TextBox ID="TextBox2" runat="server" ValidationGroup="Group2" />

<asp:Button ID="Button2" runat="server" Text="Button" 
             ValidationGroup="Group2" />
<asp:RequiredFieldValidator ID="RequiredFieldValidator2" runat="server"
         ControlToValidate="TextBox2" ValidationGroup="Group2" 
          ErrorMessage="Text 2 is required"></asp:RequiredFieldValidator>

When you run a page with this code, you'll find you cannot click button 1 before you entered some text in text box 1. The same applies to text box and button 2. However, you no longer have to enter text in the second text box when you want to click the first button.

Simple, but very effective. Oh, by the way, the client side validation now also works in other "up level" browsers, like FireFox.....

Focus on Errors

Speaking of validation controls, the BaseValidator class, that the other validator controls inherit from now has a SetFocusOnError property. When you set it to true, the first invalid control in the form now automatically gets the focus when an error occurs. Very useful if you have a large form with many controls and validators, and want to guide the user to the first field that caused the error.

OnClientClick

The Button, LinkButton and Imagebutton now feature a property called OnClientClick that allows you to set some JavaScript that is fired at the client when the button gets clicked. This way you can, for example, ask for confirmation before an item is deleted, with code like this:

<asp:Button ID="Button1" runat="server" Text="Delete" 
OnClientClick="return confirm('Are you sure you want to delete this item?');" />

In the browser this end up like this:

<input type="submit" name="Button1" value="Delete" 
  onclick="return confirm('Are you sure you 
                    want to delete this item?');" id="Button1" />

When the user clicks the Cancel button, the post back is cancelled as well and nothing happens. Yoohoo!! No more messing with Attributes.Add in the code behind....

FileUpload Control

In ASP.NET 1.x you could upload controls with code like this: <input type="file" runat="server" />. But, in ASP.NET 2, a true <asp:FileUpload> control has been introduced that allows you to work with an upload control the same way as you work with other controls.

XHTML By Default

A very problematic issue in ASP.NET 1.x was the run-time experience. Controls didn't always render valid markup, so you'd have a hard time trying to create a page that's XHTML valid. Fortunately, that's all gone and pages render in XHTML transitional by default now. If you ever want to go back to the old way, for example, when you upgraded a large .NET 1.x site to 2.0, you can add the following to the web.config file:

<xhtmlConformance mode="Legacy | Strict | Transitional" />

By default, this option is set to Transitional, but you can also instruct ASP.NET to render in Legacy mode (close to how ASP.NET 1.x used to work) or Strict. Although strict sounds compelling, you should realize it also means .NET leaves out a number of important elements, most notably, name attribute of the <form> tag.

Visual Web Developer / Visual Studio 2005 now plays by the rules a lot better as well. I think one of the biggest, if not the biggest time safer in Visual Studio is not really a new feature, but a fix of stuff that has been broken for years: the HTML designer. In every major product that produced HTML, the designer was a developer's worst nightmare. Not only created it millions of unnecessary lines of code (anyone remember recordsets in Visual Interdev??), it also screwed up your own code. You got extra attributes, reformatted text, disappearing text, indenting, page directives and so on. All in all; a big mess. But along comes Visual Studio 2005 / VWD with a great designer that behaves a lot better.

I heard that Microsoft is working on a new designer called Saphire which is supposed to behave even better. Let's wait and see.

Working With Parameters in Databases

Not a new ASP.NET 2 feature, but a pretty interesting change in .NET 2 anyway: provider independent code. In my latest book, I devoted an entire chapter (the Wrox Blog) to working with databases in a provider independent way. Through a simple web.config setting you can instruct the application to work with either a SQL Server 2005 database, or with a Microsoft Access database. One area that's hard to make dynamic is the way you work with parameters and how you name them. In my Blog application, I fixed the problem like this:

Public Shared Function ReturnCommandParamName( _
                   ByVal paramName As String) As String
  Dim returnValue As String = String.Empty
  Select Case AppConfiguration.ConnectionStringSettings.ProviderName.ToLower()
    Case "system.data.sqlclient"
      returnValue = "@" & paramName
    Case "system.data.oledb"
      returnValue = "?"
    Case Else
      Throw New NotSupportedException("The provider " & _
                  AppConfiguration.ConnectionStringSettings.ProviderName & _
                  " is not supported")
  End Select
  Return returnValue
End Function

This code returns the correct parameter name by looking at the current ProviderName. So, for SQL Server something like @firstName would be returned, while for Access you would end up with a single question mark.

While this works for the current implementation, it means you'll need to modify the ReturnCommandParamName method whenever you want to support a new provider in you code. Fortunately, the DbConnection class now has a GetSchema method that allows you to retrieve a lot of information about the database engine you're working with, including the ParrameterMarkerFormat. You can use it like this:

Public Shared Function ReturnCommandParamName( _
           ByVal paramName As String) As String


  Dim myFactory As DbProviderFactory = DbProviderFactories.GetFactory( _
           AppConfiguration.ConnectionStringSettings.ProviderName)
		
  Using myConnection As DbConnection = myFactory.CreateConnection()
    myConnection.ConnectionString = _
           AppConfiguration.ConnectionStringSettings.ConnectionString
    myConnection.Open()
    Dim markerFormat As String = _
           myConnection.GetSchema("DataSourceInformation").Rows(0) _
           ("ParameterMarkerFormat").ToString()
    Return String.Format(markerFormat, paramName)
  End Using
End Function

The ParameterMarkerFormat row will hold something like {0} for SQL Server and ? for Access. So, when you then format it using the String.Format method, you end up with something like firstName or ?, depending on whether you're using SQL Server or Access.

I was a bit surprised that for SQL Server {0} is returned, and not @{0}. However, SQL Server seems to work fine with parameters names that are not prefixed with the @ symbol.

Notice that this method opens a connection to the data source each time it's accessed. So, it's a good idea to store the ParameterMarkerFormat in a shared variable or in the cache, and only retrieve it the first time you need it.

This is Not the End

This list is far from complete, as there are many many changes, additions and new features in .NET 2.0. However, these were the ones that came to mind first, and that I have been using in my site here at imar.spaanjaars.com during the migration process from .NET 1.1 to .NET 2.0.

I might add new items to this article in the near future, or I post another, separate article detailing other small, but important changes.

Use the Talk Back feature at the bottom of this page if you want to share your favorite new ASP.NET 2.0 features.


Where to Next?

Wonder where to go next? You can read existing comments below or you can post a comment yourself on this article .


Consider making a donation
Please consider making a donation using PayPal. Your donation helps me to pay the bills so I can keep running Imar.Spaanjaars.Com, providing fresh content as often as possible.



Feedback by Other Visitors of Imar.Spaanjaars.Com

On Thursday, April 08, 2010 5:11:30 PM Bryan Nilsen said:
Is there a simple way to insert the UserID from the aspnet_Users table as the UserID in an "additional info" table, similar to UserID being inserted between aspnet_Users and aspnet_Membership? In other words, I'd like to replicate whatever SP that ASP.Net already uses to accomplish this task so I don't have to reinvent the wheel. Where would I find this?
On Thursday, April 08, 2010 5:14:17 PM Imar Spaanjaars said:
Hi Bryan,

I am not sure what you're asking exactly. You can get the ProviderUserKey from the Membership user, convert it to a Guid and send it wherever you need it.

Does that help?

Imar
On Friday, April 09, 2010 1:44:16 AM Bryan Nilsen said:
Thanks, Imar. Specifically and to clarify, what I have is a table that relates to the aspnet_Users table with a profile system for data that my application needs in addition to the standard security stuff required by the CreateUserWizard. Problem is, the create user procedure wasn't populating the PK/Guid field in my additional table with the same UserID guid as in the aspnet_Users and aspnet_Membership tables. So, after observing that it wasn't populating this field with the equivalent Guid value, I was looking for a way to populate the field, or update it, "after the fact."

One way I thought of doing this was by finding and adapting the SP with which the ASP.Net system creates and inserts the Guid for UserID in the two aspnet_ tables mentioned above.

Since I didn't know where to look for that, I tried the following today in the _CreatedUser handler of my CreateUserWizard but got nowhere because the UserName keeps coming up with a value of Nothing, which I know isn't correct because the wizard displays the "success" message at the end and the fields are written to the database. It's just very strange that my _CreatedUser handler doesn't show...um...that a user has been created.

        'empty Profile for newly created user
        Dim p As ProfileCommon = ProfileCommon.Create(cuw.UserName, True)
        p.Initialize(cuw.UserName.ToString(), True)
        'get current user's UserID value
        Dim UserID As New Guid(Membership.GetUser(cuwReg.UserName).ProviderUserKey.ToString())
        If Not String.IsNullOrEmpty(UserID.ToString) Then p.UserID = UserID.ToString
        p.Save()

Another strange thing is that, as I mentioned in the first paragraph, the guid wasn't writing to the third table, but now it appears it is--possibly because I turned on cascading update and delete in the relationships portion of all three tables. I will let you know if that stops working as it should, so that part of my request is at least temporarily taken care of; but if someone can still explore the user not showing as created, I'd appreciate it.
On Friday, April 09, 2010 3:00:06 AM Bryan Nilsen said:
Correction to above: The lines under "get current user's UserID" (in my previous entry) does work because it extracts the value from the UserName textbox; but these three methods below don't (indicating that the user hasn't yet been created):

        'Dim strName As String = Membership.GetUser(Page.User.Identity.Name).ProviderUserKey.ToString()

        'Dim strName As String = User.Identity.Name.ToString

        'Dim mu As MembershipUser = Membership.GetUser
        'Dim strUserID As String = mu.ProviderUserKey.ToString
        'Dim guidUserID As Guid = New Guid(strUserID)

The strange thing is that method (in the previous entry) returns a value for the UserID, which proves that the user has been created, yet the three methods above don't show any such indication. Could it be that the UserID has been created but not the UserName? That doesn't make any sense to me because the handler for the event is "CreatedUser".
On Friday, April 09, 2010 5:18:20 PM Imar Spaanjaars said:
Hi Bryan,

The user *has* been created, and thus this works:

Dim UserID As New Guid(Membership.GetUser(cuwReg.UserName).ProviderUserKey.ToString())

(which could be rewritten as tis:

Dim UserID As Guid = Membership.GetUser(cuwReg.UserName).ProviderUserKey, Guid)

as the ProviderUserKey *is* a Guid.

However, although the user is created, he is not logged in. Logging in happens during an earlier phase of the page life cycle, so when CreatedUser fires, the account is created but the user is not logged in yet. Hence, this:

User.Identity.Name.ToString

does not work, as the current User property is not yet associated with the user account that was just created.

Hope this helps,

Imar
On Friday, April 09, 2010 8:42:42 PM Bryan Nilsen said:
Thanks, Imar; this was a great explanation. The following code produces the error message "expected end of statement." It's apparent that it's missing an opening parenthesis but I wasn't able to guess the right place.

Dim UserID As Guid = Membership.GetUser(cuwReg.UserName.ProviderUserKey), Guid)
On Friday, April 09, 2010 9:41:40 PM Imar Spaanjaars said:
Hi Bryan,

Sorry, my bad. That's what you get when you use a textarea as a compiler... ;-) Forgot the call to CType:

Dim UserID As Guid = CType(Membership.GetUser(cuwReg.UserName).ProviderUserKey, Guid)

Hope this helps,

Imar

Talk Back! Comment on Imar.Spaanjaars.Com

I am interested in what you have to say about this article. Feel free to post any comments, remarks or questions you may have about this article. The Talk Back feature is not meant for technical questions that are not directly related to this article. So, a post like "Hey, can you tell me how I can upload files to a MySQL database in PHP?" is likely to be removed. Also spam and unrealistic job offers will be deleted immediately.

When you post a comment, you have to provide your name and the comment. Your e-mail address is optional and you only need to provide it if you want me to contact you. It will not be displayed along with your comment. I got sick and tired of the comment spam I was receiving, so I have protected this page with a simple calculation exercise. This means that if you want to leave a comment, you'll need to complete the calculation before you hit the Post Comment button.

If you want to object to a comment made by another visitor, be sure to contact me and I'll look into it ASAP. Don't forget to mention the page link, or the QuickDocId of the document.

For more information about the Talk Back feature, check out this news item.