Creating an Active Server Component in Visual Studio 97

Wayne Berry

June 1997

Wayne Berry is the editor for 15 Seconds (http://www.15seconds.com/), a twice monthly online publication written for Internet developers and content providers about IIS, ISAPI, and Active Server.

Introduction

This article provides a simple example of how to create an Active Server component in Microsoft® Visual Studio™ 97 using the Active Template Library (ATL) version 2.0 and Microsoft Visual C++® version 5.0. The Active Server component works with Active Server Pages (ASP) as an extension of Active Server. When placed within an ASP page, the component we are going to create will get the user's cookie, or assign a cookie if the user doesn't have one.

If you are still using Microsoft Visual C++ version 4.2, we recommend that you upgrade to Microsoft Visual Studio 97, which makes COM objects much easier to program.

If you don't plan to use ASP technologies, you may want to consider using a simple object, instead. Please see the discussion at the end of this article.

Creating an Active Server component consists of five steps:

  1. Creating an ATL project

  2. Adding an Active Server component

  3. Creating a method

  4. Making it work

  5. Adding the component to an ASP page

If you have already installed Visual Studio 97, you can get started right away.

Step 1. Creating an ATL Project

The first step in building an Active Server component is to create an ATL project. We will create the ATL project using the ATL COM AppWizard. The code produced by the wizard is nothing more than a DLL shell, with the ability to register the objects that will be defined later, to create those objects through a class factory, and to build a DllMain that supports the DLL shell. The shell by itself is not an Active Server component. The shell is a holder for many Active Server components. These components share the same code that is available in the shell and can share functionality between them because they exist in the same DLL. To create an ATL project in Visual Studio 97:

  1. From the File menu, choose New.

  2. Click the Project tab.

  3. From the list of projects, choose ATL COM AppWizard.

  4. For the project name, type SMUM. It is important to note that this is the name of the project, not the name of the object. We like to give the project the same name as that of the company under which the project will be licensed. In this example, "SMUM" stands for "Sign Me Up Marketing." We also prefer to use the project name as the first part of the object's common name. For instance, the Prog ID of the object in this example will be SMUM.Example. We prefer not to give the project the same name as the object because there may be more than one object within a project/shell. Also, the ATL wizard will have problems with filenames later if the object and the project have the same name.

    Figure 1. New project

  5. Click OK. The ATL COM AppWizard - Step 1 of 1 dialog box will appear (Figure 2).

  6. Check Allow merging of proxy/stub code.

    Figure 2. ATL COM AppWizard

  7. Click Finish.

  8. Click OK.

Files affected:

Step 2. Adding an Active Server Component

The second step is to add an Active Server component to the shell. This is done using the ATL Object Wizard.

  1. From the Visual Studio Insert menu, click New ATL Object. The ATL Object Wizard dialog box will appear (Figure 3).

  2. Click Objects from the left-hand list box.

  3. In the right-hand list box, click ActiveX Server Component.

    Figure 3. ATL Object Wizard

  4. Click Next. The ATL Wizard Object Properties dialog box will appear.

  5. For Short Name, type the name of the component. In this example, we will call the component "Example". Notice that while you type, the wizard automatically fills in the other properties.

  6. Type the object's Prog ID by highlighting the first part of the Prog ID and replacing it with "SMUM" (in step one, I mentioned that the example object Prog ID would be "SMUM.Example"). Be careful not to highlight the period that separates the two parts of the Prog ID. The wizard will not allow you to type a period in this edit box. If you delete the period, you will have to start the ATL Object Wizard again.

  7. Modify the Type to read "Sign Me Up Marketing Example Component", so that when you test the component from the ActiveX Control Test Container, you will be able to find it quickly. The ActiveX Control Test Container is a tool that comes with the Visual C++ installation of Visual Studio 97. The test container loads COM objects and allows you to test them by their method and properties.

    Figure 4. ATL Wizard Object Properties

  8. Click OK to create the object.

The ATL Object Wizard creates these files:

The wizard also adds code describing the interface of the Example object to the project's SMUM.idl.

When the ATL Object Wizard creates an Active Server component, it adds by default the member variables that interface with the Application, Request, Response, Server, and Session objects. 

Step 3. Creating a Method

Now, we will implement a method to get and set a cookie. First, we need to add a method called GetCookie to the Example interface.

  1. Go to the Project window of the SMUM project.

  2. Click the Class View tab.

  3. Expand the CExample class until you can see the IExample interface.

  4. Right-click IExample, and choose Add Method (Figure 5).

    Figure 5. Adding a method in the Project view

  5. The Add Method to Interface dialog box should appear (Figure 6).

  6. For Method Name, type "GetCookie".

  7. For Parameters, type "[out,retval] BSTR *pVal".

    Figure 6. Adding a method to the interface

  8. Click OK.

By using Visual Studio 97 to add a method in the Project view, you automatically insert the interface description into the *.idl file, and supporting code for the class is generated. 

Step 4. Making It Work

Now we are going to add the functionality to the GetCookie method created in Step 3. This method gets the current cookie; if not available, it creates a cookie and returns its value.

To quickly find the section of code where Visual Studio added the method, expand the project in the Project window, expand the IExample interface, and double-click on the GetCookie method. Add the following code:

STDMETHODIMP CExample::GetCookie(BSTR * pVal)
{
   GUID            guid;
   TCHAR           lpszCookie[25];
   VARIANT            varOptional;
   HRESULT            hr;
   // Check the Pointer. 
   if (pVal==NULL)
      return E_POINTER; 
   // Optimistically set the Returning. 
   // HRESULT
   hr=S_OK;
   // Configure the Optional Variant.
   // This configuration follows
   // the documentation for the
   // Active Server Object Interfaces.
   V_ERROR(&varOptional) == DISP_E_PARAMNOTFOUND;
   // Check to make sure all the interfaces
   // loaded correctly.  This is the part
   // that would fail if you called this
   // object from Visual Basic.
   if (m_bOnStartPageCalled)
   {
      // Allocate the Request Dictionary Objects
      // so that we can pass them to the Active
      // Server Object Interfaces.
      CComPtr<IRequestDictionary> piReadDict;
      CComPtr<IRequestDictionary> piWriteDict;
      // Pointer to the Write Cookie Interface
      IWriteCookie      *piWriteCookie;
   
      // From the Request Interface retrieve
      // a Dictionary of Cookies.
      hr=m_piRequest->get_Cookies(&piReadDict);
      if (FAILED(hr))
         return hr;
      // Allocate and set Variants to pass to 
      // the dictionary.
      CComVariant vtIn(_T("GUID"));
      CComVariant   vtOut;
      // Get the Cookie from the dictionary
      // named SMUMID and put it in vtOut.
      hr = piReadDict->get_Item(vtIn, &vtOut);
      if (FAILED(hr))
         return hr;
      // vtOut contains an IDispatch Pointer. 
      // To fetch the value for the cookie, you
      // must get the Default Value for the 
      // object stored in vtOut using VariantChangeType.  
      hr = VariantChangeType(&vtOut, &vtOut, 0, VT_BSTR);
      if (FAILED(hr))
         return hr;
      // If the Cookie isn't set then
      // the length of vtOut will be 0.
      if (!wcslen(V_BSTR(&vtOut)))
      {
         // There isn't a Cookie assigned
         // so we have to create one.
         // Use a GUID for the Unqiue User Cookie.
         CoCreateGuid(&guid);
         // Put the GUID into a String.
         wsprintf(lpszCookie,
            _T("%X%X%X%X%X%X%X%X%X%X%X"),
            guid.Data1,
            guid.Data2, guid.Data3,
            guid.Data4[0], guid.Data4[1],
            guid.Data4[2], guid.Data4[3],
            guid.Data4[4], guid.Data4[5],
            guid.Data4[6], guid.Data4[7]);
      
         // Create a Variant from the String.
         CComVariant   vtCookieValue(lpszCookie);
         // Get the writable Cookie Dictionary 
         // from the Response Object.  Notice
         // that it is the Response Object and
         // not the Request Object like above.
         hr=m_piResponse->get_Cookies(&piWriteDict);
         if (FAILED(hr))
            return (hr);
         // Get the Writable Interface for
         // the GUID cookie from the Cookie Dictionary.
         // Notice here that the GUID cookie
         // doesn't exist in the dictionary, but we
         // can still ask for its interface.
         hr = piWriteDict->get_Item(vtIn, &vtOut);
         if (FAILED(hr))
            return(hr);
         // Make it easy on ourselves, get and cast
         // the IDistpach value to the dual interface
         // that we want to call.
         piWriteCookie = (IWriteCookie*)(vtOut.ppdispVal);
         
         // Create the Cookie using the Name GUID and the Value of
         // the GUID that we generated.
         hr = piWriteCookie->put_Item(varOptional,
            V_BSTR(&vtCookieValue));
         if (FAILED(hr))
            return(hr);
         // Create an Expiration Date sometime in the future.
         // To keep the example simple, I am using my birthday
         // in 2010. 10/4/2010. Some other time, we will show
         // how to generate a VB DATE (double) without MFC.
         DATE   dtExpiration=40455.0;
         
         // This is required if you want the browser
         // to retain the cookie between sessions.
         hr = piWriteCookie->put_Expires(dtExpiration);
         if (FAILED(hr))
            return(hr);
         // Assign the out,retval.
         *pVal = ::SysAllocString(V_BSTR(&vtCookieValue)); 
         // A little help for Debugging.
         ATLTRACE (_T("Setting Cookie to: %s\n"),
            V_BSTR(&vtCookieValue));
      }
      else
      {
         // Assign the out,retval.
         *pVal = ::SysAllocString(V_BSTR(&vtOut));
         
         // A little help for Debugging.
         ATLTRACE (_T("Cookie was set to: %s\n"),
            V_BSTR(&vtOut));
      }
   }
   return hr;


} 

Once you have added the code, build the DLL. After compilation and linking, Visual Studio will register the DLL. If you have Microsoft Internet Information Server (IIS) installed, you can now use the component. 

Step 5. Adding the Component to an ASP Page

The last step is to add the component you just built to your ASP page.

Here is the right way to call the Active Server component. Notice that the cookie header precedes the <HTML> and <BODY> tags.

<%
' Here is where the OnStartPage method is called
Set Example=Server.CreateObject("SMUM.Example.1")
'Here is the method call to assign the cookie
Cookie = Example.GetCookie()
%>
<HTML>
<BODY>
<%=Cookie%>
</BODY>
</HTML>

Warning   When setting cookies, you must always assign the cookie header before the main body (<HTML> and <BODY> tags) of your page. For instance, this is the wrong way to send the code:

<HTML>
<BODY>
<%
Set Example=Server.CreateObject("SMUM.Example.1")
' The Cookie Header is set with this
' call if a cookie doesn't exist,
' which is too late since some of the body text has
' been sent already.
Cookie = Example.GetCookie()
%>
The Cookie is : <%=Cookie%>
</BODY>
</HTML>

Components or Simple Objects?

We just ran through the steps to create an Active Server component using the ATL Object Wizard, which inserted code and created files to support the COM object named Example. This is the same code that the wizard would have created if we had chosen to create a simple object.

Simple objects are sometimes more appropriate for large projects if you plan to use the same object over and over and if that object won't require access to Active Server component interfaces (Application, Request, Response, Server, and Session). When creating an Active Server component, the wizard adds code to access those interfaces. This is convenient only if the component is going to be used in an Active Server environment.

A Cookie Example

If you want to create a customer object based on a cookie, you can call an Active Server component from an ASP page. For example:

Set Creator=Server.CreateObject("SMUM.CustomerCreator.1")
Set Customer = Creator.CreateCustomer
Customer.EmailAddress = "webmaster@signmeup.com" 

The cookie call to the Request object is hidden from the ASP developer. When the CustomerCreator object performs the CreateCustomer method, the cookie value is retrieved from the Request interface within the CustomerCreator object. If the user does not currently have a cookie, the Creator object also sets the cookie in the Response interface. Finally, the e-mail address is written to a database, with the cookie serving as the primary key.

Consider this example in which the Creator object is a simple object instead of an Active Server component.

Set Creator=Server.CreateObject("SMUM.CustomerCreator.1")
Set Customer = Creator.CreateCustomer(Request.Cookies("SMUMID")) 
Response.Cookies("SMUMID") = Customer.Cookie
Customer.EmailAddress = "webmaster@signmeup.com" 

In this example, we have to pass the cookie in the Creator object and assign the cookie once we have created the Customer object. This technique makes more work for the programmer. If it is more work, why is it better to create a simple object? The answer lies within the scope of the project.

For instance, once all the e-mail addresses have been collected from the Web site, you may want to view each customer address from your desktop. With the simple object, you can create a Microsoft Visual Basic® application that views each e-mail address in turn. However, you cannot call the Active Server component from Visual Basic, because the instantiation of the Active Server interfaces would fail.

Simple objects can be called from Visual Basic and from ASP pages; Active Server components can be called only from ASP pages. For a little more work, you can get a lot more use out of your object.