Leveraging Exchange 2007 Web Services to Find Open Conference Rooms

Do you enjoy trying to find free conference rooms for meetings?  If you do, you’re probably a psycho who eats puppies.  I work at the headquarter campus of a large company with dozens upon dozens of buildings.  Apparently the sole function of my company is to hold meetings since it’s easier to find Hoffa’s body than a free conference room at 2 o’clock in the afternoon.  So what’s an architect with a free hour to do?  Build a solution!

I did NOT build a solution to free up more conference rooms.  That involves firing lots of people.  Not my call.  But, I DID build a solution to browse all the conference rooms of a particular building (or buildings) and show me the results all at once.  MS Exchange 2007 has a pretty nice web service API located at https://%5Bserver%5D/ews/exchange.asmx.

2010.4.23conf

The service operation I was interested in was GetUserAvailability.  This guy takes in a list of users (or rooms if they are addressable) and a time window and tells if you the schedule for that user/room.

2010.4.23conf2

Note that I’m including the full solution package for download below, so I’ll only highlight a few code snippets here.  My user interface looks like this:

2010.4.23conf3

I take in a building number (or numbers), and have a calendar for picking which day you’re interested in.  I then have a time picker, and duration list.  Finally I have a ListVIew control to show the rooms that are free at the chosen time.  A quick quirk to note: so if I say show me rooms that are free from 10AM-11AM, someone isn’t technically free if they have a meeting that ends at 10 or starts at 11.  The endpoints of the other meeting overlap with the “free” time.  So, I had to add one second to the request time, and subtract one second from the end time to make this work right.  Why am I telling you this?  Because the shortest window that you can request via this API is 30 minutes.  So if you have meeting times of 30 minutes or less, my “second-subtraction” won’t work since the ACTUAL duration request is 28 minutes and 58 seconds.  This is why I hard code the duration window so that users can’t choose meetings that are less than an hour.  If you have a more clever way to solve this, feel free!

Before I get into the code that makes the magic happen, I want to highlight how I stored the building-to-room mapping.  I found no easy way to query “show me all conference rooms for a particular building” via any service operation, so, I built an XML configuration file to do this.  Inside the configuration file, I store the building number, the conference room “friendly” name, and the Exchange email address of the room.

2010.4.23conf4

I’ll use LINQ in code to load this XML file up and pull out only the rooms that the user requested.  On to the code!

I defined a “Rooms” class and a “RoomList” class which consists of a List of Rooms.  When you pass in a building number, the RoomList object yanks the rooms from the configuration file, and does LINQ query to filter the XML nodes and populate a list of rooms that match the building (or buildings) selected by the user.

class Room
{
    public string Name { get; set; }
    public string Email { get; set; }
    public string Bldg { get; set; }

}

class RoomList : List<Room>
{
    public void Load(string bldg)
    {
        XDocument doc = XDocument.Load(HttpContext.Current.Server.MapPath("RoomMapping.xml"));

        var query = from XElem in doc.Descendants("room")
                    where bldg.Contains(XElem.Element("bldg").Value)
                    select new Room
                    {
                        Name = XElem.Element("name").Value,
                        Bldg = XElem.Element("bldg").Value,
                        Email = XElem.Element("email").Value
                    };
        this.Clear();
        AddRange(query);
    }
}

With this in place, we can populate the “Find Rooms” button action.  The full code is below, and reasonably commented.

