TECHNOLOGIES: ASP | ASP.NET | COM
Moving from ASP to ASP.NET Boosts an Application s Performance
By Michiel van Otegem
ASP.NET is a great new platform for Web development. If I had to choose between creating a new application in ASP.NET and creating a new application in ASP classic, ASP.NET would win, hands down. It s faster and incredibly stable. And, once you ve gotten the hang of ASP.NET, it takes much less time to build an application.
ASP.NET is radically different from ASP classic, though, so if you have an existing application with ASP classic pages, they won t run under ASP.NET without changes. If you want your existing applications to take advantage of ASP.NET, you need to migrate them to ASP.NET.
Do You Really Need to Migrate?
Even if you have an optimal environment for migration, it is still not something you can do in a spare afternoon. Migrating a page of a few hundred lines of code can take anywhere from five minutes to an hour, depending on the complexity of the pages and the number of things you need to change. In some cases, the migration effort may take so much time that redesigning and re-implementing the whole page or application is a better idea. Redoing the whole application may take more time, but then the application can take full advantage of all ASP.NET has to offer. That isn t the case when you just migrate your pages. Re-implementing the business logic probably doesn t take as much time as you think. Components written in Visual Basic 6.0 can be migrated using the Visual Basic .NET upgrade tool. This tool runs automatically when you open a VB6 project in VB .NET and creates a new VB. NET project. Then, it guides you through the upgrading process, step by step. This tool is not 100 percent reliable, but it will get you a long way with a limited amount of work.
In ASP classic, you could run different scripting languages in one page. Because this is not possible in this version of ASP.NET, migrating multi-language pages requires you to recode at least part of them. Depending on how you coded the original page, there may be ways around this problem in the form of user controls, but the easiest way is recoding that section of the page in ASP.NET.
Several major steps are required for migrating your application:
- Rename asp files to aspx.
- Make any language (syntax) changes to the code that are necessary.
- Make code changes related to other syntax and API changes.
- Set ASP.NET to run in ASP compatibility mode if necessary.
- Make changes to objects that previously used default properties and methods.
- Apply typing to all the variables in the code.
In the remainder of this article, I discuss each of these steps.
Handle Language Changes
If you used Option Explicit in your VBScript ASP classic pages, you will need to remove the statement and move it into the Page directive as follows:
You can leave it out, as well, because you need to declare your variables by default in VB .NET. If you didn t use Option Explicit before and didn t declare your variables, you have the fun job of declaring all the variables in your page. Having declared and typed variables is very important for performance, so setting Explicit to False is not a good idea. At this point in the migration process, I suggest you don t specify data types of any variables. The first goal is to get the code working. Once the code works, you can start specifying data types. Otherwise, you will have no way of knowing if your code doesn t work because of the syntax, because of the logic, or because of typing problems.
Another major change is that the Set keyword is no longer available. Because VB. NET is strongly typed and object-oriented, there is no need for it. You need to remove all the Set keywords. For instance, Set Conn = Server.CreateObject( ADODB.Connection )should become Conn = Server.CreateObject( ADODB.Connection ). Be aware that if you search and replace, some words contain set, such as Recordset.
Functions and subroutines in your application are likely to give you a few headaches. Parameters in .NET are passed ByVal, by default, instead of ByRef. This means that if you relied on a function or subroutines changing a parameter, you must prefix that parameter with the ByRef keyword. Another major change is that parameters passed to a subroutines must be surrounded by parentheses now, just like with functions. Therefore, DoSomething param1, param2 must become DoSomething(param1, param2).
Fortunately, the Call keyword is still supported. So, if you already used the Call keyword to force parentheses around parameters, you don t have to worry about this.
Quite a few VB functions and language constructs have been removed. These include some mathematical functions, such as Rnd, Round, and Sqr; and some date and time functions, such as Date and Time. The mathematical functions can be found in the System.Math namespace available to any .NET language. The DateTime data type has several methods that replace the old functions used with dates and times. Related to this are the changes to the typing system, in which the data types Variant and Currency no longer are supported. Because every variable is an object, the Variant data type is obsolete and can be replaced with Object. In addition, the Currency data type can be simulated with most number types. Because of these changes, functions such as VarType, IsEmpty, IsNull, and IsObject no longer are supported. Last but not least, the While...Wend syntax is now obsolete and should be replaced by While...End While or Do While...Loop.
The preceding discussion of language changes is by no means complete. You can find more information on upgrading from VB6 (or VBScript) to VB. NET at
Handle Other Syntax and API Changes
The familiar <%...%> tags are still available in ASP.NET, but they work differently. An object-oriented model in ASP.NET replaces the sequential model of ASP pages. A page in ASP.NET is an object, so procedures (in any language) actually are methods of that object. The old ASP script tags hardly have a place in the object-oriented model, but, for migration purposes, the old sequential model is supported still. An exception here is that functions, subroutines, and global variable declarations must be embedded in SCRIPT blocks. They can t exist within the <%...%> tags because those are used only for sequential execution. So a piece of code like that shown in FIGURE 1, should be changed to the code like that shown in FIGURE 2.
Function Add(X, Y)
Add = X + Y
A = 1
FIGURE 1: ASP code with a function and variable declaration. This code won't work in ASP.NET.
A = 1
FIGURE 2: ASP.NET code migrated from FIGURE 1. The function and global variable declarations have been moved to a SCRIPT tag. This code also takes advantage of the new (but optional) "Return" keyword for returning a value from a function.
Because of the changes to the typing system, the Request and Response objects have changed, also. The collections within the Request and Response objects are no longer collections but are .NET NameValueCollections, which have different properties. You still can access a value of a form element or a value in the query string by name, but Request.Form no longer will yield a string containing all the form values. Instead, it will yield an object reference, so the ASP classic code shown in FIGURE 3 won t work.
If Request.Form <> "" Then
'Do something with the form
FIGURE 3: ASP classic code using the Forms collection to test form contents. This code won't work in ASP.NET.
The code in FIGURE 3 will yield an object reference, which can t be compared with a string, resulting in an error. If you want to have code that does something similar, you should change the code to that shown in FIGURE 4.
If Request.Form.Count > 0 Then
'Do something with the form
FIGURE 4: ASP.NET code migrated from FIGURE 3. Count will be greater than zero if any form variables exist.
Another option you have here is migrating to the event-driven model of ASP.NET. You can change the form tag and the submit button to ASP.NET server controls and use the event handler of the submit button to drive the code that must do something with the form. This is illustrated in FIGURE 5.
FIGURE 5: ASP.NET code migrated from FIGURE 3 using a Web Form. This syntax is more in line with the recommended way to program in ASP.NET.
Going from FIGURE 3 to FIGURE 5 is obviously a bigger change than ordinary migration. It will take some more time to accomplish, but the advantage is that the code is already much closer to pure ASP.NET code. This makes it much more usable at a later stage, such as once you decide to recode large portions of your application. Many of the objects intrinsic to ASP classic have been improved greatly and have many more methods than they had. Most changes are such that the ASP classic code should work still, but there is no 100 percent guarantee that everything will work. The Exception object has replaced the ASPError object, but, as long as you don t type your variables, you should be fine.
Set ASP Compatibility Mode
A major difference between ASP and ASP.NET is the threading model under which it runs. ASP classic runs under the Single Threaded Apartment (STA) model, whereas ASP.NET runs under the Multiple Threaded Apartment (MTA) model. Calling STA components from code running under the MTA model doesn t perform well and is unreliable. This means that if you call COM components that run under the STA model, you have a problem. Unfortunately, every COM component created with Visual Basic 6.0 will use the STA model. Unless you have run C:\Program Files\Common Files\System\ado\makfre15.bat on your server, ADO also runs under the STA model. (Do not do this if you have applications using Microsoft Access!) As soon as you create an STA component from ASP.NET with CreateObject or Server.CreateObject, an exception occurs, and you will get the message shown in FIGURE 6.
FIGURE 6: The error screen when creating an STA component in ASP.NET.
The easiest way to remedy this (for now) is to add aspcompat= True to the Page directive. This directive runs the page in a mode in which STA components can be used safely. However, this comes with a performance hit, so use it sparingly. You will learn how to remedy this situation later in this article.
Change Object Use
If you re lucky, your pages will run now without raising a compilation error. They may still raise run-time errors. If they don t compile, it s likely your code isn t giving you the proper results yet. In this case, you ll have a clue as to what s wrong from the error information shown on your screen, which will include a line number. If your code does compile, you ll have to search for the problem. What may help you in this case is adding trace= True to the Page directive. Doing so provides you with an array of debugging information that can help you track down a problem. In a large portion of the cases containing run-time errors, the root of the problem concerns objects producing the wrong result because of the way they are used. In the world of .NET, objects no longer have default methods and properties. This means the migrated code uses a method of an object you didn t intend it to use when you originally wrote it in ASP classic, returning an incorrect result. Probably the most common situation in which this happens is when you are working with ADO Recordset objects. When you write a value from a field in a record to the browser, you probably use something like the code shown in FIGURE 7.
1: Dim oConn
2: Dim oRs
3: Dim sSQL
5: sSQL = "SELECT ID, Name FROM Employees"
6: Set oConn = Server.CreateObject("ADODB.Connection")
7: oConn.Open Application("ConnString")
8: Set oRs = Server.CreateObject("ADODB.Recordset")
9: oRs.Open sSQL, oConn
11: Do While Not oRs.EOF
12: Response.Write oRs("Name") &
18: Set oRs = Nothing
19: Set oConn = Nothing
FIGURE 7: Original ASP code that walks through an ADO Recordset.
The code in FIGURE 7 will not work until you make the syntax changes mentioned previously in this article. When you ve done that, the code will run, but the recordset code on line 12 will yield a standard string in the form of System.__ComObject. This is because the default property no longer exists. And, unless you explicitly tell ASP.NET which property you need, you will get an object, which then is converted to the above string. FIGURE 8 shows a correctly migrated page.
1: Dim oConn
2: Dim oRs
3: Dim sSQL
5: sSQL = "SELECT ID, Name FROM Employees"
6: oConn = Server.CreateObject("ADODB.Connection")
8: oRs = Server.CreateObject("ADODB.Recordset")
9: oRs.Open(sSQL, oConn)
11: Do While Not oRs.EOF
12: Response.Write(oRs.Fields("Name").Value &
18: oRs = Nothing
19: oConn = Nothing
FIGURE 8: ASP.NET code migrated from FIGURE 7.
In Figure 8, line 12 now uses the Fields collection of the Recordset object and selects the Value property. Because this code explicitly states which collection and which property you want, it will yield the correct result. When you ve changed all the default properties into explicitly named properties, your code should work properly.
Now that your code runs, it is time to apply typing. If you don t do this, the performance of the code is likely to be worse than under ASP classic because the code is late-bound, which is a real performance killer. With late-bound code, the type of all the variables has to be resolved at run time. In contrast, typed code is early-bound, meaning that the type of the variable is known at design time. The compiler can take this information to increase performance of the code. With intrinsic variables, such as strings and numbers, typing them is easy. Instead of writing Dim myInteger, you can write Dim myInteger As Integer.
Another thing you can do here is initialize the value of the variable by giving it a value in the declaration, like this:
Dim myInteger As Integer = 5
This is a great way to clean up some of your code. I strongly advise you to start typing only the simple variables, a few at a time. In some cases, a function may return a Long, for instance. And, if you type the variable getting that value as an Integer, the code will fail. By typing a few variables at a time, you should have a fair idea of what s going wrong. First, make sure all the simple types work before proceeding to more complex types, such as objects. With objects, you can use the New keyword now instead of CreateObject or Server.CreateObject to instantiate the object. You can even do this when you declare the variable, like this:
Dim myObj As New MyClass
If you want to use COM components with early binding, you need to import the type library of the component. You can do this using the tlbimp.exe tool like this:
tlbimp myTest.tlb /out:myTest.dll
Then, you can move the resulting DLL to the bin directory of the application. Note that with components that do not have a tlb file, you can use the tool on the dll file, also. But then you should make sure the file to which you write has a different name than the original dll. The tlbimp.exe tool creates a .NET wrapper around the COM component. Once you have copied the new DLL to the bin directory, you can reference it in your ASP.NET page by adding the following after the Page directive:
<%@ Import Namespace="myTest"%>
<%@ Assembly Name = "myTest"%>
With this code in place, you can declare variables to be early-bound:
Dim myObj As myTest.myClass
myObj = New myTest.myClass
Note that I have separated the variable-declaration and the object instantiation statements here. If you declare and create the object in one statement you could be faced with the situation that an STA object is created from an MTA thread, even if you ve set aspcompat= True . Be aware that using a COM wrapper no longer gives you the error shown in FIGURE 6 if you don t set aspcompat = True . This is misleading because, in this case, STA components are still being called from an MTA thread. ASP.NET no longer can detect this because the code is early-bound, and, as far as ASP.NET knows, the component is MTA.
The fact that you need to create wrappers for your custom COM components is logical. Unfortunately this is also necessary for Microsoft s COM components, such as ADO. In beta releases of the .NET SDK Microsoft provided wrappers which it called primary interop assemblies, so you didn't have to create the wrappers yourself. This is not the case with the final release. Now you must find the proper DLL and create it using tlbimp.exe. Because of the effort involved in creating wrappers, you should also look at using the equivalent .NET component instead, which will also increase performance and maintainability. If you do want to use ADO (which is core to many Web applications) you can find msado15.dll in the /Program Files/Common Files/System/ado folder. Just like with your custom components, you have to put the wrapper assembly in the bin directory of your application. Assuming you created the wrapper assembly and named it ADODB.dll, you can use it with the following code:
<%@ Import Namespace="ADODB"%>
<%@ Assembly Name="ADODB"%>
Including these directives in the page allows you to declare variables as ADODB.Connection, ADODB.Recordset, etc. One problem here is that the ADO constants such as adOpenForwardOnly are not imported through the use of this namespace. This is unfortunate because it means you have to declare these constants yourself or include adovbs.inc (with some minor changes), as is common in ASP classic applications. Importing the ADO type library into global.asax as you might have done with ASP classic s global.asa is not possible.
Migrating an application from ASP classic to ASP.NET is not that difficult. You have to be aware of the possible pitfalls each step of the way, though. This article should help guide you through this process. After following the steps in this article, your application now work properly in ASP.NET. Performance is likely to be about 20 percent better than the original ASP classic application, and the application will be much more stable. Full-fledged ASP.NET pages are up to four times as fast as ASP classic pages, so the next step should be to re-implement key pages to take full advantage of ASP.NET. This includes moving from ADO to ADO.NET. The latter is much better suited for multi-tiered applications, such as high-end Web applications, and it outperforms ADO by a considerable margin.
Michiel van Otegem is president and chief Web-development teacher of ASPNL, a consulting and teaching firm targeting the Dutch and European markets. He teaches advanced ASP, ASP.NET, and XML/XSLT classes; writes articles and tutorials for several sites and magazines; and is a frequent speaker at conferences. In addition, he is the author of SAMS Teach Yourself XSLT in 21Days. He is a longtime contributing member of ASPFriends.com mailing lists, which he now helps to moderate and improve. Readers may contact him at mailto:[email protected].
Tell us what you think! Please send any comments about this article to [email protected]. Please include the article title and author.