Introduction

In this tutorial we're going to examine the Live Alerts service. We'll discover how we can programmatically register users and groups as well as send notifications to our registered users via the Live Alerts web service. If you wish to follow along you'll need to request the API credentials from Microsoft. The Live Alerts service uses IP filtering to restrict access to the web services, hence you'll need to supply an IP address for both your development and production environments. I was told by a representative of the support staff that wildcards may be used for specifying IP addresses. For example, the IP 33.33.*.* should be valid. The following link on MSDN contains details on how you can get started and request your API credentials, however, the basic run down is:

Email alertsdk@microsoft.com with the following information:

  • Site Name (This name will appear on the screens hosted by Alerts).
  • List of partner IP Addresses for Development and Production machines
  • List the languages that the signup experiences (USA (English), etc).

For full details check out the MSDN article Getting Started with the Windows Live Alerts SDK.

Consuming the Web Service

First and foremost we must reference the web services from our application project in Visual Studio. There are two different Live Alerts environments, production and test. In this tutorial we're going to utilize the testing environment. The two web services we should reference from our web application are:

  • Message: http://services.alerts.live-ppe.com/axis/services/Message?wsdl
  • Subscription: http://services.alerts.live-ppe.com/axis/services/Subscription?wsdl

To reference the web service right click your web application from Solution Explorer and select Add Web Reference from the popup menu. The Add Web Reference window will appear, as shown in the screen shot below.

image

Enter the web service URL for either the message or subscription web service in the URL textbox and click the Go button. The service structure and available method will be displayed in the large text window. We must next indicate the web reference name (or namespace) we wish to register this web service under in our application. The default namespace is com.live-ppe.alerts.services, however, I'm going to use the namespace Live.Alerts.Message and Live.Alerts.Subscription for the message and subscription services respectively. When ready click the Add Reference button. Repeat this process for the other web service, thus registering both the message and subscription service with our web application.

After both the message and subscription web services have been registered with our web application we can view the WSDL (Web Service Description Language) file for each web service. Within this file we'll find the structure and expected data types for methods and input/output parameters. Anyway, moving on, we're now ready to build our alerts application.

Live Alerts Configuration

To make use of the Live Alerts API and successfully communicate with the Live Alerts service you must supply your identification credentials. At this point it becomes obvious that we must either hard code our credentials into the wrapper class we'll develop in the next section or retain our identification credentials in the web.config file. I'm not too fond of hard coding values such as security credentials and as it greatly limits our reusability we should retain our identification credentials and any other configuration options in the web.config file.

The web.config file includes an element named <appSettings> and is a direct child of the <configuration> element. We can utilize insert k/value pairs into this element directly within the web.config file. Consider the following web.config markup below.

<add key="AlertsPin" value="00000000"/>
<add key="AlertsPwd" value="myPassword"/>
<add key="AlertsReturnUrl" value="http://localhost:62643/LiveAlerts/ManageAlerts.aspx"/>

The AlertsPin and AlertsPwd keys are your Live Alerts PIN and password respectively issued to you by Microsoft. The PIN and password shown in the above code listing is not valid. The last key is the AlertsReturnUrl. Here we can specify the URL that will be passed to the Live Alerts service when users are redirected to their site to authenticate their passport credentials and choose their delivery method. In our case, we're redirect the user back to our Visual Studio development web server and pointing them to the ManagerAlerts.aspx page, where they'll be able to subscribe/unsubscribe to alert groups.

Programatically accessing values from the <appSettings> element is quite simple, ensure that the System.Configuration namespace is declared using the using directive and the <appSettings> element will be accessible via the ConfigurationManager class as shown in the code below.

ConfigurationManager.AppSettings["AlertsPwd"]

That's all there is to it in the way of configuration, let's go build our wrapper class, shall we?

Live Alerts Wrapper

The Live Alerts API can become detailed when performing specific actions, and more to the point, very repetitive. Nothing too difficult, however building a wrapper, which can be used to wrap much of the behavior became a desirable task while I was developing the sample applications using the service. In this section we'll discuss the basic wrapper class and as we go, we'll examine the infrastructure of Live Alerts.

Subscriptions

The Live Alerts Subscription service contains a few different methods and objects that can be used to register a user with our alert service and also allow our users to subscribe to a number of different message groups, that is, within our primary alert service, we can subdivide the messages into groups and send out alerts to specific groups and thus all users subscribed to those groups. Before a user can subscribe to any groups they must register with our alert service, to do this, we can use the Subscription service to request from the Live Alerts service a response indicating whether the specific user is already registered with our alerts service, if they are, they can subscribe/unsubscribe to message groups, otherwise they must be redirected to the Live Alerts signup page, hosted by Microsoft. There they will authenticate themselves with their Passport ID and configure their preferred message delivery method, that is, via Windows Live Messenger, Email, Mobile, etc.

Initiating a Signup Request

The InitiateSignup() method exposed by the AlertsWebServicesService class of the Subscription service is used to verify a users registration and, if required, redirect them to Microsoft's servers for signup and customization of their preferred delivery method. This method has the following prototype:

RecAlertsRequestResponse InitiateSignup(
    RecServicesHeader h, 
    RecServicesIdentification ID,
    string partnerID, 
    string returnURL, 
    string transportType)
Header

The first parameter, h, is the subscription header, the header contains details such as a message ID, timestamp and version. The header would look something like that in the code listing below within a SOAP request.

Note

The Message service also includes a RecServicesHeader class.

<h xsi:type="ns1:RecServicesHeader">
 <messageID xsi:type="xsd:string">2003-03-26.testInitiateSignup.1</messageID>
 <timestamp xsi:type="xsd:string">2003-03-26T04:36:25-08:00</timestamp>
 <version xsi:type="xsd:string">1.0</version>
</h>

The messageID must be a unique identifier for each request and can be used for troubleshooting, logging, etc. The messageID is structured as follows:

YYYY-MM-DD.UniqueID.PIN