protected void btnFindRooms_Click(object sender, EventArgs e)
    {
        //note: meetings must be 1 hour or more

        //load up rooms from configuration file
        RoomList rooms = new RoomList();
        rooms.Load(txtBldg.Text);

        //create string list to hold free room #s
        List<string> freeRooms = new List<string>();

        //create service proxy
        ExchangeSvcWeb.ExchangeServiceBinding service =
            new ExchangeSvcWeb.ExchangeServiceBinding();

        //define credentials and target URL
        ICredentials c = CredentialCache.DefaultNetworkCredentials;
        service.Credentials = c;
        service.Url = "https://[SERVER]/ews/exchange.asmx";

        //create request object
        ExchangeSvcWeb.GetUserAvailabilityRequestType request =
            new ExchangeSvcWeb.GetUserAvailabilityRequestType();

        //add mailboxes to search from building/room mapping file
        ExchangeSvcWeb.MailboxData[] mailboxes = new ExchangeSvcWeb.MailboxData[rooms.Count];
        for (int i = 0; i < rooms.Count; i++)
        {
            mailboxes[i] = new ExchangeSvcWeb.MailboxData();
            ExchangeSvcWeb.EmailAddress addr = new ExchangeSvcWeb.EmailAddress();
            addr.Address = rooms[i].Email;
            addr.Name = string.Empty;

            mailboxes[i].Email = addr;
        }
        //add mailboxes to request
        request.MailboxDataArray = mailboxes;

        //Set TimeZone stuff
        request.TimeZone = new ExchangeSvcWeb.SerializableTimeZone();
        request.TimeZone.Bias = 480;
        request.TimeZone.StandardTime = new ExchangeSvcWeb.SerializableTimeZoneTime();
        request.TimeZone.StandardTime.Bias = 0;
        request.TimeZone.StandardTime.DayOfWeek = ExchangeSvcWeb.DayOfWeekType.Sunday.ToString();
        request.TimeZone.StandardTime.DayOrder = 1;
        request.TimeZone.StandardTime.Month = 11;
        request.TimeZone.StandardTime.Time = "02:00:00";
        request.TimeZone.DaylightTime = new ExchangeSvcWeb.SerializableTimeZoneTime();
        request.TimeZone.DaylightTime.Bias = -60;
        request.TimeZone.DaylightTime.DayOfWeek = ExchangeSvcWeb.DayOfWeekType.Sunday.ToString();
        request.TimeZone.DaylightTime.DayOrder = 2;
        request.TimeZone.DaylightTime.Month = 3;
        request.TimeZone.DaylightTime.Time = "02:00:00";

        //build string to expected format: 4/21/2010 04:00:00 PM
        string startString = calStartDate.SelectedDate.ToString("MM/dd/yyyy");
        startString += " " + ddlHour.SelectedValue + ":" + ddlMinute.SelectedValue + ":00 " + ddlTimeOfDay.SelectedValue;
        DateTime startDate = DateTime.Parse(startString);
        DateTime endDate = startDate.AddHours(Convert.ToInt32(ddlDuration.SelectedValue));

        //identify the time to compare if the user is free/busy
        ExchangeSvcWeb.Duration duration = new ExchangeSvcWeb.Duration();
        //add second to look for truly "free" time
        duration.StartTime = startDate.AddSeconds(1);
        //subtract second
        duration.EndTime = endDate.AddSeconds(-1);

        // Identify the options for comparing free/busy
        ExchangeSvcWeb.FreeBusyViewOptionsType viewOptions =
            new ExchangeSvcWeb.FreeBusyViewOptionsType();
        viewOptions.TimeWindow = duration;
        viewOptions.RequestedView = ExchangeSvcWeb.FreeBusyViewType.Detailed;
        viewOptions.RequestedViewSpecified = true;
        request.FreeBusyViewOptions = viewOptions;

        //call service!
        ExchangeSvcWeb.GetUserAvailabilityResponseType response =
            service.GetUserAvailability(request);

        //loop through responses for EACH room
        for (int i = 0; i < response.FreeBusyResponseArray.Length; i++)
        {
            //if there is a result for the room
            if (response.FreeBusyResponseArray[i].FreeBusyView.CalendarEventArray != null && response.FreeBusyResponseArray[i].FreeBusyView.CalendarEventArray.Length > 0)
            {
                //** conflicts exist
            }
            else  //room is free!
            {
                freeRooms.Add("(" +rooms[i].Bldg + ") " + rooms[i].Name);
            }

        }

        //show list view
        lblResults.Visible = true;

        //bind to room list
        lvAvailableRooms.DataSource = freeRooms;
        lvAvailableRooms.DataBind();

    }

Once all that’s in place, I can run the app, search one or multiple buildings (by separating with a space) and see all the rooms that are free at that particular time.

2010.4.23conf5

I figure that this will save me about 14 seconds per day, so I should pay back my effort to build it some time this year.  Hopefully!  I’m 109% positive that some of you could take this and clean it up (by moving my “room list load” feature to the load event of the page, for example), so have at it.  You can grab the full source code here.

Share



Categories: .NET, General

4 replies

  1. Or package it and give it to an Admin Coordinator and make a friend for life! These guys spend hours doing this…

  2. Yes, my current PM and project coordinator both sent me thank you messages with an excessive number of exclamation points.

  3. hi,
    Im trying to put put together a similar requirement, i have setup a meeting request in a room and when i try to use getuseravailability on the room or even on the attendee email addresses i always get response.FreeBusyResponseArray[i].FreeBusyView.CalendarEventArray as NULL

    Why is this happening.? Please help

  4. Hi, I faced the same problem as Mehesh. Please help

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: