Entries:
Comments:
Posts:

Loading User Information from Channel 9

Something went wrong getting user information from Channel 9

Latest Achievement:

Loading User Information from MSDN

Something went wrong getting user information from MSDN

Visual Studio Achievements

Latest Achievement:

Loading Visual Studio Achievements

Something went wrong getting the Visual Studio Achievements

Building your own Windows Live Messenger Events Agent

  This articles shows how to build a Windows Live Messenger Add-in that connects to Windows Sharepoint Services.
Paul's Blog

Difficulty: Intermediate
Time Required: 1-3 hours
Cost: Free
Software: Visual Basic or Visual C# Express Editions, Windows Live Messenger, Windows Sharepoint Services 2.0 with Service Pack 2
Hardware: None
Download: Download

Summary

The guide takes you on your new text-based adventure, as you'll learn to build a little gadget that will answer questions instead of you. This pretty piece of software is nothing more than a Windows Live Messenger Add-In that will register people for events that are tracked using Windows SharePoint Services. In the end, you'll be able to activate an agent on your messenger that will interact with your friends in live scenarios using text messages.

Introduction

Microsoft Windows SharePoint Services is a versatile technology that people can use to increase the efficiency of business processes and improve team productivity. Let's see how we can extend this wonderful technology: you are probably spending almost all your time online and using Windows Live Messenger to talk to your friends; more, you may be a very organized person that keeps track of every event (e.g. movie nights, snowboarding weekends etc.), so why not let your friends register for your events in a totally unique way. Sounds good? You'll even be able to supervise the conversation and supply answers to questions that your agent is not prepared to answer.

Prerequisites

Please download Windows Live Messenger and install it on your computer if you have not done so already. With Windows Live Messenger version 8, you can even talk with your Yahoo! friends; we'll talk about Windows Live Messenger and Yahoo! Messenger later on.
I hope you are already using Visual Studio Express C# or VB since you're on the Coding4Fun web site (you're ok with any choice, as the sample is available is both languages).
You'll also need a place to set up your Windows SharePoint Services; depending on your mood, two options are available:

  1. You have Windows Server 2003 on your developer or on a near-by networked machine: just download Windows SharePoint Services with Service Pack 2 and go with the default installation.
  2. You just don't want to install something new, or your machine is not running Windows Server 2003: you are lucky, because Microsoft and its partners offer you a Hosted Windows SharePoint Services 2.0 Trial, which is more than enough to get you started.

I personally tried them both and the project worked properly, so it's up to you. Now that you have everything up and ready, let's get started: it's Coding4Fun time!

Getting Started with Windows Live Messenger Add-Ins

From a developer's perspective, Windows Live Messenger delivers a great instant messaging platform that provides support for custom add-ins through its Messenger Add-In API. However, this is not available out of the box; to take advantage of this feature, we must explicitly tell Windows Live Messenger that we want to use add-ins by tweaking a registry key.
Open the Registry Editor and set HKEY_CURRENT_USER\Software\Microsoft\MSNMessenger\AddInFeatureEnabled to a DWORD entry with the value equal to 1. It's time to check whether the change in the registry caused the desired effect in your Personal Settings, as illustrated in [Fig. 1]. A new tab called Add-ins should be visible to you.


[Figure 1]

Getting Started with Windows SharePoint Services

Here, I'll try to make you understand how Windows SharePoint Services works. Please excuse me in case you find these obvious, or I'm repeating myself; it's better to clear things out from the early stages than getting into code and not understanding the basic concepts. Windows SharePoint Services is designed as a platform to support different types of sites, also named templates. Natively installed unique types in Windows SharePoint Services include the STS type, which defines the Team Site, Blank Site, and Document Workspace configurations, and the MPS type, which defines the Basic Meeting Workspace, Blank Meeting Workspace, Decision Meeting Workspace, Social Meeting Workspace, and Multipage Meeting Workspace configurations; throughout the tutorial, we are going to work only with Team Web Site and Basic Meeting Workspace.
Let's organize your default Team Web Site to be ready for use in the upcoming custom add-in; navigate to your default web site location, as shown in [Fig. 2].


[Figure 2]

Notice that the page contains several panels named Announcements, Events and Links; in SharePoint terms, these are called lists. We are going to work only with the Events list; you are not constrained to work only with these predefined lists, you can create your own or use other predefined lists at anytime; just needs a little more investigation from your side.

Adding a New Event to the Events List

It's simple to add a new event; just click Add new event below the Events list. You'll have to complete a three-page process in order to create a valid event.

  1. Be sure to populate every field, regardless of what the red star character tells you; the Windows Live Messenger Add-In that we are building is going to use all of them. I have designed the add-in so that it works only with non-recurrent events. You are welcome to extend it to also work with recurrent events; I'll even tell you where you should modify the code to achieve that. Please consider special attention to the bottom Workspace check box.


    [Figure 3]

    You have to create a Meeting Workspace web site that associates with the event that you are currently adding. It's the only way you can keep track of attendees and other details. If you don't do this, you won't be able to register people for events or view attendees. Click Save and Close.
  2. Since you checked the Workspace box, you will be taken to the second page where you can specify details (web site title, web site location etc.) for the Meeting Workspace. Leave everything as it is and click Ok. Notice that the Meeting Workspace that you are creating will be a sub-site of the site where the Events list is located. You are now starting to understand how Windows SharePoint Services works.
  3. You are now going to select the template for this Meeting Workspace. Go with the Basic Meeting Workspace; this way, the predefined lists are already created for you, instead of a blank web site. Click Ok.

That's it! A new event that uses a Meeting Workspace should take you to the new workspace web site, like in [Fig. 4]. Again, you can see that the page contains several panels named Objectives, Attendees, Agenda and Document Library; these again are lists. At this point you got the idea: Windows SharePoint Services is made up from different types of sites (or sub-sites), and that each site (or sub-site) contains different types of lists depending on the template it's using.