The first portion is slightly obvious, that is, the year, month and day, however the UniqueID should be any unique string literal, a GUID can be used, as its guaranteed unique. Finally, the last portion of the messageID is your Live Alerts PIN, however, I've invoked the InitiateSignup() method passing in the header with a messageID that doesn't contain my real PIN and every worked as expected, nevertheless it's probably best that you use your PIN as to avoid any problems in future versions.

The version, as shown in the SOAP message above, should be 1.0. The version parameter is of type string, meaning that the version number should be enclosed in quotation marks when programmatically assigned to the version property of the RecServicesHeader class ("1.0").

Finally, the timestamp represents the date and time the message was sent using the ISO 8601 specification. The basic format of the timestamp is shown below (and can be seen in the SOAP message above).

YYYY-MM-DDThh:mm:ss-hh:mm

The first portion prior to the T character is the date that the message was sent, the portion after the T character is the hour, minute and second representing the time the message was sent. Finally, -hh:mm indicates the relative time to GMT, for example, Pacific Standard Time would be represented as -08:00. That said, a message sent April 11th at 7:34 PST would be represented as: 2008-04-11T07:34:32:-08:00.

Our wrapper method which is invoked to construct our header is shown in the listing below.

private static Live.Alerts.Subscription.RecServicesHeader CreateHeader()
{
    Live.Alerts.Subscription.RecServicesHeader header = new Live.Alerts.Subscription.RecServicesHeader();

    header.messageID = string.Format("{0}.{1}.{2}",
        DateTime.Now.ToString("yyyy-MM-dd"),
        Guid.NewGuid().ToString().Replace("-", string.Empty),
        ConfigurationManager.AppSettings["AlertsPin"]);

    header.version = "1.0";

    header.timestamp = String.Format("{0}T{1}", DateTime.Now.ToString("yyyy-MM-dd"), 
        DateTime.Now.ToString("HH:mm:sszzz"));

    return header;
}

When the CreateHeader() message is invoked, a fully populated RecServicesHeader object will be returned and can be passed to any of the Subscription methods requiring the header.

Identification

The second parameter, ID, represents your Live Alerts identification credentials. The parameter is of type RecServicesIdentification and is used to represent both your PIN and password. The identification credentials are passed in the SOAP request as shown below.

Note

The Message service also includes a RecServicesIdentification class.

<ID xsi:type="ns1:RecServicesIdentification">
 <PINID xsi:type="xsd:int">00000000</PINID>
 <PW xsi:type="xsd:string">password</PW>
</ID>

There really isn't much to it, the code listing below demonstrates the construction of the RecServicesIdentification class and the assignment of your PIN and password as set in the web.config <appSettings> element.

private static Live.Alerts.Subscription.RecServicesIdentification CreateIdentification()
{
    Live.Alerts.Subscription.RecServicesIdentification id = new Live.Alerts.Subscription.RecServicesIdentification();
    id.PINID = int.Parse(ConfigurationManager.AppSettings["AlertsPin"]);
    id.PW = ConfigurationManager.AppSettings["AlertsPwd"];

    return id;
}

The properties of the RecServicesIdentification object, PINID and PW are assigned the values specified in the AlertsPin and AlertsPwd key located in the <appSettings> element of the web.config.

Partner ID

What partner? I don't want a partner! Oh, sorry. Anyway, the partner ID is used to represent a unique identification string for each user subscribed to our Alerts service. This ID should be different between all users and is the identification used when a list of subscribed users are retrieved via the FindAllUsers() method. In our application we're going to require that all users authenticate themselves with our web site prior to allowing them to register for alerts. This being the case, we have plenty of unique attributes for our registered users. I have chosen to use their email address as the partner ID.

An authenticated user's email address can be retrieved using the ASP.NET Membership provider as shown in the code listing below (this is of course assuming you're utilize the membership provider in your application, which in our case, we are. We will examine this in more detail later).

MembershipUser user = Membership.GetUser();
user.Email // The email address
Return URL

The returnUrl parameter represents the URL that Microsoft should redirect the user to after they have successfully agreed to the Live Alerts agreement and chosen their preferred delivery method(s). As with the Live Alerts credentials, the return URL is retrieved from the <appSettings> element in the web.config file.

Transport Type

The transportType property is of type string and indicates the transport method used for delivery. The transport type should be MSNA and should probably be a configuration attribute in the <appSettings> element of the web.config to simplify any transport changes.

The Wrapper Method

Finally, our wrapper class used to invoke the InitiateSignup() method of the Subscription web service can be seen below.

public static SignupResponse InitiateSignup(string returnUrl, string userid)
{
    SignupResponse liveAlertsResponse = new SignupResponse();
    liveAlertsResponse.ReturnValue = new Status();

    AlertsWebServicesService liveAlerts = new AlertsWebServicesService();

    try
    {
        Live.Alerts.Subscription.RecAlertsRequestResponse response;
        response = liveAlerts.InitiateSignup(CreateHeader(), CreateIdentification(),
            userid, returnUrl, "MSNA");

        switch (response.response.statusCode)
        {
            case 0:
                liveAlertsResponse.RedirectUrl = response.URL;
                liveAlertsResponse.ReturnValue.Code = 0;
                break;
            case 326:
                liveAlertsResponse.ReturnValue.Code = 326;
                break;
            default:
                liveAlertsResponse.ReturnValue.Code = response.response.statusCode;
                liveAlertsResponse.ReturnValue.Message = response.response.statusReason;
                break;
        }
    }
    catch (System.Web.Services.Protocols.SoapException ex)
    {
        liveAlertsResponse.ReturnValue = new Status();
        liveAlertsResponse.ReturnValue.Code = -1;
        liveAlertsResponse.ReturnValue.Message = ex.Message;
    }

    return liveAlertsResponse;
}

It's actually quite simple, notice that an instance of AlertsWebServicesService is created and is used to invoke the InitiateSignup() method. The return value of the InitiateSignup() method is of type RecAlertsRequestResponse and implements the following properties:

Property Description
Header The header details for the response message. This property is of type RecServicesHeader and is equivalent to the header object we create for requests.
Response The response object of type RecServicesResponse. This property represents the response to the request, that is, the status code and status message. This object consists of the following properties that we'll use to retrieve the status of our request:

