Building a Custom Delivery Protocol


If you want to support delivery methods other than email, you need a custom delivery protocol. This section describes how to build a custom delivery protocol and integrate it into your SQL-NS application.

As mentioned in the early part of this chapter, several software vendors produce reusable custom delivery protocols that you can leverage. If you obtain a custom delivery protocol from one of these vendors, you can use the configuration techniques described in the later parts of this section to deploy it in your SQL-NS instance and use it in your applications.

The Custom Delivery Protocol Interface

Like other custom components in SQL-NS, custom delivery protocols are written as classes that implement a specific SQL-NS interface. The interface for delivery protocols is called IDeliveryProtocol and is shown in Listing 10.6.

Listing 10.6. The IDeliveryProtocol Interface

 public interface IDeliveryProtocol {     void Initialize(         StringDictionary channelArgs,         bool multicast,         NotificationStatusCallback nsc);     void DeliverNotification(         NotificationHeaders[] headersList,         string body);     void Flush();     void Close(); } 

The interface defines the following four methods:

  • Initialize() Sets up the delivery protocol

  • DeliverNotification() Delivers a single notification (or, in the case of multicast, several notifications in a single multicast group)

  • Flush() Completes or aborts any pending delivery operations in preparation for shutdown

  • Close() Cleans up any resources used by the delivery protocol

Figure 10.6 shows the operation of the IDeliveryProtocol methods graphically.

Figure 10.6. Operation of custom delivery protocol methods.


When the distributor starts processing a work item, it calls the delivery protocol's Initialize() method, passing it the following items:

  • The arguments specified in the declaration of the delivery channel to which the notifications in the work item are targeted

  • A flag indicating whether multicast delivery will be used

  • A delegate to a callback function that the protocol can use to report the delivery status of notifications it handles

In the Initialize() method, a protocol typically saves the multicast flag and the callback delegate in private member variables and then processes the delivery channel arguments. The delivery channel arguments usually contain the information needed by the protocol to connect to the delivery endpoint that the channel represents. When implementing a custom delivery protocol, you decide the arguments that it requires.

The distributor calls the DeliverNotification() method for each formatted notification that needs to be delivered. It passes two arguments to DeliverNotification():

  • An array of "notification headers" that specify information about each intended recipient of the notification

  • A string containing the formatted notification body

To understand the contents of the notification headers array, think about how notifications are represented in the notifications table. The table contains a row for each generated notification that specifies the notification data, as well as information about the intended recipient (such as the subscriber ID and device name). You can think of each row as representing a separate delivery instruction saying, "send this notification data to this subscriber on this device." When multicast delivery is used, the distributor groups notifications that have common notification data and formats that data just once. But it still has to carry out the delivery instructions represented by each individual notification row.

The notification headers array passed to the DeliverNotification() method essentially contains all the individual delivery instructions that apply to the given formatted notification message. The headers array contains a NotificationHeaders object for every notification row in a multicast group. If multicast delivery is not used, the headers array always contains just one element.

Each NotificationHeaders object in the array contains the following information:

  • A NotificationState object that identifies a notification row. (Think of this as an opaque identifier that the distributor uses to keep track of which row the NotificationHeaders object represents.)

  • A dictionary of protocol field values obtained by applying the protocol field expressions to the data in the notification row.

  • A RecipientInfo object that provides the subscriber ID and DeviceAddress property of the subscriber device referenced in the notification row.

Using the information passed in, the DeliverNotification() method attempts to deliver the notification message. Notice that the DeliverNotification() method does not return a value indicating whether the delivery succeeded or failed. Instead, delivery status is reported by means of the NotificationStatusCallback delegate (originally passed to the Initialize() method). This delegate is defined with the following signature:

 public delegate void NotificationStatusCallback(params NotificationStatus[] status); 


When its delivery attempts complete, the delivery protocol invokes the delegate, passing in the delivery status of one or more notifications. The delegate takes a variable number of NotificationStatus objects, each of which represents the delivery status of one notification. The delivery protocol must call the notification status callback delegate with one NotificationStatus object for each NotificationHeaders object it received in DeliverNotification(). Each NotificationStatus object has the following properties (which the delivery protocol sets appropriately):

  • NotificationState The NotificationState object that identifies the notification for which status is being reported (obtained from the NotificationHeaders object passed to the DeliverNotification() method).

  • NotificationText Specifies the text of the final notification message that the delivery protocol sent out, including any headers appended to the notification body. (This property is optional and is only useful for debugging purposes.)

  • StatusInfo Specifies additional information about the delivery status, such as an error message in the case of failure. (Again, this property is optional and is used only for debugging and diagnostic purposes.)

  • Succeeded A Boolean flag indicating whether the delivery succeeded.

  • TimeStamp A time stamp that indicates when the delivery occurred.