[Figure 4]

The Attendees list already contains the email of the user who created it (that's me). When a friend of yours registers for an event (e.g. Microsoft Academic Tour), you should be able to see its email together with its response here. Every event has its own Meeting Workspace, so don't expect to see emails for all events here. Return to the Team Web Site Home (by clicking the Up to Team Web Site link located in the top-right corner) and repeat these steps as many times it's necessary. For example, you should add movie nights and snowboarding weekends as events; be sure to set up the occurrence date correctly as this is quite important when querying for events that occur within a week, month or year time.

Building the Windows Live Messenger Add-In

The current version of messenger provides only text message support for add-ins, although you have probably already seen that it's more than capable of handling sound and video when communicating with your friends; nevertheless it's enough for what we intend to build. Fire up your code editor, we're a step closer from writing code!
The add-in will be able to:

  • Say hello to a contact with whom you have not talked with since the add-in was started.
  • Detect the status (e.g. busy) of a contact.
  • Show upcoming events based on predefined time intervals: week, month, year and all.
  • Show details for events.
  • Register contacts for events.
  • Unregister contacts for events.
  • Change your personal status message according on how many contacts have been registered. We won't change it when a contact unregisters; we don't want to make bad commercial.

Several basic requirements need to be met in order for an add-in to function as expected. Please note that they are already done and included in the sample. However, it's better for you to understand how it actually works and not just find them somewhere in the sample and wonder why did I do that:

  • The add-in needs to use the MessengerClient.dll binary, so add a reference that points to it; it can be found in the default installation location %ProgramFiles%\MSN Messenger\MessengerClient.dll.
  • Add-ins run under Code Access Security (CAS) and are not allowed to call any web service. Since our primary purpose is to work with Windows SharePoint Services web services, we need to override this behavior. We can achieve that by installing the assembly in the Global Assembly Cache (GAC); assemblies deployed in GAC must be Strong-Named Assemblies.
    • To digitally sign the assembly, go to Project Properties > Signing > Sign the assembly and check the box; choose or create a new strong name key file.
    • To install the assembly in the GAC you should use the developer tool named gacutil which is easily available in the Visual Studio 2005 Command Prompt. I used the build events to automatically install my assembly: go to Project Properties (in C#) or Project Properties > Compile (in VB) > Build Events > Post-build event command line and enter "$(DevEnvDir)..\..\SDK\v2.0\Bin\gacutil.exe" /i "$(TargetPath)". From now on, every time there is a successful build, the output assembly is automatically installed in the GAC for you; be sure to compile the project at least once before using the add-in with the messenger.
  • The add-in is an assembly that contains a class that implements the Microsoft.Messenger.IMessengerAddIn interface. The messenger must know exactly what class to use, so the assembly name must be in Namespace.ClassName.dll format, where ClassName is the class that we talked about.

Exchanging Text Messages

By this time, it's clear that we are going to work with many text messages; it's always better to keep text that you're probably going to modify in the near future (you may even want to localize the messages in your language) in resources. We have several types of text messages, which all are located in Resources.resx:

  1. The 1st table displays the commands that make the add-in take action; call them incoming text messages:
    Name Value Details
    ITextMessage_AllEvents show events Request to see all upcoming events.
    ITextMessage_YearEvents show year events Request to see all upcoming events in a year time.
    ITextMessage_MonthEvents show month events Request to see all upcoming events in a month time.
    ITextMessage_WeekEvents show week events Request to see all upcoming events in a week time.
    ITextMessage_EventDetails show details for event [event_id] Request to see details for a selected event.
    ITextMessage_RegisterForEvent register for event [event_id] Request to register for a selected event.
    ITextMessage_UnregisterForEvent unregister for event [event_id] Request to unregister for a selected event.
    ITextMessage_Help help Request to help.

  2. The 2nd table displays the responses that the add-in will send back in reply to the previous incoming text messages; call them outgoing text messages:
    Name Value Details
    OTextMessage_Welcome Hy {0}, I'm the Events Agent (*). Please ask me something like "show events", "show month events" or "help" etc. Show that the add-in is alive and your friend is welcome to use it.
    {0} - your friend's name.
    OTextMessage_EndWelcome I can see that you are not busy, so why not register for one of the events... When your friend's status is other than busy.
    OTextMessage_BeginEvents You are welcome to participate in {0} event(s): {0} - number of events that matched your friend's query.
    OTextMessage_EventDescription Event {0} - {1}; {0} - event id;
    {1} - event title.
    OTextMessage_EndEvents You can view details and register for any event by saying something like "show details for event 2" and "register for event 2". Hope you register! Show a tip to remind your friend how it can register for events.
    OTextMessage_ZeroAvailableEvents I'm sorry Sad, but no event matched your query... Please try to enlarge the time span. When there is no event available that matched your friend's query.
    OTextMessage_EventDetails Event {0} - {1} takes place on {2} in {3}. {0} - event id;
    {1} - event title;
    {2} - event date;
    {3} - event location.
    OTextMessage_Help You can talk to me by saying: show events, show year|month|week events, show details for event 2, register for event 1, unregister for event 2, help. Show all available commands that the add-in is prepared to answer.
    OTextMessage_RegisteredForEvent You have been successfully Big Smile registered for event {0} - {1}. {0} - event id;
    {1} - event title.
    OTextMessage_NotRegisteredForEvent You don't seem to be registered for event {0} - {1}. {0} - event id;
    {1} - event title.
    OTextMessage_UnregisteredForEvent :-O It's a great event! I'm sorry you do not wish to attend anymore. You have been unregistered for event {0} - {1}. {0} - event id;
    {1} - event title.
    OTextMessage_InvalidEvent Sorry, you wanted to perform an action on an invalid event. Try to query for events first, like "show events" etc. When your friend uses an invalid event.
    OTextMessage_Exception Ooops! Embarassed something unexpected happened while processing your request. The action was not completed. Use "help" in case you have any questions. Please try again later. When an exception occurred; it can happen.
    In case you've seen awkward group of characters like Smiley :-O Embarassed etc. and are wondering what they are, I'll tell you that they are emoticons; and yes, the messenger translates them into cool pictures.
  3. The 3rd table displays Windows Live Messenger related customization:
    Name Value Details
    MessengerAddIn_Description Windows Live Messenger + Windows SharePoint Services Events The description of the add-in.
    MessengerAddIn_FriendlyName Events Agent The friendly name of the add-in.
    MessengerAddIn_PersonalStatusMessage Register For Events Now! Your default personal status message when the add-in starts.
    MessengerAddIn_PlusOnePersonalStatusMessage + {0} Person(s) Registered! Registration Is Open! Your personal status message when you have at least one person registered.
    {0} - number of registered friends.

  4. The 4th table displays Windows SharePoint Services related customization:
    Name Value Details
    SharePoint_AttendeesListName Attendees No need to change in case you went with the default installation.
    SharePoint_EventsListName Events No need to change in case you went with the default installation.
    SharePoint_MeetingSeriesListName Meeting Series No need to change in case you went with the default installation.
    SharePoint_ServerName http://sharepoint.borza.ro Modify to target your Windows SharePoint Services server name or web site that contains the Events list.

The following rule applies to all text messages that are sent in and out the network: a text message can have only 400 characters; in case a reply exceeds this limit, it won't send the text message, and you'll just wonder what happened. Take into account that a large number of events may increase the size of a query reply considerably. In case you have many events, you'll have to create a paging mechanism that meets your needs.

Consuming Windows SharePoint Services Web Services

There are many web services that can be used to work remotely with a deployment of Windows SharePoint Services: Administration, Alerts, Document Workspace, Forms, Imaging, List Data Retrieval, Lists, Meetings, Permissions etc; we are going to learn how to use Lists and Meetings web services, since these are the only ones we need. When adding web references to the web services, make sure you enter the URLs like these: http://ServerName/_vti_bin/Lists.asmx?WSDL and http://ServerName/_vti_bin/Meetings.asmx?WSDL. I know that you are eager to write some code; hang on, we are just one paragraph away from coding.

Looking at the Windows Live Messenger Add-In Code

I thought I would never reach the coding stuff... we'll start by looking at the way the add-in comes to life. In MessengerAddIn.cs or MessengerAddIn.vb, the class MessengerAddIn (to remind you, the assembly name is EventsAgent.MessengerAddIn.dll) is implementing the Microsoft.Messenger.IMessengerAddIn.

Visual C#

   14 public class MessengerAddIn : IMessengerAddIn

   15 {

   16     private MessengerClient MeClient;

   17 

   18     private System.Collections.Generic.Dictionary<String, Conversation> People =

   19       new Dictionary<string, Conversation>();

Visual Basic

   14 Public Class MessengerAddIn

   15     Implements IMessengerAddIn

   16 

   17     Private MeClient As MessengerClient

   18     Private People As Dictionary(Of String, Conversation) = New Dictionary(Of String, Conversation)()

Any class that implements this interface must also implement its Initialize method. The Initialize method is the first one which gets called when you click Turn on "Events Agent", as in [Fig. 5]; and receives the Windows Live Messenger client as an argument, which is saved in a local variable for use later on. One should use the initialization to properly change the friendly name, description and personal status message. The project only uses one messenger event, which is fired up each time a new text message arrives; in case you decide to use other events, just uncomment them.

Visual C#

   25 public void Initialize(MessengerClient messenger)

   26 {

   27     MeClient = messenger;

   28 

   29     //Messenger Add-In friendly texts

   30     MeClient.AddInProperties.FriendlyName = Resources.MessengerAddIn_FriendlyName;

   31     MeClient.AddInProperties.Description = Resources.MessengerAddIn_Description;

   32     MeClient.AddInProperties.PersonalStatusMessage = Resources.MessengerAddIn_PersonalStatusMessage;

   33 

   34     //Messenger text events

   35     MeClient.IncomingTextMessage +=

   36         new EventHandler<IncomingTextMessageEventArgs>(this.IncomingTextMessage);

   37     //MeClient.OutgoingTextMessage +=

   38     //    new EventHandler<OutgoingTextMessageEventArgs>(this.OutgoingTextMessage);

   39     //MeClient.ShowOptionsDialog += new EventHandler(this.ShowOptionsDialog);

   40 }

Visual Basic

   24 Public Sub Initialize(ByVal messenger As MessengerClient) Implements IMessengerAddIn.Initialize

   25     MeClient = messenger

   26 

   27     'Messenger Add-In friendly texts

   28     MeClient.AddInProperties.FriendlyName = My.Resources.MessengerAddIn_FriendlyName

   29     MeClient.AddInProperties.Description = My.Resources.MessengerAddIn_Description

   30     MeClient.AddInProperties.PersonalStatusMessage = My.Resources.MessengerAddIn_PersonalStatusMessage

   31 

   32     'Messenger text events

   33     AddHandler MeClient.IncomingTextMessage, AddressOf Me.IncomingTextMessage

   34     'AddHandler MeClient.OutgoingTextMessage, AddressOf Me.OutgoingTextMessage

   35     'AddHandler MeClient.ShowOptionsDialog, AddressOf Me.ShowOptionsDialog

   36 End Sub

For handling incoming text messages, I have written and registered my own event handler. The event is raised regardless who sent the text message, so we need a way to track users: have we talked with a user before, what kind of query has the user previously made etc.; this is achieved using a dictionary having the user's unique id as key, and an instance of the Conversation class as value. On line 58 (in C#) or 52 (in VB) you can see how easy it is to detect the user's status - nothing complicated. Take care at: Windows Live Messenger Add-Ins ca only send a (one) text message as a reply to a (one) incoming text message, that's why I always use a return statement after I send a message (an exception is raised when you try to send multiple messages). I don't like this issue, you don't like it, but it keeps me and you secure from malicious add-ins.

Visual C#

   47 private void IncomingTextMessage(object sender, IncomingTextMessageEventArgs e)

   48 {

   49     // Check if the text message comes from a new user;

   50     // with whom we have not talked before.

   51     if (!People.ContainsKey(e.UserFrom.UniqueId))

   52     {

   53         People.Add(e.UserFrom.UniqueId, new Conversation());

   54         string message = String.Format(CultureInfo.CurrentCulture,

   55             Resources.OTextMessage_Welcome, e.UserFrom.FriendlyName);

   56 

   57         // Verify the status of the user.

   58         if (e.UserFrom.Status != UserStatus.Busy)

   59             message += Resources.OTextMessage_EndWelcome;

   60 

   61         MeClient.SendTextMessage(message, e.UserFrom);

   62         return;

   63     }

   64 

   65     // Pull the previous conversation with the user.

   66     Conversation person = null;

   67     People.TryGetValue(e.UserFrom.UniqueId, out person);

Visual Basic

   43 Private Sub IncomingTextMessage(ByVal sender As Object, ByVal e As IncomingTextMessageEventArgs)

   44     ' Check if the text message comes from a new user;

   45     ' with whom we have not talked before.

   46     If Not People.ContainsKey(e.UserFrom.UniqueId) Then

   47         People.Add(e.UserFrom.UniqueId, New Conversation)

   48         Dim message As String = String.Format(CultureInfo.CurrentCulture, _

   49             My.Resources.OTextMessage_Welcome, e.UserFrom.FriendlyName)

   50 

   51         ' Verify the status of the user.

   52         If (e.UserFrom.Status <> UserStatus.Busy) Then

   53             message += My.Resources.OTextMessage_EndWelcome

   54         End If

   55 

   56         MeClient.SendTextMessage(message, e.UserFrom)

   57         Return

   58     End If

   59 

   60     ' Pull the previous conversation with the user.

   61     Dim person As Conversation = Nothing

   62     People.TryGetValue(e.UserFrom.UniqueId, person)

What happens when a friend of yours sends you a show events text message and the add-in intercepts it? When a text message validates the condition from line 78-79 (in C#) or 72-73 (in VB), SharePointWrapper.GetEvents (we'll talk about this later on) is called and used to complete the request; plus, a friendly text message that reflects the previous call is built and sent back. You must always send the reply to the user from which the request came from; otherwise, an exception will be thrown.

Visual C#

   78 if (e.TextMessage.StartsWith(Resources.ITextMessage_AllEvents,

   79     true, CultureInfo.CurrentCulture))

   80 {

   81     try

   82     {

   83         person.Events = SharePointWrapper.GetEvents(

   84             new Uri(Resources.SharePoint_ServerName), SharePointWrapper.QuerySpan.All);

   85 

   86         MeClient.SendTextMessage(ReplyGetEvents(person.Events), e.UserFrom);

   87         return;

   88     }

   89     catch (WebException)

   90     {

   91         MeClient.SendTextMessage(Resources.OTextMessage_Exception, e.UserFrom);

   92         return;

   93     }

   94 }

Visual Basic

   72 If e.TextMessage.StartsWith(My.Resources.ITextMessage_AllEvents, _

   73    True, CultureInfo.CurrentCulture) Then

   74     Try

   75         person.Events = SharePointWrapper.GetEvents( _

   76             New Uri(My.Resources.SharePoint_ServerName), SharePointWrapper.QuerySpan.All)

   77 

   78         MeClient.SendTextMessage(ReplyGetEvents(person.Events), e.UserFrom)

   79         Return

   80     Catch ex As WebException

   81         MeClient.SendTextMessage(My.Resources.OTextMessage_Exception, e.UserFrom)

   82         Return

   83     End Try

   84 End If

The code that is executed when a user requests help, show events that occur in a week, month or year time is either similar with the previous code, or too simple and I'm not going to display it here. Instead, I'll show you what happens when a user requests to register for an event. A text message that would trigger the code looks like register for event 2; all I did was get the id from the incoming message and processed the request; the rest you'll understand as it looks almost the same. When a user wants to perform an action on an invalid event, the add-in notifies him or her of the mistake.

Visual C#

  184 if (e.TextMessage.StartsWith(Resources.ITextMessage_RegisterForEvent,

  185     true, CultureInfo.CurrentCulture))

  186 {

  187     try

  188     {

  189         int id = Convert.ToInt32(e.TextMessage.ToLower().Replace(

  190             Resources.ITextMessage_RegisterForEvent, "").Trim(), CultureInfo.CurrentCulture);

  191 

  192         MeClient.SendTextMessage(ReplyRegisterForEvent(

  193             person.Events.Data.GetListItem(id), e.UserFrom.Email), e.UserFrom);

  194         return;

  195     }

  196     catch (ArgumentNullException)

  197     {

  198         MeClient.SendTextMessage(Resources.OTextMessage_InvalidEvent, e.UserFrom);

  199         return;

  200     }

  201     catch (WebException)

  202     {

  203         MeClient.SendTextMessage(Resources.OTextMessage_Exception, e.UserFrom);

  204         return;

  205     }

  206     catch (Exception)

  207     {

  208         MeClient.SendTextMessage(Resources.OTextMessage_Exception, e.UserFrom);

  209         return;

  210     }

  211 }

Visual Basic

  153 If e.TextMessage.StartsWith(My.Resources.ITextMessage_RegisterForEvent, _

  154   True, CultureInfo.CurrentCulture) Then

  155     Try

  156         Dim id As Integer = Convert.ToInt32(e.TextMessage.ToLower().Replace( _

  157             My.Resources.ITextMessage_RegisterForEvent, "").Trim, CultureInfo.CurrentCulture)

  158 

  159         MeClient.SendTextMessage(ReplyRegisterForEvent( _

  160             person.Events.Data.GetListItem(id), e.UserFrom.Email), e.UserFrom)

  161         Return

  162     Catch ex As ArgumentNullException

  163         MeClient.SendTextMessage(My.Resources.OTextMessage_InvalidEvent, e.UserFrom)

  164         Return

  165     Catch ex As WebException

  166         MeClient.SendTextMessage(My.Resources.OTextMessage_Exception, e.UserFrom)

  167         Return

  168     Catch ex As Exception

  169         MeClient.SendTextMessage(My.Resources.OTextMessage_Exception, e.UserFrom)

  170         Return

  171     End Try

  172 End If

Requests to unregister for events and show details for an event are similar and there is no reason to talk about them. Each task (request) uses its own method to create its reply - that's were many conditions are checked and a proper response is built. Be sure to download the sample, which is available in both languages and discover the hidden aspects for yourself.

Looking at the Windows SharePoint Services Wrapper Code

Most Windows SharePoint Services web services use as arguments Collaborative Application Markup Language (CAML), so they're quite flexible. In case you want to do more with SharePoint than I have done here, you'll have to learn CAML. It's important to notice that the authentication to the web service, located on line 46 (in C#) or 43 (in VB), is made through the GetSharePointCredentials method; don't forget to supply your credentials as described within the body of the method.

Depending on the time span, the appropriate CAML query is built - it checks for a greater date than now and a less date than the one specified in the query span.
Right now I can think of three methods to process the response from the web services: navigate your way using xpaths, work with datasets or... deserialize the response into objects. Although you have to write more code, deserialization brings a huge benefit - it's easier to work with later on; three classes were used to achieve that: SharePointListRoot, SharePointListData and SharePointListItem.

Visual C#

   37 public static SharePointListRoot GetEvents(Uri websiteUri, QuerySpan span)

   38 {

   39     if (websiteUri == null)

   40         throw new ArgumentNullException("websiteUri");

   41     try

   42     {

   43         // Work with the Lists web service

   44         Lists wsLists = new Lists();

   45         wsLists.Url = websiteUri.ToString() + "/_vti_bin/Lists.asmx";

   46         wsLists.Credentials = GetSharePointCredentials();

   47 

   48         XmlDocument xDocument = new XmlDocument();

   49         XmlNode xQuery = xDocument.CreateNode(XmlNodeType.Element, "Query", "");

   50         XmlNode xViewFields = xDocument.CreateNode(XmlNodeType.Element, "ViewFields", "");

   51         XmlNode xQueryOptions = xDocument.CreateNode(XmlNodeType.Element, "QueryOptions", "");

   52         string viewName = "";

   53         string rowLimit = "0";

   54 

   55         // CAML (Collaborative Application Markup Language)

   56         switch (span)

   57         {

   58             case QuerySpan.All:

   59                 xQuery.InnerXml =

   60                     "<Where>" +

   61                     "<Gt><FieldRef Name='EventDate'/><Value Type='DateTime'>" +

   62                     DateTime.UtcNow.ToString("s", CultureInfo.InvariantCulture) + "</Value></Gt>" +

   63                     "</Where>";

   64                 break;

   65             case QuerySpan.InAYear:

   66                 xQuery.InnerXml =

   67                     "<Where><And>" +

   68                     "<Gt><FieldRef Name='EventDate'/><Value Type='DateTime'>" +

   69                     DateTime.UtcNow.ToString("s", CultureInfo.InvariantCulture) + "</Value></Gt>" +

   70                     "<Lt><FieldRef Name='EventDate'/><Value Type='DateTime'>" +

   71                     DateTime.UtcNow.AddYears(1).ToString("s", CultureInfo.InvariantCulture) + "</Value></Lt>" +

   72                     "</And></Where>";

   73                 break;

   74             case QuerySpan.InAMonth:

   75                 xQuery.InnerXml =

   76                     "<Where><And>" +

   77                     "<Gt><FieldRef Name='EventDate'/><Value Type='DateTime'>" +

   78                     DateTime.UtcNow.ToString("s", CultureInfo.InvariantCulture) + "</Value></Gt>" +

   79                     "<Lt><FieldRef Name='EventDate'/><Value Type='DateTime'>" +

   80                     DateTime.UtcNow.AddMonths(1).ToString("s", CultureInfo.InvariantCulture) + "</Value></Lt>" +

   81                     "</And></Where>";

   82                 break;

   83             case QuerySpan.InAWeek:

   84                 xQuery.InnerXml =

   85                     "<Where><And>" +

   86                     "<Gt><FieldRef Name='EventDate'/><Value Type='DateTime'>" +

   87                     DateTime.UtcNow.ToString("s", CultureInfo.InvariantCulture) + "</Value></Gt>" +

   88                     "<Lt><FieldRef Name='EventDate'/><Value Type='DateTime'>" +

   89                     DateTime.UtcNow.AddDays(7).ToString("s", CultureInfo.InvariantCulture) + "</Value></Lt>" +

   90                     "</And></Where>";

   91                 break;

   92         };

   93 

   94         XmlNode xResult = wsLists.GetListItems(Resources.SharePoint_EventsListName,

   95             viewName, xQuery, xViewFields, rowLimit, xQueryOptions);

   96 

   97         // Create an SharePointListRoot object from the web response.

   98         XmlTextReader xReader = new XmlTextReader(xResult.OuterXml, XmlNodeType.Element, null);

   99         XmlSerializer xSerializer = new XmlSerializer(new SharePointListRoot().GetType());

  100 

  101         return (SharePointListRoot)xSerializer.Deserialize(xReader);

  102     }

  103     catch (WebException)

  104     {

  105         throw;

  106     }

  107 }

Visual Basic

   35 Public Shared Function GetEvents(ByVal websiteUri As Uri, ByVal span As QuerySpan) As SharePointListRoot

   36     If (websiteUri Is Nothing) Then

   37         Throw New ArgumentNullException("websiteUri")

   38     End If

   39     Try

   40         ' Work with the Lists web service

   41         Dim wsLists As Lists = New Lists()

   42         wsLists.Url = websiteUri.ToString() & "/_vti_bin/Lists.asmx"

   43         wsLists.Credentials = GetSharePointCredentials()

   44 

   45         Dim xDocument As XmlDocument = New XmlDocument()

   46         Dim xQuery As XmlNode = xDocument.CreateNode(XmlNodeType.Element, "Query", "")

   47         Dim xViewFields As XmlNode = xDocument.CreateNode(XmlNodeType.Element, "ViewFields", "")

   48         Dim xQueryOptions As XmlNode = xDocument.CreateNode(XmlNodeType.Element, "QueryOptions", "")

   49         Dim viewName As String = ""

   50         Dim rowLimit As String = "0"

   51 

   52         ' CAML (Collaborative Application Markup Language)

   53         Select Case (span)

   54             Case QuerySpan.All

   55                 xQuery.InnerXml = _

   56                     "<Where>" & _

   57                     "<Gt><FieldRef Name='EventDate'/><Value Type='DateTime'>" & _

   58                     DateTime.UtcNow.ToString("s", CultureInfo.InvariantCulture) & "</Value></Gt>" & _

   59                     "</Where>"

   60             Case QuerySpan.InAYear

   61                 xQuery.InnerXml = _

   62                     "<Where><And>" & _

   63                     "<Gt><FieldRef Name='EventDate'/><Value Type='DateTime'>" & _

   64                     DateTime.UtcNow.ToString("s", CultureInfo.InvariantCulture) & "</Value></Gt>" & _

   65                     "<Lt><FieldRef Name='EventDate'/><Value Type='DateTime'>" & _

   66                     DateTime.UtcNow.AddYears(1).ToString("s", CultureInfo.InvariantCulture) & "</Value></Lt>" & _

   67                     "</And></Where>"

   68             Case QuerySpan.InAMonth

   69                 xQuery.InnerXml = _

   70                     "<Where><And>" & _

   71                     "<Gt><FieldRef Name='EventDate'/><Value Type='DateTime'>" & _

   72                     DateTime.UtcNow.ToString("s", CultureInfo.InvariantCulture) & "</Value></Gt>" & _

   73                     "<Lt><FieldRef Name='EventDate'/><Value Type='DateTime'>" & _

   74                     DateTime.UtcNow.AddMonths(1).ToString("s", CultureInfo.InvariantCulture) & "</Value></Lt>" & _

   75                     "</And></Where>"

   76             Case QuerySpan.InAWeek

   77                 xQuery.InnerXml = _

   78                     "<Where><And>" & _

   79                     "<Gt><FieldRef Name='EventDate'/><Value Type='DateTime'>" & _

   80                     DateTime.UtcNow.ToString("s", CultureInfo.InvariantCulture) & "</Value></Gt>" & _

   81                     "<Lt><FieldRef Name='EventDate'/><Value Type='DateTime'>" & _

   82                     DateTime.UtcNow.AddDays(7).ToString("s", CultureInfo.InvariantCulture) & "</Value></Lt>" & _

   83                     "</And></Where>"

   84         End Select

   85 

   86         Dim xResult As XmlNode = wsLists.GetListItems(My.Resources.SharePoint_EventsListName, _

   87             viewName, xQuery, xViewFields, rowLimit, xQueryOptions)

   88 

   89         ' Create an SharePointListRoot object from the web response.

   90         Dim xReader As XmlTextReader = New XmlTextReader(xResult.OuterXml, XmlNodeType.Element, Nothing)

   91         Dim xSerializer As XmlSerializer = New XmlSerializer(New SharePointListRoot().GetType())

   92 

   93         Return CType(xSerializer.Deserialize(xReader), SharePointListRoot)

   94     Catch ex As WebException

   95         Throw

   96     End Try

   97 End Function

When you want to add a new attendee, you basically need to add a new item to the Attendees list. A trick to notice: every site has its own web services, so you'll need to change the URL of the web service to target the web service of the workspace site that contains the Attendees list you want to modify; otherwise, a null reference exception will be raised. For example, a Team Web Site is not a Meeting Workspace and does not have a meetings web service. Always take care at this issue when you work with SharePoint web services.

Visual C#

  115 public static bool AddAttendee(Uri workspaceUri, string email)

  116 {

  117     if (workspaceUri == null)

  118         throw new ArgumentNullException("workspaceUri");

  119     try

  120     {

  121         // Work with the Lists web service

  122         Lists wsLists = new Lists();

  123         wsLists.Url = workspaceUri.GetLeftPart(UriPartial.Path) + "/_vti_bin/Lists.asmx";

  124         wsLists.Credentials = GetSharePointCredentials();

  125 

  126         XmlDocument xDocument = new XmlDocument();

  127         XmlNode xUpdates = xDocument.CreateNode(XmlNodeType.Element, "Batch", "");

  128 

  129         // CAML (Collaborative Application Markup Language)

  130         xUpdates.InnerXml =

  131             "<Method ID='1' Cmd='New'>" +

  132             "<Field Name='Title'>" + email + "</Field>" +

  133             "<Field Name='Status'>Accepted</Field>" +

  134             "<Field Name='Attendance'>Optional</Field>" +

  135             "</Method>";

  136 

  137         XmlNode xResult = wsLists.UpdateListItems(Resources.SharePoint_AttendeesListName,

  138             xUpdates);

  139 

  140         // Since we are performing only one action (a new), we get as response only one node;

  141         // we check to see if there was an error completing the task.

  142         return (xResult.ChildNodes[0].ChildNodes[0].InnerText == "0x00000000");

  143     }

  144     catch (WebException)

  145     {

  146         throw;

  147     }

  148 }

Visual Basic

  105 Public Shared Function AddAttendee(ByVal workspaceUri As Uri, ByVal email As String) As Boolean

  106     If (workspaceUri Is Nothing) Then

  107         Throw New ArgumentNullException("workspaceUri")

  108     End If

  109     Try

  110         ' Work with the Lists web service

  111         Dim wsLists As Lists = New Lists()

  112         wsLists.Url = workspaceUri.GetLeftPart(UriPartial.Path) & "/_vti_bin/Lists.asmx"

  113         wsLists.Credentials = GetSharePointCredentials()

  114 

  115         Dim xDocument As XmlDocument = New XmlDocument()

  116         Dim xUpdates As XmlNode = xDocument.CreateNode(XmlNodeType.Element, "Batch", "")

  117 

  118         ' CAML (Collaborative Application Markup Language)

  119         xUpdates.InnerXml = _

  120             "<Method ID='1' Cmd='New'>" & _

  121             "<Field Name='Title'>" & email & "</Field>" & _

  122             "<Field Name='Status'>Accepted</Field>" & _

  123             "<Field Name='Attendance'>Optional</Field>" & _

  124             "</Method>"

  125 

  126         Dim xResult As XmlNode = wsLists.UpdateListItems(My.Resources.SharePoint_AttendeesListName, _

  127             xUpdates)

  128 

  129         ' Since we are performing only one action (a new), we get as response only one node;

  130         ' we check to see if there was an error completing the task.

  131         Return (xResult.ChildNodes(0).ChildNodes(0).InnerText = "0x00000000")

  132     Catch ex As WebException

  133         Throw

  134     End Try

  135 End Function

When a user wants to re-register for an event, we won't add it again to the Attendees list, because we would have duplicate items; instead, we are going to use the Meetings web service to set its response back from Declined to Accepted, or from Accepted to Declined, in the unregister case. Again, the URLs targeting the web services are set as they should be. This is one of the places you need to modify in order to make it work with non-recurrent events; check the comments within the code for more details.

Visual C#

  198 public static void SetAttendee(Uri workspaceUri, string email, AttendeeResponse response)

  199 {

  200     if (workspaceUri == null)

  201         throw new ArgumentNullException("workspaceUri");

  202     try

  203     {

  204         // Work with the Lists web service

  205         Lists wsLists = new Lists();

  206         wsLists.Url = workspaceUri.GetLeftPart(UriPartial.Path) + "/_vti_bin/Lists.asmx";

  207         wsLists.Credentials = GetSharePointCredentials();

  208 

  209         // Also work with the Meetings web service

  210         Meetings wsMeetings = new Meetings();

  211         wsMeetings.Url = workspaceUri.GetLeftPart(UriPartial.Path) + "/_vti_bin/Meetings.asmx";

  212         wsMeetings.Credentials = GetSharePointCredentials();

  213 

  214         XmlDocument xDocument = new XmlDocument();

  215         XmlNode xQuery = xDocument.CreateNode(XmlNodeType.Element, "Query", "");

  216         XmlNode xViewFields = xDocument.CreateNode(XmlNodeType.Element, "ViewFields", "");

  217         XmlNode xQueryOptions = xDocument.CreateNode(XmlNodeType.Element, "QueryOptions", "");

  218         string viewName = "";

  219         string rowLimit = "0";

  220 

  221         // For non-recurring events, SharePoint sets the default id to 1;

  222         // so it's safe to query only for the meeting with the instance id equal to 1.

  223         xQuery.InnerXml =

  224             "<Where>" +

  225             "<Eq><FieldRef Name='ID'/><Value Type='Counter'>" + 1 + "</Value></Eq>" +

  226             "</Where>";

  227 

  228         XmlNode xResult = wsLists.GetListItems(Resources.SharePoint_MeetingSeriesListName,

  229             viewName, xQuery, xViewFields, rowLimit, xQueryOptions);

  230 

  231         // We know the instance id, but we need the unique id of the meeting.

  232         string uid = xResult.ChildNodes[1].ChildNodes[1].Attributes["ows_EventUID"].InnerText;

  233 

  234         wsMeetings.SetAttendeeResponse(email, 0, uid, 1, DateTime.UtcNow, DateTime.UtcNow, response);

  235     }

  236     catch (WebException)

  237     {

  238         throw;

  239     }

  240 }

Visual Basic

  182 Public Shared Sub SetAttendee(ByVal workspaceUri As Uri, ByVal email As String, ByVal response As AttendeeResponse)

  183     If (workspaceUri Is Nothing) Then

  184         Throw New ArgumentNullException("workspaceUri")

  185     End If

  186     Try

  187         ' Work with the Lists web service

  188         Dim wsLists As Lists = New Lists()

  189         wsLists.Url = workspaceUri.GetLeftPart(UriPartial.Path) + "/_vti_bin/Lists.asmx"

  190         wsLists.Credentials = GetSharePointCredentials()

  191 

  192         ' Also work with the Meetings web service

  193         Dim wsMeetings As Meetings = New Meetings()

  194         wsMeetings.Url = workspaceUri.GetLeftPart(UriPartial.Path) + "/_vti_bin/Meetings.asmx"

  195         wsMeetings.Credentials = GetSharePointCredentials()

  196 

  197         Dim xDocument As XmlDocument = New XmlDocument()

  198         Dim xQuery As XmlNode = xDocument.CreateNode(XmlNodeType.Element, "Query", "")

  199         Dim xViewFields As XmlNode = xDocument.CreateNode(XmlNodeType.Element, "ViewFields", "")

  200         Dim xQueryOptions As XmlNode = xDocument.CreateNode(XmlNodeType.Element, "QueryOptions", "")

  201         Dim viewName As String = ""

  202         Dim rowLimit As String = "0"

  203 

  204         ' For non-recurring events, SharePoint sets the default id to 1;

  205         ' so it's safe to query only for the meeting with the instance id equal to 1.

  206         xQuery.InnerXml = _

  207             "<Where>" & _

  208             "<Eq><FieldRef Name='ID'/><Value Type='Counter'>" & 1 & "</Value></Eq>" & _

  209             "</Where>"

  210 

  211         Dim xResult As XmlNode = wsLists.GetListItems(My.Resources.SharePoint_MeetingSeriesListName, _

  212             viewName, xQuery, xViewFields, rowLimit, xQueryOptions)

  213 

  214         ' We know the instance id, but we need the unique id of the meeting.

  215         Dim uid As String = xResult.ChildNodes(1).ChildNodes(1).Attributes("ows_EventUID").InnerText

  216 

  217         wsMeetings.SetAttendeeResponse(email, 0, uid, 1, DateTime.UtcNow, DateTime.UtcNow, response)

  218     Catch ex As WebException

  219         Throw

  220     End Try

  221 End Sub

This concludes our overview of the code. Not every line of code is displayed here, but the number preceding each line of code is the actual line number from the sample code files; so, if you have any questions about the code you'll be able to find it in a snap.

It Works on Windows Live Messenger Network

Although you can talk with your Yahoo! friends using text messages, Windows Live Messenger Add-Ins do not work with Yahoo! contacts; in other words, the add-in won't receive the text messages from them, although you see them, so it does not have how to send text messages back. You'll have to find another suitable alternative for them to register for your events.
The usual way of testing a messenger add-in is to ask a friend of yours to send you text messages. Of course, you can have multiple Windows Live Ids and use Virtual PC to install and run more messengers; but hey, I did not want to do that. So, at one moment I had an interesting idea: why not make Encarta Instant Answers (which is a BOT) make it send me some text messages that matched my patterns (e.g. show events). In case you say to Encarta Instant Answers show events, it will respond with something like: Why must I show events? (the response you get may differ). It should have worked, but unfortunately it's the same as in the Yahoo! case.
The good news is that it works with your friends that are connected to the Windows Live Messenger Network using Pocket MSN. I was curious and tried MSN Messenger on a device running Windows Mobile 5.1 Pocket PC Phone Edition and it worked fine; I was able to interact with the add-in. Do not get me wrong, add-ins can be loaded only on a Windows Live Messenger, and not on a Pocket MSN.
And a dummy remark: people who use Windows Live Messenger have no problems using Windows Live Messenger Add-Ins.

Demo

In the end, I just want to show you how it works, because so far we have only talked around the subject without seeing it in action. Return to your Personal Settings > Add-ins, as illustrated in [Fig. 1] and click Add to Messenger; navigate to your sample installation folder and select the EventsAgent.MessengerAddIn.dll assembly. After completing this, the combo should have the Events Agent value; optionally, you may want to check the Automatically turn on this add-in when my status is anything other than Online or Appear Offline box. By clicking Turn on "Events Agent", like in [Fig. 6] you are activating your add-in and allowing it to interact with your friends, and this is what we expect it to do!

 
[Figure 5]

Before I give it a spin, please check my sample events that will be used to demonstrate the project. In [Fig. 6] you can see that I am using three events that occur at different time intervals, and all of them are using meeting workspaces to allow people to enroll in events as attendees.


[Figure 6]

It's spinning... in [Fig. 7] you can watch my conversation with one of my friends. A golden bar tells us that we have almost reached our goal; you'll be able to recognize text messages sent by the add-in when a message begins with a Paul-Valentin Borza's add-in "Events Agent" says: text.


[Figure 7]

Oh, you're right it's not my conversation - I haven't written a single word, or character: our project, the Windows Live Messenger Add-In Events Agent has done all the work for me. And that's great because that means my work here is done! Don't forget to look at the result: there are two attendees now.


[Figure 8]

Conclusion

I'm happy that you learned how to use and combine two powerful technologies: Windows Live Messenger Add-Ins and Windows SharePoint Services. You have built your own events agent that will serve you every time you start your favorite messenger. You'll now be able to show it to your friends while talking to them, from the same windows you have always did before! I'm sure they'll be quite surprised... mine were. You can try my events agent on my Windows Live Messenger Id windowslive@borza.ro; you'll even be able to talk with me in case you need any assistance.
Being at my second Coding4Fun article, I feel great when I can help people achieve a greater potential that has always been within their reach; and all of these happening while simply coding for fun. Thanks to the Microsoft Academic Program Team Romania for support.

Improvements

I encourage you to extend my work by:

Bio

Paul-Valentin Borza is in its second year of study at the Babes-Bolyai University of Cluj-Napoca, Faculty of Mathematics and Computer Science. Since 2005, he is involved in the Microsoft Student Partners - Microsoft Academic Program Romania. He can be reached through his web site at www.borza.ro.

Tags:

Follow the Discussion

  • the spark and the pioneerthe spark and the pioneer

    My second (2nd!) Coding4Fun article just got published on MSDN Coding4Fun! I want to thank Todi Pruteanu...

  • Chi WangChi Wang

    Could u tell me how to change the nickname?

  • yassiryassir

    i couldn't reach the : HKEY_CURRENT_USER\Software\Microsoft\MSNMessenger\AddInFeatureEnabled by the way i m using WLM 8.5 any idea????

  • BillBill

    And why don't some channels show up, like mine!? ,

  • no1no1

    I cant find any MessengerClient.dll file on my PC.

    I am using Windows Live Messenger V14. Are these addons still supported?

Remove this comment

Remove this thread

close

Comments Closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums,
or Contact Us and let us know.