statusCode : int : The status code
statusReason : string : The status message, if an error this property will contain details of the error.
URL The URL that the user should be redirected to, if appropriate, to register for Live Alerts and customize their message delivery method.

Lastly, in the above code sample, notice the switch statement. If the returned status code is 0 the user must register for Live Alerts, that is, they should be redirected to Microsoft's servers for registration. However, if the status code 326 the user is already registered for Live Alerts and don't need to register again, they may stay on our site and manage their subscriptions. Lastly, if the status code isn't 0 or 326 we can assume an error occurred and collect the data for the caller to process the response.

Before we move on, notice that our wrapper method returns an object of type MI.Wrapper.SignupResponse. This class is defined as follows:

[Serializable]
public class SignupResponse
{
    public string RedirectUrl;
    public Status ReturnValue;
}

[Serializable]
public class Status
{
    public int Code;
    public string Message;
}

First and foremost notice that the classes shown in the above listing are marked as Serializable, this is because we're going to return them to client-side code, that is, they will be serialized as JSON text and returned to our AJAX client-side code. More importantly at this point are the properties they implement. The SignupResponse class is used to represent the response from an InitiateSignup request, in other words, it wraps the response object returned by the Live Alerts service. That is, the ReturnValue property is used to represent both the status code and status message. The ReturnUrl property represents the URL that the user should be redirected to if they have not yet signed up with Live Alerts.

Creating Subscription Groups

A subscription group is a group that users can subscribe to and receive messages sent to that group. From an administration point of view, subscription groups are the only way to, that is, separate your site content into subscription groups, users subscribe to the groups that interest them and when you send messages to the particular groups, only the users who have subscribed to them will receive your Alerts.

To create a subscription group you can use the AddGroup() method. This method, like the InitiateSignup() method accepts two parameters representing the request header and Live Alerts identification credentials. We'll use the two wrapper methods shown above to create the header and identification objects, we can then pass the return value to the AddGroup() method. Finally, the last two parameters accepted by the AddGroup() method represent the group name and description. This method has the following prototype.

RecServicesRequestResponse AddGroup(
    RecServicesHeader h, 
    RecServicesIdentification ID, 
    string group, 
    strong groupDescription)

The following wrapper class demonstrates the usage of this method.

public static Status CreateGroup(string groupName, string groupDescription)
{
    Status newGroupStatus = new Status();

    AlertsWebServicesService liveAlerts = new AlertsWebServicesService();

    // Create the group
    Live.Alerts.Subscription.RecServicesRequestResponse response;
    response = liveAlerts.AddGroup(CreateHeader(), CreateIdentification(), groupName, groupDescription);

    // Capture the return value (code 0 = success)
    newGroupStatus.Code = response.response.statusCode;
    newGroupStatus.Message = response.response.statusReason;

    return newGroupStatus;
}

Our wrapper class accepts two parameters, the group name and description and returns an instance of a MI.Wrapper.Status object, previously seen above.

Retrieving an Array of Subscription Groups

Retreiving an array of all subscription groups couldn't be any easier! The method FindAllGroups() implemented by the Subscription web service can be used to retreive a string array of all subscription groups. This method accepts two arguments, that is, the request header and Live Alerts identification. The prototype for this method is shown below.

RecGroupsRequestResponse FindAllGroups(
    RecServicesHeader h, 
    RecServicesIdentification ID)

The return value is of type RecGroupsRequestResponse. This object exposes the properties outlined in the table below.

Property Description
header The header details for the response message. This property is of type RecServicesHeader and is equivalent to the header object we create for requests.
response The response object used to represent the response code and message. Identical to what we've previously seen.
subscriptionGroups A string array representing all subscription groups.
unSubscribedGroups All unsubscribed groups for the given user account. This property is null when returned from the FindAllGroups() method.

The wrapper class below demonstrates the usage of this method.

public static string[] FindAllGroups()
{
    AlertsWebServicesService liveAlerts = new AlertsWebServicesService();

    // Find all groups
    Live.Alerts.Subscription.RecGroupsRequestResponse response;
    response = liveAlerts.FindAllGroups(CreateHeader(), CreateIdentification());
    
    return response.subscriptionGroups;
}

The method simply returns a string array containing all available subscription groups.

Retrieving a List of Subscribed Groups for a Given User

Retrieving a list of subscribed groups for a given user is actually quite similar to what we've seen above when retreiving a list of all subscription groups. The FindGroupsForUser() method of the Subscription service can be invoked, passing the request header, Live Alerts identification and the partner user ID we used to register the user with the Live Alerts service. Recall that in our application we'll require the user to authenticate themselves with our web site, also recall that I stated that the users registered email address will be used as their partner ID, that is, our authenticated users email address may be specified for the last argument to the FindGroupsForUser() method. This method has the following prototype:

RecGroupsRequestResponse FindGroupsForUser(
    RecServicesHeader h, 
    RecServicesIdentification ID,
    string partnerUID)

This method, like FindAllGroups(), returns an instance of RecGroupsRequestResponse. Our wrapper class for this method is shown below.

public static Groups FindSubscribedGroups(string userId)
{
    AlertsWebServicesService liveAlerts = new AlertsWebServicesService();

    // Find all groups
    Live.Alerts.Subscription.RecGroupsRequestResponse response;
    response = liveAlerts.FindGroupsForUser(CreateHeader(), CreateIdentification(), userId);

    Groups groups = new Groups();
    groups.Subscribed = response.subscriptionGroups;
    groups.UnSubscribed = response.unSubscribedGroups;

    return groups;
}

Notice that our wrapper FindSubscribedGroups() method returns an object of type MI.Wrapper.Groups. This class is quite simple and consists only of two string array properties, Subscribed and UnSubscribed, representing the subscribed groups and available groups respectively. The MI.Wrapper.Groups class is defined as follows.

public class Groups
{
    public string[] Subscribed;
    public string[] UnSubscribed;
}

Managing User Subscriptions