The delivery protocol can report the status of notifications one at a time or in sets. There is no requirement that the status of notifications be reported in order, or that the status of all the notifications passed to one DeliverNotification() invocation be reported together.

Caution

Although there are no restrictions on the grouping or ordering of notification status reports through the notification status callback, the delivery protocol must eventually report the status of every notification it handles. In other words, the total number of NotificationStatus objects passed in all the invocations of the callback must equal the total number of NotificationHeaders objects passed to the delivery protocol in all the invocations of DeliverNotification().


The use of the callback mechanism for reporting notification status allows the delivery protocol to implement asynchronous delivery. Instead of performing the delivery operation immediately when DeliverNotification() is called, the delivery protocol can queue a delivery request and return from DeliverNotification quickly. It can then service the queued delivery requests asynchronously and report their delivery status through the call-back. Delivery protocols implemented asynchronously usually perform much better than their synchronous counterparts because they do not block the distributor while waiting for a delivery to complete. As soon as DeliverNotification() returns, the distributor can start processing the next notification in the distributor work item. This can be an advantage if the delivery operations involve network latency, as they often do.

The distributor calls the delivery protocol's Flush() method at the end of a distributor work item. In the Flush() method, the protocol should either complete or abort any pending notification delivery attempts and report their status through the notification status callback. After Flush() returns, there should be no more outstanding notification requests. It is illegal for the delivery protocol to call the notification status callback after Flush() returns.

The Flush() method is usually important only if the protocol implements asynchronous delivery. In these delivery protocols, the Flush() should ensure that asynchronous delivery requests either complete or are canceled. In synchronous delivery protocols, each delivery operation completes by the end of the DeliverNotification() method, so Flush() usually doesn't do anything.

After Flush() returns and the distributor performs the cleanup it needs to do to finish processing the work item, it calls the protocol's Close() method. Close() should clean up any resources held by the protocol during its operation.

The distributor may reuse an instance of a delivery protocol class across the processing of several distributor work items. However, the distributor makes the following guarantees:

1.

Initialize() will be called at the start of a work item and will not be called again until Flush() and Close() have been called.

2.

DeliverNotification() will be called zero or more times, depending on the number of notifications in the work item.

3.

Flush() will be called at least once after Initialize().

4.

Close() will be called after the final call to Flush(). After Close() returns, no other methods will be called on the delivery protocol until Initialize() is called again.

Note

If you need to debug a custom delivery protocol, you can either attach a debugger to the SQL-NS Windows service process, or you can start the service as a console application from within a debugger. The "Debugging a Custom Component" topic in the SQL-NS Books Online provides instructions for debugging custom components that run in the SQL-NS Windows service.


Implementing the Custom Delivery Protocol

In this section, we implement the code for a custom delivery protocol that uses Message Queuing to deliver notifications to a message queue. Open the code for this custom delivery protocol using the following instructions:

1.

Navigate to the C:\SQL-NS\Samples\MusicStore\CustomComponents\MQDeliveryProtocol directory.

2.

Open the solution file MQDeliveryProtocol.sln in Visual Studio.

The delivery protocol is implemented in the MQDeliveryProtocol.cs file. Listing 10.7 shows the code in this file.

