In this section you ll create an ISAPI service. The service will be very simple so you can focus on the service infrastructure and not concern yourself as much with the implementation details of a complex service. To fulfill these requirements, you ll create a simple page hit counter service. This service will simply count the number of requests the site has received over its lifetime and provide this number on demand.
Note | Normally for a page hit counter service, you re better off using a facility such as Perfmon, because it provides a lot of benefits in terms of accessibility, reliability, and logging. See Chapter 9 for more information on how to take advantage of this new feature. |
Run through the ATL Server Project Wizard and make sure that all the options under the Server Options tab are unchecked (the default). This will ensure that you ve disabled all the default ISAPI services that come with ATL Server.
You can open the .cpp file in your ISAPI project. Notice how little code it currently contains. All it basically does is expose the entry points required by IIS and delegate all the functionality to an instantiation of CIsapiExtension .
Add a new header file to this project. You ll put the code required for your counter there. Create a CSimpleCounter class that provides the functionality that you need. It should look something like Listing 4-1.
__interface ATL_NO_VTABLE __declspec(uuid("b3902ede-b647-4616-8ca4-1dcb98a015b0")) ISimpleCounter : public IUnknown { STDMETHOD (Set) (); STDMETHOD (Get) (long*); }; class CSimpleCounter : public ISimpleCounter, public CComObjectRootEx<CComGlobalsThreadModel> { BEGIN_COM_MAP(CSimpleCounter) COM_INTERFACE_ENTRY(ISimpleCounter) END_COM_MAP() protected: long m_Counter; public: void Init(void) { m_Counter = 0; } STDMETHOD (Set)(void) { InterlockedIncrement(&m_Counter); return S_OK; } STDMETHOD (Get)(long *input) { *input = m_Counter; return S_OK; } };
You can see that your class does everything you need: It initializes the counter to 0 when Init is called and it exposes a Get function and a Set function that allow you to either get the current value of the counter or increment the counter, respectively.
Now that you have the counter itself, you need to expose this counter as a service from your ISAPI DLL. We explain how to do this in the next section.
Now you need to be able to expose the functionality provided by CSimpleCounter to the application DLL. The application DLL needs to be able to
Check if the service exists. This allows you to decouple the application DLL and ISAPI DLL somewhat, as opposed to tying the application to a specific instantiation of the counter.
Bind to the service if it exists. Obviously, if the service does exist, you want to get access to it.
Finally, you can actually use the service.
In order to do this, you need to create your own extension that extends the base CIsapiExtension by providing this new hit counter service. If you had selected any of the options on the Server Options tab of the Project Wizard, they would have already created this extension of CIsapiExtension for you (in order to expose their own functionality).
You ll start by modifying the typedef in the ISAPI .cpp. Currently, ExtensionType is a CIsapiExtension . In this case, you want to modify ExtensionType so that it s a CMyIsapiExtension . Go ahead and make the change so that
typedef CIsapiExtension<> ExtensionType;
becomes
typedef MyCIsapiExtension<> ExtensionType;
Now you ll create a MyCIsapiExtension header file where you ll define what MyCIsapiExtension really is. CIsapiExtension is templated on a thread pool class, a stat class, an error text provider, and worker traits. You need to template your MyCIsapiExtension on these same parameters so that you can pass them through. Thus, your definition of MyCIsapiExtension will look something like this:
template <class ThreadPoolClass=CThreadPool<CIsapiWorker>, class CStatClass=CNoRequestStats, class HttpUserErrorTextProvider=CDefaultErrorProvider, class WorkerThreadTraits=DefaultThreadTraits > class MyCIsapiExtension : public CIsapiExtension<ThreadPoolClass, CStatClass, HttpUserErrorTextProvider, WorkerThreadTraits> { };
Now all you need to do to complete the service is implement the required methods in this class, as shown in Listing 4-2. This includes the GetExtensionVersion method that s called when the ISAPI is first loaded. It also includes an implementation for QueryService so that you can connect to your ISAPI service.
#include "SimpleCounter.h" template <class ThreadPoolClass=CThreadPool<CIsapiWorker>, class CStatClass=CNoRequestStats, class HttpUserErrorTextProvider=CDefaultErrorProvider, class WorkerThreadTraits=DefaultThreadTraits > class CMyIsapiExtension : public CIsapiExtension< ThreadPoolClass, CStatClass, HttpUserErrorTextProvider, WorkerThreadTraits> { typedef CIsapiExtension<ThreadPoolClass, CStatClass, HttpUserErrorTextProvider, WorkerThreadTraits> baseISAPI; CComObjectGlobal<CSimpleCounter> g_Counter; public: BOOL GetExtensionVersion(HSE_VERSION_INFO* pVer) { if (!baseISAPI::GetExtensionVersion(pVer)) { return FALSE; } if (GetCriticalIsapiError() != 0) { return TRUE; } g_Counter.Init(); return TRUE; } HRESULT STDMETHODCALLTYPE QueryService(REFGUID guidService, REFIID riid, void** ppvObject) { if (InlineIsEqualGUID(guidService, __uuidof(ISimpleCounter))) return g_Counter.QueryInterface(riid, ppvObject); return baseISAPI::QueryService(guidService, riid, ppvObject); } };
That s all there is to it ”you have now exposed your counter as an ISAPI service. In the next section, you ll actually use your service from your application DLL by displaying a hit counter on your Web page.
Start by opening up the main header file in your application DLL. It should contain the implementation of your wizard-generated Hello World request handler. Then #include CMyIsapiExtension.h, which is where your service resides. Take a look at the code in Listing 4-3.
#include "..\SimpleServiceIsapi\CMyIsapiExtension.h" [ request_handler("Default") ] class CSimpleServiceHandler { private: CComPtr<ISimpleCounter> m_SimpleCounter; long counter; public: HTTP_CODE ValidateAndExchange() { HRESULT hr = m_spServiceProvider->QueryService( __uuidof(ISimpleCounter), &m_SimpleCounter ); if (FAILED(hr)) { m_SimpleCounter = 0; } m_SimpleCounter->Set(); m_HttpResponse.SetContentType("text/html"); return HTTP_SUCCESS; } protected: [ tag_name(name="Hello") ] HTTP_CODE OnHello(void) { m_HttpResponse << "Hello World!"; return HTTP_SUCCESS; } [ tag_name(name="Counter") ] HTTP_CODE OnCounter(void) { m_SimpleCounter->Get(&counter); m_HttpResponse << counter; return HTTP_SUCCESS; } }; // class CSimpleServiceHandler
You start by creating a CComPtr to your counter and a long to store the current hit value. You QueryService your ISAPI service in your ValidateAndExchange method and increment the counter. You then create the OnCounter method, which gets the value of the hit counter and outputs it.
Now all you need to do is modify the SRF file to include your new functionality. The following code will do the trick:
<html> {{handler SimpleService.dll/Default}} <body> This is a test: {{Hello}}<br> This page has been hit {{Counter}} times!<br> </body> </html>
Now build and run the code. Keep refreshing the page and watch as your hit counter goes through the roof! Once you ve read Chapter 6 you should try adding a button to this page that lets you reset the counter.
Note | You ll have to implement the functionality to expose this from the service. Currently the Init method isn t exposed. |