User group subscriptions can be managed using the ChangeSubscription() method exposed by the Subscription service. Using this method we can specify whether or not a user should be added or removed from a specified group. The front-facing UI for the users subscription management interface will make use of this method, thus allowing the users to manage their own subscriptions. This method has the following prototype:

RecServicesRequestResponse ChangeSubscription(
    RecServicesHeader h, 
    RecServicesIdentification ID,
    string partnerUID,
    string[] groups,
    string action,
    string email,
    int routingRule,
    string locale,
    string timeZone)

The first two parameters are expected, the request header and Live Alerts notification are required for all requests. The remaining parameters are discussed in the table below.

Parameter Description
partnerUID The users partner ID. This will be the email address they registered themselves with on our site. This ID is used to identify the registered Live Alert users.
groups A string array of groups that the indicated action should be performed against. That is, if the action is remove, the user indicated by the partnerID will be removed from all groups within this string array.
action The action to perform. This can be "add" or "remove".
email This property is not currently implemented. A value of string.Empty is valid for now.
routingRule This property is not currently implemented. A value of 0 is valid for now.
locale This property is not currently implemented. A value of string.Empty is valid for now.
timeZone This property is not currently implemented. A value of string.Empty is valid for now.

Before we analyze our wrapper class, we're going to create a simple enumeration that can be used to indicate the subscription action. This enumeration is defined below.

public enum SubscriptionAction
{
    add,
    remove
}

Finally, the wrapper class used to manage subscriptions for the given user is shown below.

public static Status ManageUserSubscriptions(string userId, string userEmail, string[] groups, 
    SubscriptionAction action)
{
    AlertsWebServicesService liveAlerts = new AlertsWebServicesService();

    Live.Alerts.Subscription.RecServicesRequestResponse response;
    response = liveAlerts.ChangeSubscription(CreateHeader(), CreateIdentification(),
        userId, groups, action.ToString(), string.Empty, 0, string.Empty, string.Empty);

    Status returnVal = new Status();
    returnVal.Code = response.response.statusCode;
    returnVal.Message = response.response.statusReason;

    return returnVal;
}

Again, this method can be invoked from the front-facing UI and should be available to the user management interfaces, thus allowing users to manage their own subscriptions. This method can be used to add or remove a specific user from a group of subscription groups. The subscription groups should be indicated as a string array.

Messaging

The Messaging service, registered in our application under the namespace Live.Alerts.Message, implements methods that can be used to send messages to users and even subscription groups and thus the users subscribed to those groups. The only method I want to discuss in this tutorial is the GroupDeliver() method, which as the name suggests, is used to deliver a message to a subscription group.

Note

Before we analyze the GroupDeliver() method, its again worth noting that this method returns a type of RecServicesRequestResponse, while named the same, it is however a different object. This object is implemented in the Message web service, and in our case, we've registered the Message web service under the namespace Live.Alerts.Message, the fully qualified name is Live.Alerts.Message.RecServicesRequestResponse. Not to be confused with RecServicesRequestResponse exposed by the Subscription web service.

This method has the following prototype.

RecServicesRequestResponse GroupDeliver(
    RecServicesHeader header,
    RecServicesIdentification ID,
    RecServicesGroupMessage gm)

The first two parameters are nothing new, however again, the Message service implements the RecServicesHeader and RecServicesIdentification objects and thus should be an instance of the classes implemented via the Message server. Our wrapper includes two other methods to create the correct objects, however we will not examine them in this text.

The second parameter, gm, is of type RecServicesGroupMessage and represents the message content and delivery group. This class exposes the following properties.

Property Description
fromContacts An array of RecServicesContact objects. The RecServicesContact object represents where the message should be sent (the user) and the screen ID to use when sending messages. This will appear in the from section. Finally, this object also represents the transport type, the only available transport type at this time is again, MSNA.

When redelivering group messages only the from and transport property of this object is required.
groupName A string literal indicating the name of the subscription group that the message should be delivered to.
content The plain text message to deliver. The value of this property may be no more than 90 double-byte characters.
emailMessage The email message that should be delivered to recipients. HTML tags may be used.
messengerMessage The Windows Live Messenger message that will be delivered to all recipients.
mobileMessage The message to deliver to mobile endpoints.
locale The locale of the message, such as, en-us.
Note

Ensure that you enclose messenger and email messages in paragraph (<p></p>) tags otherwise Live Alerts will treat the HTML as malformed and the plain text content will be render in place of the email/messenger message content.

In the above table we made reference to the RecServicesContact class. Really quickly let's take a look at the available properties of this class.

Property Description
from The address/screen name of the sender.
to The address, via the partner user ID of the recipient. Note that this property is not used when performing group deliveries.
transport A string literal specifying the deliver transport that will be used. This should be "MSNA".

The wrapper method that can be used to deliver group messages is shown in the code listing below.

public static Status SendGroupMessage(string from,
    string groupName, 
    string content,
    string emailMessage,
    string messengerMessage,
    string actionUrl)
{
    MessageWebServicesService messageService = new MessageWebServicesService();

    // Build contact object
    Live.Alerts.Message.RecServicesContact contact = new Live.Alerts.Message.RecServicesContact();
    contact.transport = "MSNA";
    contact.from = from;

    // Build message object
    RecServicesGroupMessage message = new RecServicesGroupMessage();
    message.groupName = groupName;
    message.content = content;
    message.fromContacts = new Live.Alerts.Message.RecServicesContact[] { contact };
    message.messengerMessage = messengerMessage;
    message.emailMessage = emailMessage;
    message.actionURL = actionUrl;

    // Send the message to the specified group
    Live.Alerts.Message.RecServicesRequestResponse response;
    response = messageService.GroupDeliver(CreateMessageHeader(),
        CreateMessageIdentification(), message);
    
    // Response
    Status status = new Status();
    status.Code = response.response.statusCode;
    status.Message = response.response.statusReason;

    return status;
}

Pretty straightforward! Let's move on and examine our web service, our proxy between client-side AJAX code and the Live Alerts web service.

Building our Web Service

