Steven's profileDevelopmentalBlogLists Tools Help

Blog


    24 February

    Notification Services - Part 4 - Managed API

    In the first 3 parts of this series we discovered how to define a Notification Services application via the instance and application XML. In part 2 we defined an example scenario where users are automatically subscribed to receive notifications in their email whenever someone posts a comment against their blog post. In this part we will discover how to write code to access the event schemas we defined in part 3, and how to insert subscribers. For the sake of brevity, this post will define sections of code, not entire classes. Assume a class library project in C# that will be called by our main blog ASP.NET application. The methods defined in this post would be found in such a library.

    This post assumes that the Notification Services instance is installed on the same machine that will be running the code. This is important because to access a NS instance, you don't use connection strings as is typical in ADO.NET. Instead, it refers to registry entries which are automatically placed by NS when the instances is 'Registered' and 'Started'. For more information, see the MSDN link.

    Preperation
    Our class library project will need access to the Notification Services libraries in order to communicate with Notification Services. You can add this through references. The assembly file name is Microsoft.SqlServer.NotificationServices.dll. This assembly is installed in the GAC when Notification Services is installed (with SQL Server).

    At some stage we will also need to define what our Instance name and Application name is. We could put this in a constant but ideally it would be in web.config and accessible through a property somewhere. This code will assume a class called 'Settings' which has properties called 'InstanceName' and 'ApplicationName'. Eg. string application = Settings.ApplicationName;

    A lot of the time we will need access to an instance of a class called NSInstance. Through this class we can get access to our NS application, by getting an instance of NSApplication. The rest of this post will refer to instances of both of these classes, so we will define some code for them now:

    NSInstance BlogInstance = new NSInstance(Settings.InstanceName);
    NSApplication BlogApplication =
                 new NSApplication(BlogInstance, Settings.ApplicationName);

    In Notification Services an instance can have multiple applications, even though previously we have defined only 1.

    Subscribers
    First we'll define some methods for subscribing and unsubscribing users. Before we do this, we need to think about how authentication is tracked. For simplicity sake, we will assume that users are authenticated by active directory. This means the current windows principal will provide us with a username as a string:

    string userId = WindowsIdentity.GetCurrent().Name;

    You may remember from our application schemas that we needed to store a UserId. This was a simple string that represents the user a subscription belongs to. Before we can add subscriptions, we need to first check that the instance has the user registered as a subscriber. We can do this as follows:

    public bool Exists(string userId)   {
             SubscriberEnumeration mySubscriberEnumeration =
                    new SubscriberEnumeration(BlogInstance);
             Subscriber mySubscriber = mySubscriberEnumeration[userId];
             return (mySubscriber != null);
    }

    As you can see we use our BlogInstance to access an enumeration of all users, which can be indexed by the user's name. We could easily modify this into a Delete method for a subscriber as follows:

    if (mySubscriber != null)  mySubscriber.Delete();

    Now we know how to check for a subscriber, we can write code to add one:

    if (Exists(userId)==false) {
          Subscriber subscriber = new Subscriber(BlogInstance);
          subscriber.Enabled = true;
          subscriber.SubscriberId = userId;
          subscriber.Add();
    }

    Simple! If we don't check for a subscriber first, an exception will occur if a name is added twice.

    Devices
    In my last post I discussed Devices very quickly. Devices allow you to specify different delivery locations for different subscriptions. For example, I might want to receive notifications by email and instant message. We need to define a device for our subscriptions to be allocated against:

                SubscriberDevice device = new SubscriberDevice(BlogInstance);
                device.DeviceName = "WorkEmail";
                device.SubscriberId = userId;
                device.DeviceTypeName = "Email";
                device.DeviceAddress = "myrealemailaddress@hotmail.com";
                device.DeliveryChannelName = "SMTP";
                device.Add();

    DeviceName is often referenced in your application XML rule for subscriptions so it is usually important to have a naming standard for your devices. However we have not been using devices up to this point so the values you put here aren't important for now.

    DeliveryChannelName however is extremely important. In part 2 we defined the instance XML and part of that was the <DeliveryChannels>. In that XML we defined a protocol of type SMTPDelivery with a name of SMTP. When defining the device delivery channel name, it must match one of the channels specified in your instance XML otherwise no deliveries will be sent (no exception will occur, however when the service runs your notifications, it is at this point that it will fail, and details will be inserted into the event log).

    As with subscribers you can check if the device has already been added as follows:

    SubscriberDeviceEnumeration deviceEnumeration =
                    new SubscriberDeviceEnumeration(BlogInstance, userId);
    SubscriberDevice mySubscriberDevice = deviceEnumeration ["WorkEmail"];
    return (mySubscriberDevice != null);

    As with subscribers, we should check the device has not already been added before commencing the insert process. And as with subscribers, deleting a device is as easy as calling the Delete method of the SubscriberDevice instance.

    Subscriptions
    Since we are only concerned with adding a subscription, the code for this is reasonably simple. Remember our requirements originally stated that a user will be automatically subscribed to a post after the post is created. This means we don't even need to check if the user is already subscribed since they couldn't possibly be. This makes our code even easier. Here is what the add method might look like:

    public void Add(string userId, string deviceName, int PostId) { 
                Subscription subscription =
                             new Subscription(BlogApplication, "CommentSubscription");         
                subscription.SetFieldValue("DeviceName", "WorkEmail");
                subscription.SetFieldValue("SubscriberLocale", "en-au");
                subscription.SubscriberId = userId;
                subscription.SetFieldValue("PostId", PostId);
                subscription.Add();
    }

    Because the .Net type subscription has no way of knowing at design-time what fields your subscription schema might have, it uses a common method for setting your subscription fields. This means you won't get compile errors if you don't really have a "PostId" in your subscription schema in Notification Services, but you will get a runtime exception on the subscription.Add();  method call.

    Important to note is that in the constructor for a Subscription takes an NSApplication instance now. This is because even though subscribers and devices are specific to an instance, an actual subscription is specific to an application only. Also note the second parameter of the constructor. This is the name of the subscription class we are subscribing to. Note that the name we supplied 'CommentSubscription' is the name we specified for our subscription class in the application XML. Once again, this will cause a runtime exception at the subscription.Add(); method call if the subscription class name is invalid.

    Well done! The user is now subscribed to receive notifications when comments are posted. However we still have to define how comments are posted to Notification Services so that those subscriptions can be acted upon.

    Events
    Going back to our Events schema defined in our application XML, there is a number of fields we need to supply information for. The Event class helps us set values for those fields very easily, just like it does for subscriptions. To add an event to Notification Services is as easy as this:

    Event event = new Event(BlogApplication, "NewCommentEvent");
    event["PostId"] = postId;
    event["Name"] = CommentPosterUserName;
    event["Details"] = CommentText;

    EventCollector eventCollector =
                    new EventCollector(BlogApplication, "BlogEventsProvider");
    eventCollector.Write(event);

    In this code, we assume that we already have the values of the comment text, the user who posted the comment, and the original blog postId that the comment was posted against. The Event class lets us define the details to post to Notification Services through an indexer, and the EventCollector class will write the events to Notification Services for us. As usual incorrect fields specified will result in a runtime exception.

    Some items to note here. First, the constructor for an event needs the name of the event class we are posting to. We only defined 1 in our events schema; this was defined in the last post in our application XML. Secondly, in our last post we also defined the <Providers> node and the <NonHostedProvider> sub node (right at the end). When defining an EventCollector we must pass our provider name to the contstructor. You can not post an event to Notification Services without using one of the providers specified in your application XML. Because we are specifying events in code outside of Notification Services, our provider is a 'NonHostedProvider': not hosted by Notification Services.

    Summary
    Well that's it. We call the events to post a subscription in the very next line of code after we post our blog entry. And we call our event method whenever someone posts a comment against a blog entry. And Notification Services will take care of the rest.

    This brings us to the end of my series on Notification Services. If there is anything else you would like to know about, please don't hesitate to contact me and I'll do my best to provide useful information.

    Notification Services Series
    Notification Services - Part 1 - Technology Overview
    Notification Services - Part 2 - Example Overview and Instance XML
    Notification Services - Part 3 - Application XML
    Notification Services - Part 4 - Managed API

    Further Resources
    Notification Services tutorial: http://msdn2.microsoft.com/en-au/library/ms170337.aspx
    MSDN help: http://msdn2.microsoft.com/en-au/library/ms172483.aspx
    API reference: http://msdn2.microsoft.com/en-au/library/microsoft.sqlserver.notificationservices.aspx


    17 February

    Notification Services - Part 3 - Application XML

    The application XML defines the key information about your Notification Services (NS) application. This includes the schemas for event data being received, schemas for what users can subscribe to, and schemas for the information that will be sent in a notification. At the bare minimum you need 1 schema for each section. The application XML is defined with the following elements:

    <?xml version="1.0" encoding="utf-8" ?>
    <Application xmlns:xsd="..."  xmlns:xsi="..."  xmlns="...">
        <EventClasses></EventClasses>
        <SubscriptionClasses></SubscriptionClasses>
        <NotificationClasses></NotificationClasses>

        <Providers></Providers>
        <Generator></Generator>
        <Distributors></Distributors>
        <ApplicationExecutionSettings></ApplicationExecutionSettings>

    </Application>

    Here you can see the sections where we define our 3 different schemas: EventClasses, SubscriptionClasses, and NotificationClasses.

    Events
    Continuing on from the example we mentioned previously, users will subscribe to comments for a particular blog. This means that the comment itself is an event: when a comment is sent to notification services, the system checks for any subscribers who should be notified, and notifies them. This means that the comment is in fact our event.

    It is important to decide early on what information you want to track about the comment. In our case, users will be subscribing to receive comments for a particular blog post. The best way to track a blog post is by its Id. Referring to the schema defined in part 2 each blog post has a 'PostId' and this is referenced as a foreign key to the Comment table. This is the first item of information we will need in our schema. No doubt we'll also need to send notifications with the comment title and contents, so we'll add those two fields to our schema as well. Our final entry for the application XML looks like this:

    <EventClasses>
        <EventClass>
            <EventClassName>NewCommentEvent</EventClassName>
            <Schema>
                <Field>
                    <FieldName>PostId</FieldName>
                    <FieldType>int</FieldType>
                    <FieldTypeMods>not null</FieldTypeMods>
                </Field>
                <Field>
                    <FieldName>Name</FieldName>
                    <FieldType>varchar(255)</FieldType>
                    <FieldTypeMods>not null</FieldTypeMods>
                </Field>
                <Field>
                    <FieldName>Details</FieldName>
                    <FieldType>nvarchar(4000)</FieldType>
                    <FieldTypeMods>not null</FieldTypeMods>
                </Field>               
            </Schema>
            <IndexSqlSchema>
                <SqlStatement>
                    CREATE INDEX KnowledgeEventIndex
                    ON KnowledgeEvent ( PostId );
                </SqlStatement>
            </IndexSqlSchema>
        </EventClass>
    <EventClasses>

    When your instance XML is imported into NS, and the application XML is processed, these fields will actually be created in a new table. You can also see we have defined an index that will be created in the same table.

    Notifications
    This is quite simple to define. What information will our users receive in their email? Well we already had to consider this when defining our event schema. The user will receive the comment name and contents in their email. So this is the only information we need to define in our schema for notifications:

    <NotificationClasses>
        <NotificationClass>
            <NotificationClassName>BlogCommentNotification</NotificationClassName>
            <Schema>
                <Fields>                   
                    <Field>
                        <FieldName>Name</FieldName>
                        <FieldType>nvarchar(255)</FieldType>
                    </Field>
                    <Field>
                        <FieldName>Details</FieldName>
                        <FieldType>nvarchar(4000)</FieldType>
                    </Field>                   
                </Fields>
            </Schema>
            <ContentFormatter>
                <ClassName>XsltFormatter</ClassName>
                <Arguments>
                    <Argument>
                        <Name>XsltBaseDirectoryPath</Name>
                        <Value>....</Value>
                    </Argument>
                    <Argument>
                        <Name>XsltFileName</Name>
                        <Value>NotificationFormatter.xslt</Value>
                    </Argument>
                </Arguments>
            </ContentFormatter>
            <Protocols>
                <Protocol>
                    <ProtocolName>SMTP</ProtocolName>
                    <Fields>
                        <Field>
                            <FieldName>Subject</FieldName>
                            <SqlExpression>'[BLOG_COMMENT] ' + Name</SqlExpression>
                        </Field>
                        <Field>
                            <FieldName>From</FieldName>
                            <SqlExpression>N'blogbot@snagy.name'</SqlExpression>
                        </Field>
                        <Field>
                            <FieldName>To</FieldName>
                            <SqlExpression>DeviceAddress</SqlExpression>
                        </Field>
                        <Field>
                            <FieldName>Priority</FieldName>
                            <SqlExpression>N'Normal'</SqlExpression>
                        </Field>
                        <Field>
                            <FieldName>BodyFormat</FieldName>
                            <SqlExpression>N'html'</SqlExpression>
                        </Field>
                    </Fields>
                    <ProtocolExecutionSettings></ProtocolExecutionSettings>
                </Protocol>
            </Protocols>
        </NotificationClass>
    </NotificationClasses>

    The first part of the <NotificationClass> node is very simple to create. First we give our schema a name via the <NotificationClassName> node. Then we define our 2 fields in the <schema> node.

    Next we go on to define our content formatter. In our case, we want to use a stock standard XSLT to define our markup. So we specify its base path and file name. Finally, we want to define what protocol to use for this notification class. In the last post we defined our <DeliveryChannels> in the instance XML. Here, we must match a valid protocol from that list. We only defined one: of type SMTPDelivery, and called it 'SMTP'. We use that name here and go on to define all the important fields required for sending an email. Note that the <SqlExpression> field specifies the value to use. This actually is a valid SQL expression; the value you supply here is translated into an SQL statement to retrieve the values required for the notification. For example, the 'To' section specifies DeviceAddress as the value to use for this field. We'll learn about the DeviceAddress and where it comes from a little later, but suffice to say it is pulled from the database. For absolte literal values, we follow normal SQL conventions and put them in 'single quotes'. In the case you want to combine a literal value with a database field, combine them with normal SQL syntax, such as in the case of the 'Subject' field where we defined the literal '[BLOG_COMMENT] ' and the database value Name (which is the comment name).

    Subscriptions
    Next we have to define how to turn event data into notifications, and we do this through the <SubscriptionClasses> node:

    <SubscriptionClasses>
        <SubscriptionClass>
            <SubscriptionClassName>CommentSubscription</SubscriptionClassName>
            <Schema></Schema>
            <EventRules>
                <EventRule></EventRule>
            </EventRules>
        </SubscriptionClass>       
    </SubscriptionClasses>

    As usual, we give our subscription class a name through the <SubscriptionClassName> node. Then, we define 2 parts to our subscription: the schema defining what fields can be subscribed to, and the rule that will process an event and turn it into a notification.

    The schema follows the same format as the <EventClasses> schema. For our application, we only want the user to be able to subscribe to a particular blog post, so we specify the PostId. However, we also want to track subscriptions on a 'per device' basis, so we also need to track what device the user is subscribing with. This manifests in our schema as follows:

    <Schema>
        <Field>
            <FieldName>DeviceName</FieldName>
            <FieldType>nvarchar(255)</FieldType>
            <FieldTypeMods>not null</FieldTypeMods>
        </Field>
        <Field>
            <FieldName>PostId</FieldName>
            <FieldType>int<FieldType>
            <FieldTypeMods>not null</FieldTypeMods>
        </Field>
    </Schema>

    Essentially, any subscription being made to the application will have to supply a device name and a PostId. However, when this schema is created as a table, Notification Services adds some other fields, such as a SubscriberId. In fact, all the schemas we've seen so far will be slightly modified by NS to keep things running smoothly.

    Next we must specify the rule to bring it all together. This rule takes the form of an SQL statement which joins potential subscriptions on to events. Each resulting row is a notification to be sent, and is parsed through the NotificationClass schema and formatters. Here is the <EventRule> we need to create notifications:

    <EventRule>
        <RuleName>CommentSubscriptionRule</RuleName>
        <EventClassName>NewCommentEvent</EventClassName>
        <Action>
            INSERT INTO BlogCommentNotification (SubscriberId, DeviceName, Name, Details)
            SELECT s.SubscriberId, s.DeviceName, e.Name, e.Details
            FROM NewCommentEvent e, CommentSubscription s
            WHERE e.PostId = s.PostId ;
        </Action>
    </EventRule>

    So first up we give our rule a name. Next, we define which event class we are comparing against for subscriptions. We only have one: NewCommentEvent. Finally the <Action> node defines our SQL statement that discovers potential notifications. Later on we define a time interval for how often NS should check for new notifications. When this interval fires, NS goes through all Event Rules and runs them.

    So lets delve into that a little further. Lets pretend NS is configured to fire off notification checks every 4 hours. Over a 4 hour period, our blog site gets 15 comments spread over 3 different blog posts: PostId 17, 18, and 19. NS already has received subscriptions for PostId 17 only, however 3 people are subscribed to that single post. In total 7 comments were made. The 4 hours expires, and the above SQL statement is processed. The result of the select statement will return 21 rows, which are inserted into the 'BlogCommentNotification' table. Another process fires and passes each row in this table to our XSLT formatter, and sends the notification via the defined channel (SMTP).

    Other Settings
    Besides the Events, Subscriptions, and Notifications classes, we originally mentioned some other nodes in our application XML. These provide settings for how often the service should check for notifications, trace detail levels, and vacuuming (the process of cleaning out data from events table once sent, etc). For example, with the <Distributors> node you can specify a different machine as being responsible for sending the notifications (useful for high-load scenarios). The only other node worth specifically mentioning is the <Providers> node:

    <Providers>
        <NonHostedProvider>
            <ProviderName>BlogEventsProvider</ProviderName>
        </NonHostedProvider>
    </Providers>

    This node is used to define how events get submitted to the application. If you intend to provide events through the managed API (as we are) then this must be set to a <NonHostedProvider>. The name you specify here will be used in your code. More on this in the next post.

    Summary
    Well that wraps it up. Sorry for such a long post: I just didn't want to separate the application XML out into different posts. Note that all 3 major classes allow you to supply multiple schemas. This way you can have different kinds of notifications, different delivery methods, etc all depending on how users subscribe. The additional setting nodes that we didn't cover in detail can be found in the MSDN reference mentioned at the bottom. Next post will cover the managed API, specifically how to submit events, and how to add/remove subscribers.

    Notification Services Series
    Notification Services - Part 1 - Technology Overview
    Notification Services - Part 2 - Example Overview and Instance XML
    Notification Services - Part 3 - Application XML
    Notification Services - Part 4 - Managed API

    Further Resources
    Notification Services tutorial: http://msdn2.microsoft.com/en-au/library/ms170337.aspx
    MSDN help: http://msdn2.microsoft.com/en-au/library/ms172483.aspx
    API reference: http://msdn2.microsoft.com/en-au/library/microsoft.sqlserver.notificationservices.aspx

    10 February

    Notification Services - Part 2 - Example Overview and Instance XML

    As mentioned in part 1 of my discussions about NS, I described how 'Subscribers' can subscribe via certain 'Devices' to 'Events' to receive 'Notifications'. In this post, I will demonstrate how to declaratively define your Notification Services instance. In following posts I will discuss how to create your application XML and talk to Notification Services using the API.

    Example Overview

    Previously we mentioned a user's blog as an example. We'll continue with that concept here. Imagine the database schema for a blog: You have 'Posts' which are performed by 'Users', and people can publish 'Comments' to them. A simple schema might look like this:

    schema

    You want to be able to let the Users of the blog subscribe to Comments made against particular posts. You've already built the main web application where users can login and post new blog articles. Now you want to extend the application to allow for notifications, and decide to use Notification Services as your technology.

    So you come up with a design and decide on the following:

    1. Only the writer of a blog post can subscribe to the comments for that post
    2. Subscription will be automatic: As soon as the 'Publish' button is clicked, the user is subscribed to receive notifications. That way you don't have to write any new web pages for subscribing/unsubscribing - You'll deal with that in a later version
    3. Notifications will be delivered by email. They will contain the title of the blog post being commented against, and the details of the comment itself.
    4. All comments will be emailed to the user to keep it simple.

    You now have to do a couple of things. The first is to create the Notification Services instance/application, and the second is to modify your existing blog code to do 2 things: auto-subscribe a user who writes a post, and to also send all comments to Notification Services.

    Instance XML

    You decide to call your instance 'Intranet' and this specific NS application 'BlogNotifications'. First you have to create the instance XML, and the first draft looks something like this:

    <InstanceName>Intranet</InstanceName>
    <SqlServerSystem>(local)</SqlServerSystem>
    <Applications>
        <Application>
            <ApplicationName>BlogNotifications</ApplicationName>
            <BaseDirectoryPath>...</BaseDirectoryPath>
            <ApplicationDefinitionFilePath>  ...  </ApplicationDefinitionFilePath>
            <Parameters>
                 ...
            </Parameters>
        </Application>
    </Applications>

    <DeliveryChannels>       
        <DeliveryChannel>
            <DeliveryChannelName>SMTPDelivery</DeliveryChannelName>
            <ProtocolName>SMTP</ProtocolName>
            <Arguments>
                <Argument>
                    <Name>SmtpServer</Name>
                    <Value>smtp.myhost.com</Value>
                </Argument>
                <Argument>
                    <Name>BodyEncoding</Name>
                    <Value>utf-8</Value>
                </Argument>
            </Arguments>
        </DeliveryChannel>
    </DeliveryChannels>

    This means that you want an instance called 'Intranet' to be created on the local server. The available delivery channels to this instance are only SMTP at this stage. The name 'SmtpServer' is the name that will be used by your application code to reference what delivery channel to use for a particular subscription (more on that later). In Notification Services, anyone who is a subscriber, is defined at the instance level. This means only 1 list of subscribers is maintained for multiple applications.

    The application will be called 'BlogNotifications' and the 'ApplicationDefinitionFilePath' points to the second XML file we need to define. As mentioned earlier, you can have multiple applications in an instance. Here we have defined only 1, as with the Delivery Channels.

    It is possible to define variables to use in your instance XML to make things easier. These are called 'parameters' and when your instance xml is uploaded to Notification Services, it will prompt you for the values of any parameters you have defined. This works great in our case, because we may not want to 'hard code' the SMTP server. When testing we might use 1 server, but the production server might be somewhere else and therefore we would want to change the SMTP server at upload time. Parameters work perfect for this:

    <ParameterDefaults>
           <Parameter>
                <Name>_SMTPServer_</Name>
                <Value>smtp.myhost.com</Value>
           </Parameter>
    </ParameterDefaults>

    We would then alter our deliver channel code like this:

    <Argument>
            <Name>SmtpServer</Name>
            <Value>%_SMTPServer_%</Value>
    </Argument>

    You can define any number of parameters. You can even use machine variables from the local OS in your parameters, as follows:

    <Parameter>
             <Name>_Server_</Name>
             <Value>%COMPUTERNAME%</Value>
    </Parameter>

    Finally, you can pass on values to your Application by defining some parameters for your application XML:

    <Applications>
            <Application>
               <ApplicationName>BlogNotifications</ApplicationName>
               <BaseDirectoryPath>...</BaseDirectoryPath>
               <ApplicationDefinitionFilePath>  ...  </ApplicationDefinitionFilePath>
               <Parameters>
                    <Parameter>
                        <Name>_NSServer_</Name>
                        <Value>%_Server_%</Value>
                    </Parameter>
                </Parameters>
            </Application>
    </Applications>

    Here we are passing the value of a paramter called _Server_ as a parameter to the application XML called _NSServer_.

    That pretty much sums up the instance XML. In the next part, I'll discuss the Application XML how to link notifications to subscriptions.

    Notification Services Series
    Notification Services - Part 1 - Technology Overview
    Notification Services - Part 2 - Example Overview and Instance XML
    Notification Services - Part 3 - Application XML
    Notification Services - Part 4 - Managed API

    Further Resources
    Notification Services tutorial: http://msdn2.microsoft.com/en-au/library/ms170337.aspx
    MSDN help: http://msdn2.microsoft.com/en-au/library/ms172483.aspx
    API reference: http://msdn2.microsoft.com/en-au/library/microsoft.sqlserver.notificationservices.aspx