Listing 10.7. Implementation of the MQDeliveryProtocol

 namespace MusicStoreCustomComponents {     /// <summary>     /// Class that represents notifications sent via     /// the MQ Delivery Protocol.     /// </summary>     public class MQNotification     {         ...         // Public accessors for properties.         public string MessageId         {             ...         }         public string SubscriberId         {             ...         }        public string TargetAddress        {            ...        }       public string Body       {            ...       }    }      /// <summary>      /// Implements a custom delivery protocol that      /// sends notifications over MSMQ.      /// </summary>      class MQDeliveryProtocol : IDeliveryProtocol      {          private string queueName;          private bool multicast;          private NotificationStatusCallback nsc;          private MessageQueue queue;          public void Initialize(              StringDictionary channelArgs,              bool multicast,              NotificationStatusCallback nsc)          {              // Read the queue name from the channel arguments.              if (channelArgs.ContainsKey("QueueName") &&                  channelArgs["QueueName"] != "")              {                  this.queueName = channelArgs["QueueName"];              }              else              {                  // QueueName is a required delivery channel argument,                  // so throw an exception if it isn't specified.                  throw new ArgumentException(                      "A required delivery channel argument was not specified.",                      "QueueName");              }              // Store the multicast parameter and notification status              // callback delegate.              this.multicast = multicast;              this.nsc = nsc;              // Establish a connection to the message queue.              if (MessageQueue.Exists(queueName))              {                  // The message queue exists, so just obtain a                  // reference to it.                  this.queue = new MessageQueue(this.queueName);              }              else              {                  // The message queue does not exist, so create it.                  this.queue = MessageQueue.Create(this.queueName);              }           }           public void DeliverNotification(               NotificationHeaders[] headersList,               string body)           {               int numNotifications = headersList.Length;               MQNotification[] notifications;               NotificationStatus[] statusObjects;               // Create arrays to hold the notification objects and               // status objects.               notifications = new MQNotification[numNotifications];               statusObjects = new NotificationStatus[numNotifications];               // For each header in the given list, create a notification               // object and a status object.               for (int i = 0; i < numNotifications; i++)               {                    NotificationHeaders headers = headersList[i];                    notifications[i] = new MQNotification(                        headers.ProtocolFields["MessageId"],                        headers.RecipientInfo.SubscriberId,                        headers.RecipientInfo.DeviceAddress,                        body);                    try                    {                        // Attempt the delivery.                        this.queue.Send(notifications[i]);                        // Since no exception was thrown, the delivery                        // status is a success.                        statusObjects[i] = new NotificationStatus(                            headers.NotificationState,                            true,                            "",                            body,                            (object)DateTime.UtcNow);                    }                    catch (Exception ex)                    {                         // The Send() operation threw an exception, so                         // record the notification status as failed.                         statusObjects[i] = new NotificationStatus(                             headers.NotificationState,                             false,                             ex.Message,                             body,                            (object)DateTime.UtcNow);                    }                }                // Report all status back to the distributor.                nsc(statusObjects);            }            public void Flush()            {                // Do nothing.            }            public void Close()            {                // Close the message queue.                this.queue.Close();            }      } } 

The first part of the source file defines a class, MQNotification, that represents the structure of the messages that will be delivered to the message queue. The delivery protocol creates an instance of this class for each notification it is asked to deliver and sends this instance to the message queue.

The MQNotification class defines properties for the various aspects of a notification delivered via the MQDeliveryProtocol, including message ID, subscriber ID, target address, and notification body. This is a delivery protocol made up purely for the purposes of illustration, and these fields were chosen somewhat arbitrarily as examples of the headers that typical delivery protocols might use. Notice that the message structure is not specific to the music store application. The delivery protocol described here could be used with any application that wants to deliver to a message queue.

The delivery protocol operations are implemented in the MQDeliveryProtocol class, which implements the IDeliveryProtocol interface. The Initialize() method saves the multicast flag and the notification status callback, and then looks in the channel arguments dictionary for an argument called QueueName. This is a required argument that tells the protocol the name of the queue to connect to. If the argument is not specified, Initialize() throws an exception. Because the queue name is not hard-coded, but instead obtained dynamically from a delivery channel argument, the MQDeliveryProtocol can be used with any message queue. You can create a delivery channel for the message queue you want to use, configure it to use the MQDeliveryProtocol, and specify the queue name through the QueueName argument. This is an example of how a delivery channel is used to represent a delivery endpoint (the specific message queue). The channel arguments tell the delivery protocol how to connect to that endpoint.

After reading the channel arguments, Initialize() creates a MessageQueue object to represent the message queue. If the queue already exists, the protocol just creates a reference to it. If the queue does not exist, the protocol actually creates it.

The DeliverNotification() method determines the number of notification headers passed in and creates two arrays with this number of elements. The first array is used to store the MQNotification objects that it sends to the message queue. The second array stores the NotificationStatus objects that it will pass to the notification status callback to report the status of each notification.

For each element in the headers array, the protocol creates an MQNotification object and sets the properties using the value of a protocol field, the recipient information, and the formatted notification body. In this case, the protocol uses a protocol field called MessageId that provides a unique identifier for the message. When we look at the protocol declaration in the ADF, you will see this protocol field defined.

After creating the MQNotification object, the protocol attempts to deliver the notification by calling the Send() method on the message queue object. If this method does not throw an exception, the code assumes that the delivery succeeded and creates a notification status object indicating success. If an exception is thrown, the code catches it and creates a notification status object indicating failure. The StatusInfo property of the status object is set to the value of the exception message.

Note

All the information returned by the delivery protocol through the notification status objects can be viewed in the notification distribution views, as described in the section, "The Notification Distribution Views" (p. 360).


After the delivery protocol attempts to deliver all the notifications, it reports their status using the notification status callback. It passes the entire array of NotificationStatus objects to the callback delegate.

Because this is a synchronous delivery protocol, the Flush() method has no work to do; all delivery attempts complete before DeliverNotification() returns. The Close() method closes the message queue object and then returns. This is the only operation required to clean up the delivery protocol state.

Declaring the Custom Delivery Protocol in the ICF

To use the custom delivery protocol, we need to declare it in the ICF. Listing 10.8 shows this declaration.

Listing 10.8. Declaration of the Custom Delivery Protocol

[View full width]

 <NotificationServicesInstance>   ...   <Protocols>     <Protocol>       <ProtocolName>MQProtocol</ProtocolName>       <ClassName>MusicStoreCustomComponents.MQDeliveryProtocol</ClassName>       <AssemblyName>%_InstanceBaseDirectoryPath_%\CustomComponents\MQDeliveryProtocol\bin \Debug\MQDeliveryProtocol.dll</AssemblyName>     </Protocol>   </Protocols>   <DeliveryChannels>     ...   </DeliveryChannels>   ... </NotificationServicesInstance> 

The declaration appears in a <Protocol> element, inside the <Protocols> element. It specifies a protocol name, which can then be used in delivery channel declarations and notification class declarations in the ADF to identify the custom delivery protocol. The declaration also specifies the class and assembly names that the distributor will use to load the custom delivery protocol at runtime. As with all other SQL-NS custom components, the class name must be provided in fully qualified form, with the namespace prefix, and the assembly name must include a full path. The assembly name given here refers to the delivery protocol assembly that will be built when we compile the delivery protocol source code, as described in the section, "Testing the Custom Delivery Protocol," (p. 386).

Creating a Delivery Channel for the Custom Delivery Protocol

After the custom delivery protocol has been declared, we can create a delivery channel that uses it, as shown in Listing 10.9.

Listing 10.9. Declaration of a Delivery Channel Using the Custom Delivery Protocol

 <NotificationServicesInstance>   ...   <Protocols>     <Protocol>       <ProtocolName>MQProtocol</ProtocolName>       ...     </Protocol>   </Protocols>   <DeliveryChannels>     <DeliveryChannel>       <DeliveryChannelName>FileChannel</DeliveryChannelName>       ...     </DeliveryChannel>     <DeliveryChannel>       <DeliveryChannelName>SMTPChannel</DeliveryChannelName>       ...     </DeliveryChannel>     <DeliveryChannel>       <DeliveryChannelName>MQChannel</DeliveryChannelName>       <ProtocolName>MQProtocol</ProtocolName>       <Arguments>         <Argument>           <Name>QueueName</Name>           <Value>.\Private$\NewSongNotificationQueue</Value>         </Argument>       </Arguments>     </DeliveryChannel>   </DeliveryChannels>   ... </NotificationServicesInstance> 

The delivery channel declaration is added below the other delivery channel declarations in the <DeliveryChannels> element in the ICF. It specifies a delivery channel name and references the protocol name declared in Listing 10.8. It also defines the one required delivery channel argument, the queue name.

Supporting the Custom Delivery Protocol in the Notification Class

Finally, to deliver NewSong notifications with the custom delivery protocol, we need to add a <Protocol> declaration for it to the NewSong notification class, as shown in Listing 10.10.

Listing 10.10. Declaring Support for the Custom Delivery Protocol in the NewSong Notification Class

 <Application>   ...   <NotificationClasses>     <NotificationClass>       <NotificationClassName>NewSong</NotificationClassName>       ...       <Protocols>         <Protocol>           <ProtocolName>File</ProtocolName>         </Protocol>         <Protocol>           <ProtocolName>SMTP</ProtocolName>           ...         </Protocol>         <Protocol>           <ProtocolName>MQProtocol</ProtocolName>           <Fields>             <Field>               <FieldName>MessageId</FieldName>               <SqlExpression>                 NEWID()               </SqlExpression>             </Field>           </Fields>           <ProtocolExecutionSettings>             <RetrySchedule>               <RetryDelay>P0DT00H05M00S</RetryDelay>               <RetryDelay>P0DT00H15M00S</RetryDelay>             </RetrySchedule>           </ProtocolExecutionSettings>         </Protocol>       </Protocols>       <ExpirationAge>PT06H00M00S</ExpirationAge>     </NotificationClass>   </NotificationClasses>   ... </Application> 

The new <Protocol> declaration appears after the other protocol declarations, which remain valid. This means that notifications of this notification class can be delivered over multiple delivery protocols.

The protocol declaration references the protocol name and declares the one protocol field that the protocol requires. This protocol field provides a message ID by calling the NEWID() SQL function to generate a new identifier. You saw the value of this protocol field accessed in the implementation of the delivery protocol's DeliverNotification() method, in Listing 10.7.

For illustration purposes, Listing 10.10 also shows how a protocol declaration can specify a retry schedule for failed notifications. In this case, the retry schedule specifies two delay intervals: 5 minutes and 15 minutes. If one or more delivery failures occur in the processing of a work item when using the MQProtocol, the distributor will wait 5 minutes and then process the work item again, attempting to deliver those notifications that were not successfully delivered before. If there are still failures, the distributor will try once more after waiting 15 minutes. Because the retry schedule is specified within the <Protocol> declaration, you can specify different retry patterns for different protocols.

Note

The delays in a retry schedule are relative to one another, not relative to the first processing attempt on the work item. In the example shown in Listing 10.10, the first retry attempt will happen 5 minutes after the original attempt, and the second retry attempt will happen 15 minutes after the first retry attempt (20 minutes after the original attempt).


Also for illustration, Listing 10.10 shows how the <ExpirationAge> element can be used to specify an expiration age for notifications of the notification class. In this example, the expiration age is set to 6 hours. Undelivered notifications older than 6 hours will never be delivered.

Testing the Custom Delivery Protocol

Use the following steps to get the music store instance running with the custom delivery protocol:

1.

Build the MQDeliveryProtocol Visual Studio solution. This produces the custom delivery protocol assembly.

2.

Add the protocol and delivery channel declarations to your ICF. The ICF code in Listings 10.8 and 10.9 is available in the supplemental ICF, InstanceConfiguration-3.xml in the C:\SQL-NS\Chapters\10\SupplementaryFiles directory. Add this code to the main ICF (C:\SQL-NS\Samples\MusicStore\InstanceConfiguration.xml) either by typing it manually or by copying over the supplemental ICF.

3.

Add the declaration for the MQProtocol to the ADF. The ADF code in Listing 10.10 is available in the C:\SQL-NS\Chapters\10\SupplementaryFiles\ApplicationDefinition-15.xml supplemental ADF, which you can copy over the main ADF, C:\SQL-NS\Samples\MusicStore\SongAlerts\ApplicationDefinition.xml.

4.

Update your instance using the same steps you used before. (For reference, see steps 27 in the "Testing the FileSystemWatcherProvider in the Music Store Application" section, p. 251, in Chapter 8.) Make certain that you use the update_with_argument_key.cmd script, not the update.cmd script.

Caution

You must add both the new ICF code and the new ADF code (as described in steps 2 and 3) before updating the instance.

5.

After the update completes, start the SQL-NS service.

To test the new delivery protocol, you need subscriber devices configured to use the delivery channel that references it (MQChannel). When you completed the instructions in the "Adding Subscriber Devices for New Delivery Channels" section (p. 365) earlier, you added devices that use only the SMTP delivery channel. Now that you've added the MQChannel to the instance, you must run the AddMQSubscriberDevicesAndSubscriptions.sql script (located in C:\SQL-NS\Chapters\10\Scripts). This will add a set of subscriber devices that specify MQChannel as their delivery channel name (and a corresponding set of subscriptions that reference these subscriber devices). Complete the following instructions:

1.

Open AddMQSubscriberDevicesAndSubscriptions.sql in Management Studio.

2.

Take a look at the data in the records it adds. Notice that the new subscriber devices are of the TextMessageDevice type and specify MQChannel as the delivery channel name.

3.

Execute the script.

For the purposes of seeing notifications delivered by the custom delivery protocol, we'll use a simple client program that listens for messages on the message queue. When it receives a message, this program displays the message contents on the console. We'll start this program, submit some events into the music store application that match the subscriptions referencing the new subscriber devices, and then wait to see the listener program receive and display the resulting notifications.

Before you can test the custom delivery protocol, you need to build the client listener program using the following instructions:

1.

Navigate to the C:\SQL-NS\Samples\MusicStore\CustomComponents\Test\MQListener directory.

2.

Open the solution file MQListener.sln in Visual Studio.

3.

Build the solution. This creates the executable for the listener program, MQListener.exe, in the bin\Debug subdirectory.

Use the following instructions to exercise the custom delivery protocol and see the results:

1.

Make sure that the SQL-NS Windows service for the music store instance is running and then start the client listener program by typing the following command:

 C:\SQL-NS\Samples\MusicStore\CustomComponents\Test\MQListener\bin\Debug\MQListener.exe 


2.

Run the AddSongs program and connect to your music store database.

3.

Enter the following song data:
Album Title: Miles Davis and Horns
Artist Name: Miles Davis
Genre: Jazz
Song 1: Willie the Wailer
Song 2: Morpheus

4.

Check the Submit Events for Songs Added box and click the Add to Database button.

5.

About 30 seconds after you submit the events, check the command prompt window running the listener program. When the notifications arrive on the message queue, the listener program will print out their contents.

The messages printed by the notification listener show the header values created by the delivery protocol, as well as the notification bodies. Because the subscriber devices associated with the custom delivery protocol's delivery channel are of the TextMessageDevice type, the content formatter produces smaller notification bodies than the ones in the email notifications.

Note

To quit this MQListener program, press Ctrl+C in the command prompt window in which it is running.


Implementing Custom Delivery Protocols That Use HTTP

Many modern network protocols are based on the Hypertext Transfer Protocol (HTTP). These protocols all use HTTP as an underlying message transport, but implement different message formats and semantics.

Because network protocols based on HTTP are so prevalent, SQL-NS provides extra support for them, beyond the basic IDeliveryProtocol extension mechanism. Using the SQL-NS HTTP extension framework, you can implement custom delivery protocols based on HTTP, without having to reimplement the basic HTTP message exchange. Instead, you can use an HTTP implementation that SQL-NS provides and customize it for the particular HTTP-based protocol you want to support. To be clear, SQL-NS does not provide a built-in HTTP delivery protocol, but rather a partial implementation of a custom delivery protocol class that you can customize by writing a small amount of code (much less code than if you were reimplementing the fundamental HTTP message exchange).

To use this mechanism, you declare a custom delivery protocol (in a <Protocol> element of the ICF) that specifies HttpExtension as the class name. HttpExtension is the name of a SQL-NS built-in class that implements IDeliveryProtocol but calls out to custom code that you provide to build the HTTP message payload and parse responses from the delivery endpoint. You use HttpExtension in place of a custom delivery protocol class that you write on your own. You do not need to specify an assembly name in the protocol declaration when specifying HttpExtension as the class name.

You then declare a delivery channel in the ICF that references the custom delivery protocol name you specified in the <Protocol> declaration. In the delivery channel arguments, you specify the class name and assembly name for a protocol provider class that implements the IHttpProtocolProvider interface. The protocol provider is a class that you write. The HttpExtension class calls the IHttpProtocolProvider methods on the provider class to form the HTTP payload for messages that it sends out and to process HTTP responses received. Implementing IHttpProtocolProvider is much simpler than implementing an HTTP-based protocol from scratch using IDeliveryProtocol.

The HTTP implementation provided by HttpExtension is sophisticated: It uses multiple threads for asynchronous delivery and has been carefully optimized for maximum performance. Writing an equivalent HTTP implementation on your own would present a significant development challenge.

For more information on how to use HttpExtension and IHttpProtocolProvider, see the SQL-NS Books Online.





Microsoft SQL Server 2005 Notification Services
Microsoft SQL Server 2005 Notification Services
ISBN: 0672327791
EAN: 2147483647
Year: 2006
Pages: 166
Authors: Shyam Pather

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net