For this application we're going to make use of a technique known as a proxy, that is, we're going to use our server, the server hosting our web application as a proxy between the client and the web service, in this case, Live Alerts. Why is this required? Due to a security policy known as same-origin, that is, only a request made with the XMLHttpRequest object may be to the same server as the originating scripts. An attempt to request resources from another origin will fail, for more details check out my blog post on Internet Explorer 8 and Cross-domain Requests.

The proxy technique we'll make use of in this article is illustrated in the figure below.

image

Our web service must implement all of the methods that we wish to expose to our client-side code, for example, the creation of a new group, the registration of users with Live Alerts, etc. Let's get to it, shall we?

Creating the WCF Web Service

We're going to make use of a Windows Communicate Foundation (or WCF) web service in this tutorial. To begin, go ahead and head on over to your web project, right click the root of the project within Solution Explorer and select Add New Item. The Add New Item dialog box will appear as shown below.

image

Select AJAX-enabled WCF Service, name the web service LiveAlerts.svc and click the Add button when ready. Ensure that LiveAlerts.svc file is placed within the root of your web application and the LiveAlerts.cs, the code behind file for the web service, is placed within the App_Code folder. Open the LiveAlerts.cs file and replace the content with that shown in the listing below.

using System;
using System.Configuration;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;

using System.Web;
using System.Web.Security;
using System.Web.Configuration;

