January 24, 2000
The following article originally appeared in the MSDN Online Voices "Servin' It Up" column.
This article is for you if you fall into one or more of the categories below:
Why Use Components?
Server.CreateObject vs. CreateObject
OnStartPage/OnEndPage vs. ObjectContext
Distribution of Components
Components. Some love them, some fear them. Those who fear components can usually share a horror story with you. Let's face it: When it comes to working with components under ASP, what you don't know can hurt you. If you've fallen, get up, dust yourself off, and read on. In this article, I'll provide general guidelines learned from experience that will help you build better component-based ASP solutions.
Before I begin to discuss component guidelines, it's worth addressing what value components add to ASP applications. Many developers unfamiliar with components usually wonder what all of the fuss is about. Components can provide the following benefits to your ASP application:
These benefits can come with a price. Creating a component solution can be more expensive in terms of adding complexity to the development process. Deployment and troubleshooting may also become more difficult and are realistic factors. However, don't let short-term obstacles hinder your long-term investment. How do you know if it's worth the cost? Consider the following:
After reviewing the considerations above, test your assumptions. Prototypes can quickly separate fact from fiction.
Now, with some understanding of the benefits that components make possible, let's move on. Following the guidelines below will help you maximize these benefits. Consider these guidelines a steppingstone to help you build more stable, more scalable, better performing ASP component applications.
In general, use stateless components and stateless ASP pages where you can. Components should not require state from one method call to the next. Store complex state in a database. For simple data, leverage cookies, the QueryString, or hidden form fields to pass data across pages.
Server resources are limited. Maintaining state in your components means consuming valuable resources while exposing your application to resource contention and concurrency issues. Stateless components help you avoid these issues. Stateless components also provide more deployment options as well as enhance your ability to share resources across multiple clients.
A common pitfall that developers run into is designing or using components that need to maintain state. Watch out for this desktop mentality. Typically, developers coming from a desktop development background will design components that rely on state.
Avoiding the use of ASP Sessions improves server performance by shortening the code path and reducing the consumption of server resources. If you don't use ASP Sessions, disable Session state through the Internet Services Manager (see the Internet Information Services [IIS] documentation). You can also disable Session state on a per-page basis using the following tag in your ASP pages that do not require Session:
Deployment flexibility is another important aspect, particularly when running your application on a Web farm. If you rely on ASP Sessions, a given user's requests are bound to a specific Web server, because Session state is server-specific. Avoiding state in your middle tier and your Web server, and using a database, allows your ASP requests to be processed by any of the available Web servers in the farm. Therefore, you reduce contention, provide better redundancy, and allow for more distribution options.
For alternatives to passing data across pages without using ASP Sessions, please see the following Knowledge Base (KB) articles:
Don Box also offers more insight into state management with respect to MTS in his ActiveX® Q&A article .
In general, use your components at page scope. Page scope means you create an object on a page, use it, and release it -- all on the same page.
Components marked Both or Apartment work well within page scope. Use Apartment model components, such as Visual Basic components, at page scope only. If you need to store a component in Application or Session, "Both" is recommended. You can store components marked Both in Session or Application scope, but your component will need to guarantee thread safety.
Using components at page scope allows server resources to be reclaimed. Freeing resources minimizes concurrency issues and allows poolable resources to be shared across clients. Additionally, page scope components avoid threading issues to which Session- or Application-scoped objects are subject. Threading issues will be covered in more detail under the Threading Models category below.
One of the most common problems is storing Visual Basic or other Apartment model objects in Application scope. If you attempted to store an Apartment model object created using Server.CreateObject within Application scope, you would see the following error:
Application object error 'ASP 0197: 80004005'
Disallowed object use
/VirDir/global.asa, line 7
Cannot add object with apartment model behavior to the application intrinsic object.
However, if you use the <OBJECT> tag to store an Apartment model object in Application scope, you do not get a run-time error. Instead, the object is created on a special Single-Threaded Apartment (STA) thread, and all calls are marshaled to that thread -- and serialized. The reason is the component's threading model is not checked. Unfortunately, the problem then appears during run time.
Another common problem is storing Apartment model objects in Session scope, which binds the user's session to a specific thread. This behavior can seriously impact server performance. It essentially defeats the purpose of a thread pool, since all calls will be serialized to the thread that created the object.
See the following KB articles for additional information:
Separate your presentation, business, and data services. Your business components should enforce business rules. Your business components should not wrap your data access technology. That's the role of the components in your data tier. Your business components should not contain references to ASP objects.
ASP provides presentation services. Objects that reference ASP should render HTML. These objects could, in turn, call business components registered with MTS/COM+.
Partitioning your application into separate and distinct services can offer the following benefits:
A common pitfall is one we refer to as the Swiss Army component. The Swiss Army component provides all of your services rolled into one (just like the knife with a corkscrew, toothpick, and 17 other tools). Grouping unrelated services in a component makes it difficult to use, understand, and maintain.
Another trap that's easy to fall into is referencing ASP from your business components. Coupling ASP to your business logic (by using Request or Response objects, or by having HTML built in it) both limits reuse of your components by different clients and limits horizontal scalability. Objects that reference ASP built-in objects should live on the same box as the Web server. Ideally, your business components can be distributed across different boxes for horizontal scalability. Either provide presentation services directly in your ASP script, or build HTML rendering components that reference ASP built-in objects and keep these components on your IIS box.
Successful design patterns can serve as models to handle common business problems. For example, patterns for handling Create Read Update Delete (CRUD) operations can help you partition the application into distinct logical services for presentation, business rules, and data access.
Refer to the following articles for more specific examples of design patterns that you can model from in your own applications:
Which comes first? Choosing your component's scope or choosing your component's threading model? Either way, you need to consider threading ramifications unless you stick with Apartment or Both model components at page scope. (If you are a Visual Basic programmer, and you wonder what your component's theading model is, it is always Apartment.)
If you need your objects to be stored in Application or Session, you need to use components marked Both and aggregate the Free-threaded Marshaller (FTM).
Do not use Single-threaded components and avoid using Free-threaded components from ASP.
Note: Visual Basic can produce Single-threaded components if you're not careful. Be sure to set the Threading Model to Apartment Threaded on the General tab in your project's properties. Also note that you should select the Unattended Execution and Retained in Memory options, which are found on the same tab.
If you're using Visual Basic, it's a no-brainer. Visual Basic limits you to Apartment model. I wouldn't consider this much of a limit, given that Visual Basic Apartment model objects can perform extremely well, at page scope. Fitch and Mathers Stock 2000 shattered any preconceived notions of performance. In addition, many live sites built with ASP, SQL, and Visual Basic prove daily that page-scope Apartment model components can both scale and perform.
If you aggregate the FTM on your component marked Both, you can make calls between threads without any marshalling or thread switches. If a component marked Both does not aggregate the FTM, ASP will treat it as an Apartment-threaded object -- just like a Visual Basic component. Bear in mind that if you plan on taking advantage of COM+ Object Pooling, do not aggregate the FTM. For rules on Object Pooling, see the Platform SDK documentation.
Single-threaded and Free-threaded components run under the System security context. Even worse, Single-threaded components are subject to dead-locks.
Probably the most common trap is using components that weren't designed to run under ASP, such as using Single-threaded components. Most developers run into this either when they port a desktop application to ASP or when they work with a third-party control. If you're not sure what the component's threading model is, you can check the component's registry entry (but you can't always rely on it).
For more insight into threading models and their impact on ASP, please see the following articles:
Additionally, the following KB articles provide more details on threading issues:
The component should not make any assumptions about the user context under which it will run. Do not access user-specific information, such as HKEY_CURRENT_USER or desktop-specific resources, as these will not be available to your component. Your application should not perform actions that typically require desktop interaction, such as raising dialog boxes, using SendKeys or invoking components that rely on a user interface.
Your component will run under a different secure desktop. First of all, this implies that your application cannot raise dialogs and that it cannot interact with other GUI entities (by using SendKeys, for example). By default, Inetinfo.exe is not allowed to interact with the desktop. The different user context also inhibits your component to access certain resources -- mainly the HKEY_CURRENT_USER portion of the registry.
A common mistake is to reference keys under HKEY_CURRENT_USER. For example, Visual Basic's GetSetting and SaveSetting functions fail under ASP because they reference keys beneath the HKEY_CURRENT_USER hive. The following KB discusses this point:
Printer, MAPI information, and network shares are typically "lost" when a component is called from ASP rather than from a desktop client.
See the following KB articles for more information:
There are several points about security to consider:
A detailed explanation of security is beyond the scope of this article. However, given the complexity of the topic, the following articles are a great place to begin understanding issues from the perspective of ASP components:
The following article provides an excellent overview of how security works from IIS:
Use Server.CreateObject. If you're using MTS/COM+ library packages, use Server.CreateObject to avoid blocking of threads.
CreateObject equates to calling CoCreateInstance by the scripting engine. If you use CreateObject instead of Server.CreateObject, the following things occur:
Server.CreateObject equates to GetObjectContext.CreateInstance. This means ASP is aware of the object and knows its threading model. In addition, by calling Server.CreateObject, your component will be in the same transaction as your ASP page if your ASP page is transactional. (Just note that a transactional page may imply an avoidable coupling of business rules and the presentation layer.)
When an object is behind a firewall, you may need to call CreateObject. See Q193230 PRB: Server.CreateObject Fails when Object is Behind Firewall for more information.
While CreateObject is faster than Server.CreateObject under IIS 4.0, the performance is the same under IIS 5.0. Also, if you're using MTS/COM+ library packages/applications, Server.CreateObject prevents blocking of threads.
Declare Out parameters as Variant. In Visual Basic terms, this means By Reference parameters should be Variant. Parameters passed By Value (In parameters) are not restricted to Variant, but must be compatible with Variant.
Scripting clients speak Variant. COM servers can speak specific data types. When you pass specific data types By Value to the COM server, the COM server can receive them without a problem. But unless it is a Variant, a By Reference argument will fail to be "sent back" to your ASP script.
One of the most common errors is a Type Mismatch. This is generally because a variable is passed By Reference to a COM object as something other than a Variant. The usual workaround is to pass the parameters By Value or to change the parameter to a Variant.
If you will be distributing your components across machines or running them out-of-process, you may see significant performance gains by passing parameters By Value. Passing By Reference would create more marshalling overhead across processes or machines, because data has to be sent back and forth. It is also a matter of correctness and efficiency that you pass parameters By Value when you do not actually need to pass parameters By Reference. Note that by default, Visual Basic passes parameters By Reference.
The following KB articles discuss passing parameters to COM objects from ASP:
Avoid calling components that wait on other components to return events.
Component methods should return execution to ASP as soon as possible. Consider using MSMQ or COM+ Queued Components to provide asynchronous calls -- or when the work to do is long running and doesn't have to happen online.
Rather than having ASP wait for a long-running process to complete, dispatch the work item asynchronously. You will then return a response to the client from ASP. Once the work item is complete, you can notify the client by e-mail or some other means (see below).
ASP is not designed to handle events. Return responses as quickly as possible to your HTTP requests for optimal server performance.
Looping on the server checking for a status flag is not a server-friendly way to provide browser notifications.
A common reason developers look to events is to provide browser notification of work that is processed on the server. While you can develop an elaborate browser notification system, such as opening another port on the server through sockets, many developers can accomplish what they need through the following techniques:
The following KB articles provides discuss these issues:
With IIS 4.0 and later, use ObjectContext to access ASP built-in objects (i.e., Response, Request, Server, and so forth). Avoid using the ScriptingContext object, OnStartPage, and OnEndPage whenever possible.
OnStartPage, OnEndPage, and the ScriptingContext object are provided for legacy support.
The ATL Wizard uses OnStartPage and OnEndPage, if you insert an ASP object.
You get the ObjectContext with your Both- or Apartment-model components without registering them with MTS/COM+. For local ActiveX EXEs, you can't use ObjectContext, so you need to use OnStartPage/OnEndPage. To use context for Free-threaded and Single-threaded components, you need to register those components with MTS/COM+. If you don't, you'll need to use OnStartPage.
Your error handler should expect the unexpected. Capture errors in every portion of your application, and log them as fully as possible. Good logs are invaluable to tracing, isolation, and troubleshooting in anomalous and maybe sporadic situations. These logs may be implemented as text files or by writing to the NT Event Log. In most cases, "bubbling" the errors while adding information is an effective way to notify the caller that something went wrong. Bubbling provides the caller freedom to react in specific ways to specific problems.
When you record your errors, it is important to provide as much useful information as possible. Consider including the following:
From our experience, good error handling and logging are the most effective ways to isolate and diagnose problems at run time.
Remember the ASP 0115 error? Hopefully, you're not struggling with one. If you are, I suggest checking out Troubleshooting with the IIS Exception Monitor.
The ASP 0115 error was not always within the developer's control -- but more often than not, error handling could have prevented many occurrences as well as help resolve them when they did occur.
In a nutshell, the biggest trap is either skipping error handling all together, or neglecting to include useful diagnostic information.
In COM, it is poor practice to propagate exceptions across component boundaries. Capture exceptions -- but return HResults to communicate the failure to the caller.
The following articles provide practical samples of effective error handling:
Avoid Global variables in your components. In Visual Basic terms, this means do not have Public or Global variables in standard .BAS modules.
Global variables are not really global. Each thread will have its own copy. If methods happen to execute on the same thread, they will see the same variables; otherwise, they will be accessing different copies of them. This means that you may write a value to a global variable (while on thread A), but another user (who is executing on thread B) will never see the new value.
This stems from the fact that Visual Basic uses Thread-local Storage (TLS) internally to reference global variables. This means each thread has its own copy of Public variables, and the global data is not really "global," since there are multiple copies of it. It also means that users who happen to run on the same thread will have access to the same variables regardless of whether they expect it.
Using Public variables in a standard .BAS module can lead to corrupted data when the different threads service different user requests that expect the same data again.
The following articles by Matt Curland in the June 99 edition of Visual Basic Programmer's Journal are must-reads:
Additionally, the following article by Daniel Appleman provides a nice overview of how multithreading works in Visual Basic: A Thread to Visual Basic
Component distribution implies performance, scalability, and security issues. Different distributions of the same components may yield configurations that are higher performance, or more scalable, or more manageable.
The following guidelines have helped improve performance and scalability when distributing components across multiple machines:
Needless to say, there are exceptions. But these are good starting guidelines.
Distributing components across machines allows your application to keep up with your scalability needs. Following the guidelines above can help you achieve your application's performance and scalability goals.
Objects referencing ASP built-in objects will communicate a lot with your Web server, and since they are part of the presentation layer, they belong there.
Database or extremely data-intensive logic probably belongs in stored procedures in your database. Placing your data access components on your application server, instead of on the database, avoids expensive DCOM calls between your components. Instead, your data access component communicates to your database more efficiently using SQL Server communication, such as TCP/IP.
Here is a handful of traps you can try to avoid:
Successfully calling remote MTS components from IIS can also be tricky. A simple, yet effective solution to both improve performance and simplify security issues is to call an intermediate MTS/COM+ package/application. Early binding will reduce your network hops, improving performance. If you use a Server package/application, you can set an identity for the package/application to run as. This technique is discussed in the KB article Q159311 Instantiating Remote Components in Microsoft Transaction Server and Internet Information Server.
If you've decoupled your services and, in particular, kept ASP out of your business components, your distribution should be fairly flexible. You should be able to throw more boxes at your problem and disperse your components as needed to handle scalability and performance issues that come along. How do you know? Test. How do you test? Here are basic guidelines:
For more information on stress testing your application, read I Can't Stress It Enough -- Load Test Your ASP Application.
As you can see, there are a few things you need to bear in mind throughout your development cycles. Factor in as many of the application guidelines presented here up front, because they will help you prevent costly mistakes down the line. By following the few guidelines outlined in this article throughout your development cycles, you can not only prevent a few do-overs, but actually deliver scalable, reliable, high-performance ASP component-based solutions.
J.D. Meier was born and reared on the U.S. East Coast. Since heeding Horace Greeley's advice, he has worked as a Developer Support engineer specializing in server-side components and Windows DNA applications involving MTS and ASP technology.
|Back to top|
|Did you find this material useful? Gripes? Compliments? Suggestions for other articles? Write us!|