[ServiceContract(Namespace = "MI.Service")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class LiveAlerts
{
    // Add [WebGet] attribute to use HTTP GET
    [OperationContract]
    public MI.Wrapper.SignupResponse InitiateSignup()
    {
        MI.Wrapper.SignupResponse response;

        // Require user autnetication
        if (!HttpContext.Current.User.Identity.IsAuthenticated)
        {
            response = new MI.Wrapper.SignupResponse();
            response.ReturnValue = new MI.Wrapper.Status();

            response.ReturnValue.Code = -2;
            response.ReturnValue.Message = "Error: User is not authenticated.";

            return response;
        }

        // Get the currently authenticated user
        MembershipUser user = Membership.GetUser();
        response = MI.Wrapper.LiveAlerts.InitiateSignup(ConfigurationManager.AppSettings["AlertsReturnUrl"],
            user.Email);

        return response;
    }

    [OperationContract]
    public MI.Wrapper.Status CreateGroup(string groupName, string groupDescription)
    {
        MI.Wrapper.Status response = new MI.Wrapper.Status();

        // Create the group
        response = MI.Wrapper.LiveAlerts.CreateGroup(groupName, groupDescription);

        return response;
    }
    
    [OperationContract]
    public MI.Wrapper.Status SendGroupMessage(string from, 
        string groupName, 
        string content,
        string emailMessage,
        string messengerMessage,
        string actionUrl)
    {
        MI.Wrapper.Status response = new MI.Wrapper.Status();
        response = MI.Wrapper.LiveAlerts.SendGroupMessage(from, groupName, content, 
            emailMessage, messengerMessage, actionUrl);

        return response;
    }
    
}

We've implemented three methods, these are outlined below.

  • InitiateSignup() method: This method is used to begin the Live Alerts signup process for the currently authenticated user. Notice that the current user identity is check to ensure that the user is authenticated. If the user is not currently authenticated we return an MI.Wrapper.SignupResponse object with the error code of -2 and a friendly error message that can be displayed to the user if desired. Otherwise, the user is authenticated and we simply invoke the InitiateSignup() method within our wrapper and pass the currently authenticated users email address along with the return URL configured in web.config.
  • CreateGroup() method: This method is a pure proxy method between our client-side code and the wrapper we built above. We simply invoke the CreateGroup() method of our wrapper and return the MI.Wrapper.Status object to the client-side requester.
  • SendGroupMessage() method: This method, again, simply acts as a proxy between the SendGroupMessage() method exposed by our wrapper.

The Final Product

To demonstrate the usage of our wrapper, web service and more importantly, Live Alerts, I've constructed a fairly simple application and even a simple ASP.NET AJAX extender control, which can be used to extend an asp:Image object to include the initial signup functionality. Let's go ahead and examine the AJAX extender control.

ASP.NET AJAX Extender: Extending the Signup Process

This control simply extends asp:Image objects. When the extended control is clicked the Live Alerts signup initialization begins. Why did I create this extender? To simplify the inclusion of a Live Alerts register button on any page of our web site. You need only place an image, extend it with this extender and the user can register with Live Alerts simply by clicking the image.

The server-side object is very simple and can be seen below.

namespace MI.Controls
{
    [TargetControlType(typeof(Image))]
    public class LiveAlertsSignup : ExtenderControl
    {
        #region Properties
        public string LoginUrl
        {
            get
            {
                string s = (string)ViewState["LoginUrl"];
                return ((s == null) ? "Login.aspx" : s);
            }
            set
            {
                ViewState["LoginUrl"] = value;
            }
        }

        public string ManageSubscriptionsUrl
        {
            get
            {
                string s = (string)ViewState["ManageSubscriptionsUrl"];
                return ((s == null) ? "ManageAlerts.aspx" : s);
            }
            set
            {
                ViewState["ManageSubscriptionsUrl"] = value;
            }
        }

        public string ProgressPanel
        {
            get
            {
                string s = (string)ViewState["ProgressPanel"];
                return ((s == null) ? string.Empty : s);
            }
            set
            {
                ViewState["ProgressPanel"] = value;
            }
        }

        public string RedirectConfirmation
        {
            get
            {
                string s = (string)ViewState["RedirectConfirmation"];
                return ((s == null) ? "Would you like to be redirected to the Live Alerts sign up page now?" : s);
            }
            set
            {
                ViewState["RedirectConfirmation"] = value;
            }
        }
        #endregion

        protected override System.Collections.Generic.IEnumerable<ScriptDescriptor> GetScriptDescriptors(Control targetControl)
        {
            ScriptBehaviorDescriptor descriptor = new ScriptBehaviorDescriptor("LiveAlerts.SignupExtender", targetControl.ClientID);

            descriptor.AddProperty("loginUrl", this.LoginUrl);
            descriptor.AddProperty("subscriptionsUrl", this.ManageSubscriptionsUrl);
            descriptor.AddProperty("redirectConfirmationMsg", this.RedirectConfirmation);
            Control progressPanel = FindControl(this.ProgressPanel);
            if (progressPanel != null)
                descriptor.AddElementProperty("progressPanel", progressPanel.ClientID);

            return new ScriptDescriptor[] { descriptor };
        }

        protected override System.Collections.Generic.IEnumerable<ScriptReference> GetScriptReferences()
        {
            ScriptReferenceCollection srefs = new ScriptReferenceCollection();
            srefs.Add(new ScriptReference(Page.ResolveClientUrl("~/LiveAlertsSignup.js")));

            return srefs;
        }
    }
}

The extender includes four properties:

  • LoginUrl: The URL the user should be redirected to if their not authenticated.
  • ManageSubscriptionsUrl: The URL the user should be redirected to if they're already registered and need to manage their subscriptions.
  • ProgressPanel: The ID of a simple asp:Panel control that is used to visually represent activity while the Live Alerts service is being contacted behind the scenes, asynchronously.
  • RedirectionConfirmation: The message that will be presented to the user if they must be redirected to the Live Alerts signup page.

The real action happens all within the client-side JavaScript code. Please note that the JavaScript code listing below is not complete, the set and get accessors have been omitted, download the accompanying source files for the complete code, however the methods of interest have been included.

LiveAlerts.SignupExtender = function(element) {
    LiveAlerts.SignupExtender.initializeBase(this, [element]);
    
    this._loginUrl = null;
    this._subscriptionsUrl = null;
    this._progressPanel = null;
    this._redirectConfirmationMsg = null;
    
    this._onClick$Delegate = Function.createDelegate(this, this._onSignupClick);
    this._onSignupSuccess$Delegate = Function.createDelegate(this, this._onSignupSuccess);
    this._onSignupError$Delegate = Function.createDelegate(this, this._onSignupError);
}

LiveAlerts.SignupExtender.prototype = {
    initialize: function() {
        LiveAlerts.SignupExtender.callBaseMethod(this, 'initialize');
        
        var e = this.get_element();
        
        // Attach the onclick handler to the target control
        $addHandler(e, "click", this._onClick$Delegate);
        
        e.style.cursor = "pointer";
    },
    dispose: function() {        
        //Add custom dispose actions here
        LiveAlerts.SignupExtender.callBaseMethod(this, 'dispose');
    },
    
    _onSignupClick : function()
    {
        if (this._progressPanel != null)
            this._progressPanel.style.display = "block";
        
        // Invoke the signup web service
        MI.Service.LiveAlerts.InitiateSignup(this._onSignupSuccess$Delegate, this._onSignupError$Delegate);
    },
    
    _onSignupSuccess : function(e)
    {
        if (this._progressPanel != null)
            this._progressPanel.style.display = "none";
        
        switch (e.ReturnValue.Code)
        {
            case -2:
                window.location = this._loginUrl;
                break;
            case 0:
                var ans = confirm(this._redirectConfirmationMsg);
                if (ans)
                    window.location = e.RedirectUrl;
                break;
            case 326:
                window.location = this._subscriptionsUrl;
                break;
            default:
                alert(e.ReturnValue.Message);
                break;
        }
    },
    
    _onSignupError : function(e)
    {
        return;
    },

The _onSignupClick() method invokes our InitiateSignup() web service proxy method and specifies event handlers for the success and error events of the web service endpoint invocation. The _onSignupSuccess() method is the juicy part! Notice the switch statement. An error code of -2 indicates that the user has not been authenticated and should be redirected to the login page, an error code of 0 indicates that the user must register with the Live Alerts service, and finally, an error code of 326 indicates that the user is registered and should be redirected to the subscript management page. Last but not least, if the status code didn't match any of the above values, we simply display the error message to the user.

Let's go ahead and check out the usage of this extender control.

Users: Register for Live Alerts

The default.aspx file contains the simple markup that is used to register a user for Live Alerts.

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" MasterPageFile="~/MasterPage.master" %>
<%@ Register TagPrefix="Demo" Namespace="MI.Controls" Assembly="__code" %>

<asp:Content ID="Content1" ContentPlaceHolderID="PrimaryContent" runat="server">
    <p>
        Stay current! Register now to receive Alters via Windows Live Alerts to your Live Messenger, 
        Email and/or Mobile phone.
    </p>
    <p>Note: This is what the user of our web site will see.</p>
    <div style="text-align:center">
        <asp:Panel runat="server" id="Progress1" style="display:none; text-align:center">
            <img src="images/activityanimation.gif" /><br />
            Please wait...
        </asp:Panel>
        <asp:Image runat="server" ID="signupImage" ImageUrl="images/msn_alerts_130x34.jpg" />
        <Demo:LiveAlertsSignup runat="server" ID="LiveAlertsSignup1"
            ProgressPanel="Progress1"
            TargetControlID="signupImage" />
    </div>
</asp:Content>

The page looks like that shown below, when the user clicks the image button their identity status is checked, if their currently authenticated they will be redirected to the login page, otherwise they will be given the opportunity of registering with Live Alerts or managing their subscriptions.

image

Administrators: Creating Subscription Groups

The CreateGroup.aspx file is used to create subscription groups. Again this page invokes a web service to create the specified subscription group. The markup is shown below.

<%@ Page Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" CodeFile="CreateGroup.aspx.cs" Inherits="CreateGroup" Title="Untitled Page" %>

<asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server">

    <script type="text/javascript">
   1:  
   2:         function createSubscriptionGroup()
   3:         {
   4:             if (!Page_ClientValidate(''))
   5:                 return false;
   6:                 
   7:             var progress = $get("alertsProgress");
   8:             if (progress)
   9:                 progress.style.display = "block";
  10:             
  11:             var groupName = $get('<%=GroupName.ClientID %>').value;
  12:             var groupDescription = $get('<%=GroupDescription.ClientID %>').value;
  13:             
  14:             MI.Service.LiveAlerts.CreateGroup(groupName, groupDescription, 
  15:                 createGroupSuccess, createGroupError);
  16:         }
  17:         
  18:         function createGroupSuccess(e)
  19:         {
  20:             var progress = $get("alertsProgress");
  21:             if (progress)
  22:                 progress.style.display = "none";
  23:                 
  24:             if (e.Code != 0)
  25:             {
  26:                 alert("Error" + e.Message);
  27:                 return false;
  28:             }
  29:             
  30:             alert("Your subscription group has been created!");
  31:         }
  32:         
  33:         function createGroupError(e)
  34:         {
  35:             var progress = $get("alertsProgress");
  36:             if (progress)
  37:                 progress.style.display = "none";
  38:                 
  39:             alert("Error! We should do something!");
  40:         }
  41:     
</script> </asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="PrimaryContent" Runat="Server"> <div id="alertsProgress" style="display:none; text-align:center"> <img src="images/activityanimation.gif" /><br /> Please wait... </div> <table style="width:100%"> <tr> <td> Group name: </td> <td> <asp:TextBox runat="server" ID="GroupName" MaxLength="100" Width="300px"></asp:TextBox> <asp:RequiredFieldValidator runat="server" ID="Req1" ControlToValidate="GroupName" ErrorMessage="*"></asp:RequiredFieldValidator> </td> </tr> <tr> <td> Group description: </td> <td> <asp:TextBox runat="server" ID="GroupDescription" MaxLength="1024" TextMode="MultiLine" Height="150px" Width="300px"></asp:TextBox> <asp:RequiredFieldValidator runat="server" ID="RequiredFieldValidator1" ControlToValidate="GroupDescription" ErrorMessage="*"></asp:RequiredFieldValidator> </td> </tr> <tr> <td colspan="2" style="text-align:right"> <asp:Button ID="btnCreateGroup" runat="server" Text="Create Group" onclientclick="createSubscriptionGroup(); return false;" /> </td> </tr> </table> </asp:Content>

The page looks something like that shown below.

image

This page is accessible only to users within the Administrators role of our web site, as indicated in the web.config file:

<location path="CreateGroup.aspx">
  <system.web>
    <authorization>
      <allow roles="Administrators" />
      <deny users="*"/>
    </authorization>
  </system.web>
</location>

Administrators: Sending Group Messages

The page SendMessages.aspx is used by an administrator to send group messages to the indicated group. The markup is shown below.

<%@ Page Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" CodeFile="SendMessages.aspx.cs" Inherits="SendMessages" Title="Untitled Page" %>

<asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server">

    <script type="text/javascript">
   1:  
   2:         function sendMessage()
   3:         {
   4:             if (!Page_ClientValidate(''))
   5:                 return false;
   6:                 
   7:             var progress = $get("alertsProgress");
   8:             if (progress)
   9:                 progress.style.display = "block";
  10:                 
  11:             var groupDropDown = $get('<%=ddlGroups.ClientID %>');
  12:             
  13:             var from = $get('<%=txtFromContacts.ClientID %>').value;
  14:             var content = $get('<%=txtContent.ClientID %>').value;
  15:             var email = $get('<%=txtEmail.ClientID %>').value;
  16:             var messenger = $get('<%=txtMessenger.ClientID %>').value;
  17:             var actionUrl = $get('<%=txtActionUrl.ClientID %>').value;
  18:  
  19:             MI.Service.LiveAlerts.SendGroupMessage(from, 
  20:                 groupDropDown.options[groupDropDown.selectedIndex].value,
  21:                 content, email, messenger, actionUrl, sendMessageSuccess, sendMessageError);
  22:         }
  23:         
  24:         function sendMessageSuccess(e)
  25:         {
  26:             var progress = $get("alertsProgress");
  27:             if (progress)
  28:                 progress.style.display = "none";
  29:                 
  30:             if (e.Code == 0)
  31:                 alert("Your message has been sent to the selected group");
  32:             else
  33:                 alert("Error: " + e.Code + ". " + e.Message);
  34:         }
  35:         
  36:         function sendMessageError(e)
  37:         {
  38:             var progress = $get("alertsProgress");
  39:             if (progress)
  40:                 progress.style.display = "none";
  41:                 
  42:             alert("Error! Do something");
  43:         }
  44:     
</script> </asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="PrimaryContent" Runat="Server"> <h3>Send Group Messages</h3> <div id="alertsProgress" style="display:none; text-align:center"> <img src="images/activityanimation.gif" /><br /> Please wait... </div> <table style="width:100%"> <tr> <td> Group: </td> <td> <asp:DropDownList runat="server" ID="ddlGroups"></asp:DropDownList> </td> </tr> <tr> <td> From: </td> <td> <asp:TextBox runat="server" ID="txtFromContacts" Width="300px"></asp:TextBox> <asp:RequiredFieldValidator runat="server" ID="RequiredFieldValidator1" ControlToValidate="txtFromContacts" ErrorMessage="*"></asp:RequiredFieldValidator> </td> </tr> <tr> <td>Action URL</td> <td> <asp:TextBox runat="server" ID="txtActionUrl" Width="300px"></asp:TextBox> </td> </tr> <tr> <td> Content: </td> <td> <asp:TextBox runat="server" ID="txtContent" Width="500px" MaxLength="90" Height="50px" TextMode="MultiLine"></asp:TextBox> <asp:RequiredFieldValidator runat="server" ID="RequiredFieldValidator2" ControlToValidate="txtContent" ErrorMessage="*"></asp:RequiredFieldValidator> </td> </tr> <tr> <td> Email: </td> <td> <asp:TextBox runat="server" ID="txtEmail" Width="500px" Height="50px" TextMode="MultiLine"></asp:TextBox> </td> </tr> <tr> <td> Messenger: </td> <td> <asp:TextBox runat="server" ID="txtMessenger" Width="500px" Height="50px" TextMode="MultiLine"></asp:TextBox> </td> </tr> <tr> <td colspan="2" style="text-align:right"> <asp:Button ID="btnSendMessage" runat="server" Text="Send Message" onclientclick="sendMessage(); return false;" /> </td> </tr> </table> </asp:Content>

This page again makes use of a web service method to send messages. And of course, the page is shown in the screen shot below.

image

The group downdown list is dynamically bound using the Live Alerts wrapper as shown below.

protected void Page_Load(object sender, EventArgs e)
{
    ddlGroups.DataSource = MI.Wrapper.LiveAlerts.FindAllGroups();
    ddlGroups.DataBind();
}

When specifying email and Live Messenger content, ensure that you enclose the content in paragraph tags (<p></p>). If the content is not enclosed in paragraph tags, Live Alerts will consider the content malformed and will utilize the plain text message for all delivery methods.

Users: Manage Subscriptions

Finally, a user must be able to manage their subscriptions. This can be accomplished via the ManageAlerts.aspx page. This page does not make use of a web service, however it does perform asynchronous operations via the UpdatePanel. I will leave it up to you to implement a web service methods and utilize them to perform this management task. The pages markup is quite simple and shown below.

<%@ Page Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" CodeFile="ManageAlerts.aspx.cs" Inherits="ManageAlerts" Title="Untitled Page" %>

<asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="PrimaryContent" Runat="Server">

    <div style="text-align:center">
        <asp:UpdateProgress runat="server" ID="UpdateProgress1">
            <ProgressTemplate>
                <img src="images/activityanimation.gif" /> <br />
                Please wait...
            </ProgressTemplate>
        </asp:UpdateProgress>
    </div>

    <asp:UpdatePanel runat="server" ID="UpdatePanel1">
        <ContentTemplate>
            <h3>You're subscribed to the following groups:</h3>
            <asp:CheckBoxList runat="server" ID="chkSubscribedGroups" RepeatColumns="2"></asp:CheckBoxList>
            <div style="text-align:right">
                <asp:Button runat="server" ID="btnUnSubscribe" Text="Unsubscribe" 
                    onclick="btnUnSubscribe_Click" />
            </div>
            <h3>You can also subscribe to these groups:</h3>
            <asp:CheckBoxList runat="server" ID="chkUnsubscribedGroups" 
                RepeatColumns="2"></asp:CheckBoxList>
            <div style="text-align:right">
                <asp:Button runat="server" ID="btnSubscribe" Text="Subscribe" 
                    onclick="btnSubscribe_Click" />
            </div>
        </ContentTemplate>
    </asp:UpdatePanel>
</asp:Content>

The available and currently subscribed-to groups are bound to CheckBoxList controls. The code behind, shown below, binds the controls and rebinds them only when changes occur:

public partial class ManageAlerts : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        // Ensure that the user is logged in
        if (!User.Identity.IsAuthenticated)
            Response.Redirect("login.aspx?ReturnUrl=ManageAlerts.aspx");

        // Ensure that the user is registered for alerts
        // if not, redirect them to the Live Alerts signup page
        MI.Wrapper.SignupResponse checkUserResponse = new MI.Wrapper.SignupResponse();
        checkUserResponse = MI.Wrapper.LiveAlerts.InitiateSignup(Request.Url.AbsoluteUri, 
            Membership.GetUser().ProviderUserKey.ToString());

        if (checkUserResponse.ReturnValue.Code != 326)
            Response.Redirect(checkUserResponse.RedirectUrl);

        // If we're not in a postback retreive available subscription
        // groups and bind to the check box lists
        if (!Page.IsPostBack)
        {
            // Get all groups for this user
            MI.Wrapper.Groups userGroups = new MI.Wrapper.Groups();
            userGroups = MI.Wrapper.LiveAlerts.FindSubscribedGroups(Membership.GetUser().Email);

            BindToCheckBoxList(userGroups);
        }
    }

    protected void BindToCheckBoxList(MI.Wrapper.Groups groups)
    {
        chkSubscribedGroups.DataSource = groups.Subscribed;
        chkSubscribedGroups.DataBind();

        chkUnsubscribedGroups.DataSource = groups.UnSubscribed;
        chkUnsubscribedGroups.DataBind();
    }

    protected void SubscriptionManagment(MI.Wrapper.SubscriptionAction action, string[] groups)
    {
        // User identity
        MembershipUser user = Membership.GetUser();

        MI.Wrapper.Status response;
        response = MI.Wrapper.LiveAlerts.ManageUserSubscriptions(user.Email, user.Email,
            groups, action);

        if (response.Code == 0)
        {
            // Get all groups for this user
            MI.Wrapper.Groups userGroups = new MI.Wrapper.Groups();
            userGroups = MI.Wrapper.LiveAlerts.FindSubscribedGroups(Membership.GetUser().Email);

            // Rebind the checkbox list
            BindToCheckBoxList(userGroups);
        }
    }

    // Click handlers
    protected void btnUnSubscribe_Click(object sender, EventArgs e)
    {
        List<string> groups = new List<string>();
        foreach (ListItem item in chkSubscribedGroups.Items)
        {
            if (item.Selected)
                groups.Add(item.Text);
        }
        if (groups.Count == 0)
            return;

        SubscriptionManagment(MI.Wrapper.SubscriptionAction.remove, groups.ToArray());
    }

    protected void btnSubscribe_Click(object sender, EventArgs e)
    {
        List<string> groups = new List<string>();
        foreach (ListItem item in chkUnsubscribedGroups.Items)
        {
            if (item.Selected)
                groups.Add(item.Text);
        }
        if (groups.Count == 0)
            return;

        SubscriptionManagment(MI.Wrapper.SubscriptionAction.add, groups.ToArray());
    }
}

Finally, the subscription management page is actually pretty intuitive and easy to use, check out the screen shot below.

image

The user can select multiple check boxes and unsubscribe/subscribe to them with the click of a button.

Conclusion

The sample application includes a database with the ASP.NET membership and roles structure. The administrator account and password is:

administrator/pass_word

You may create standard user accounts and register them with Live Alerts. Finally, when an alert is sent the following screen shot depicts how the user will receive the alter via their Windows Live Messenger.

image

The page all new users are redirected to is shown below. This page is hosted by Microsoft and allows users to customize their delivery method.

image image

I hope you enjoyed this tutorial. Stayed tuned for new tutorials!

Download all source files