Pieter van der Westhuizen

Calling Yahoo Weather web-service from an Outlook add-in

It’s been a while since my last blog post and today we start the first in a series of exciting blog posts on integrating Microsoft Office applications with web-based services. The web is growing bigger and bigger by the day and there are a wide range of applications and services available on the internet which you can use to provide your users with more features and functionality inside Microsoft Outlook.

In today’s article, we’ll build an Outlook Add-in that displays the current weather conditions and time of day for all recipients of an e-mail using the Yahoo Weather web-service. At the end of this article, our Outlook add-in will resemble the following screenshot:

Outlook add-in integrated with Yahoo web-services


Yahoo Weather RSS

Yahoo has a great lot of various web-services and APIs, and some of them are usual RSS feeds. In order to retrieve the weather information for the mail recipients, we’ll use the Yahoo Weather RSS feed. This RSS feed returns the current weather conditions of a location as well as a 6-day forecast. In essence, it provides the data that is used to generate the following webpage:

Using Yahoo Weather RSS feed to get the current weather as well as a 6-day forecast.

Getting the weather data for a location is relatively straight forward. According to the Yahoo Weather RSS Feed documentation all we need to do is perform an HTTP GET to the http://weather.yahooapis.com/forecastrss URL and pass it two parameters:

  • w for the Where On Earth Identifier or WOEID; and
  • u for the degree unit we need the temperature in, can either be c for Celsius of F for Fahrenheit.

For example, to get the current weather in Celsius and 5 day forecast for Pretoria, South Africa, we’ll use the following URL:

http://weather.yahooapis.com/forecastrss?w=1586638&u=c


Retrieving the Yahoo WOEID for a location

The challenge with the Yahoo Weather RSS Feed is that we cannot simply pass it an address and receive the weather information back, it requires the WOEID parameter. Luckily, we can retrieve the WOEID for an address using the Yahoo GeoPlanet API’s YQL Tables. This service can return all kinds of information about places around the world including the WOEID for the location. In order to get information about Pretoria, South Africa, we’ll need to perform an HTTP GET request to the following URL:

http://query.yahooapis.com/v1/public/yql?q=select * from geo.places where text = ‘Pretoria, South Africa’&format=xml

Note, the format is literally a SQL select statement from the geo.places table. The query also supports a parameter called format, which accepts the following values:

  • xml – Returns the data in XML format; and
  • json – Returns the data in JSON format.

In the returned data, you’ll see a list of WOEID’s where every Country, Province, State or Town has their own unique WOEID.

Returned data

In this case, since we only need the weather for Pretoria and not the entire Gauteng province, we’ll use the 1586638 WOEID value. You will notice that the service also return the latitude and longitude coordinates for the location, which we’ll use shortly.

GeoNames Timezone web-service

One of the requirements of our Outlook Add-in is to also display the local time of the contact alongside their weather information. To do this, we’ll use the GeoNames Timezone web-service. You might remember that we’ve used the GeoNames API before when we created a Microsoft Word App in this blog post – Office UX guidelines for Excel 2013 content apps and Word 2013 task pane apps.

GeoNames is a free web service that provides a wide range of geographical information web services. In order to retrieve the current date and time we’ll need to pass in the latitude and longitude of the location in question. Luckily, the Yahoo geo.places table also contained theses values, so in order to retrieve the local time for Pretoria, South Africa, we can perform a HTTP GET request to the following URL:

http://api.geonames.org/timezoneJSON?lat=-25.745800&lng=28.187590&username=demo

Notice, that we’re calling the timezoneJSON service, which will return the data in JSON format. If you require the data in XML, simply call the timezone service as illustrated below:

http://api.geonames.org/timezone?lat=-25.745800&lng=28.187590&username=demo

The returned data should look similar to the following:

Data returned by the timezone service.

You’ll also notice the username parameter at the end. In order to use the GeoNames web services you need to sign up for a free account and pass in your own username.


Creating the Outlook Add-in

Now that we have some background about the web services we’ll use, lets develop our add-in by creating a new ADX COM Add-in project in Visual Studio using Add-in Express for Office and .net.

Creating the Outlook Add-in in Visual Studio

We’ll create a Visual C# Project and select Microsoft Office 2010 as the minimum supported Office version. Click Next.

Select the programming language and minimum supported Office version.

On the following page, select Microsoft Outlook as the only supported application and complete the “New Microsoft Office COM Add-in” project.

Completing the New Microsoft Office COM Add-in project.

Creating the UI for the Outlook add-in

The add-in will consist of an advanced Outlook region for the MailItem Inspector window. In order to create the region, we first need to add a new ADX Outlook Form item to our Outlook addin project and call it frmWeather.cs

Adding a new ADX Outlook Form item to our Outlook addin project

We’ll need a bit more control over the styling and layout of the UI, so we’ll create a WPF control and host it inside out ADX Outlook Form. Add a new WPF User Control to the Outlook add-in project and call it weatherList.xaml.

Adding a new WPF User Control to the Outlook add-in project.

Change the WPF User Controls’ XAML markup to the following:

<UserControl x:Class="WeatherAddin.weatherList"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300"
             Width="331" Height="Auto">
 
    <UserControl.Resources>
        <DataTemplate x:Key="ItemTemplate">
 
            <StackPanel Orientation="Horizontal">
                <Image Width="52" Height="52" Stretch="Fill" Source="{Binding ImageUri}"/>
                <StackPanel Orientation="Vertical">
                    <StackPanel Orientation="Horizontal">
                        <Label FontWeight="Bold" Content="{Binding Fullname}" />
                        <Label HorizontalAlignment="Right" Content="{Binding CurrentDateTime}" ContentStringFormat="t" />
                    </StackPanel>
                    <Label Content="{Binding FullWeatherDescription}" />
                </StackPanel>
 
            </StackPanel>
 
        </DataTemplate>
    </UserControl.Resources>
 
    <Grid Height="300" VerticalAlignment="Top" HorizontalAlignment="Left" Width="396" Margin="0,0,-65,0" >
        <ListView Margin="10" 
                  Name="lvDataBinding" 
                  BorderThickness="0"
                  ItemTemplate="{StaticResource ItemTemplate}" 
                  ItemsSource="{Binding Contacts}" >
        </ListView>
    </Grid>
 
</UserControl>

The preceding XAML contains a number of stack panels that will display an image of the contact’s current weather conditions as well as 3 labels showing their names, temperature and current time. The list is data bound to a list of objects – we’ll create the object that holds the data shortly.

Switch back to the ADX Outlook Form called frmWeather.cs that we’ve added earlier and drop an ElementHost control onto its design surface. Set its Child property to weatherList, which is the weatherList.xaml WPF User Control we’ve created earlier. The final design for the control should resemble the following screenshot:

Next, add a new ADXOlFormsManager to the AddinModule designer surface by clicking on its button on the designer toolbar.

Add a new ADXOlFormsManager to the AddinModule designer surface.

Add a new item to its Items’ collection and change the following properties of the items:

  • AlwaysShowHeader – True
  • FormClassName – WeatherAddin.frmWeather
  • InspectorItemTypes – Mail
  • InspectorLayout – RightSubpane
  • InspectorMode – Read;Compose


Calling web-services from the Outlook add-in to retrieve the weather and time data

We’ll need to create a class that will make it easier to retrieve and store the weather and time information for each recipient in the e-mail. Add a new class called ContactWeather.cs to your project and add the following properties to it:

public string Fullname { get; set; }
public string City { get; set; }
public string Country { get; set; }
public string WeatherCondition { get; set; }
public int WeatherCode { get; set; }
public int WeatherTemp { get; set; }
public string DegreeUnit { get; set; }
public DateTime CurrentDateTime { get; set; }
public decimal Latitude { get; set; }
public decimal Longitude { get; set; }
public string Woeid { get; set; }
 
public string Location
{
    get { return string.Format("{0},{1}", City, Country); }
}
 
public string ImageUri
{
    get { return string.Format("http://l.yimg.com/a/i/us/we/52/{0}.gif", WeatherCode); }
}
 
public string FullWeatherDescription
{
    get
    {
        return string.Format("{0}°{1} and {2} in {3}"", 
            WeatherTemp, DegreeUnit,   WeatherCondition, Location);
    }
 

The properties are self-explanatory; the ImageUri property will return an image representing the current weather condition of the contact. We’re using the WeatherCode property to build the URL for the image. We’ll add a new method called RetrieveInfo that will accept a string parameter to indicate what degree unit to use for returning temperatures. The code for the method follows below:

public void RetrieveInfo(string degreeUnit = "c")
{
    DegreeUnit = degreeUnit.ToUpper();
    if (String.IsNullOrEmpty(Woeid))
    {
        RetrieveWoeid();
    }
 
    string xml = HttpGet("http://weather.yahooapis.com/forecastrss?w=" + 
        Woeid + "&u=" + degreeUnit);
    var doc = XDocument.Parse(xml);
    XNamespace xmlNamespace = "http://xml.weather.yahoo.com/ns/rss/1.0";
    XElement itemElement = doc.Root.Element("channel").
        Element("item").Element(xmlNamespace + "condition");
    WeatherCondition = itemElement.Attribute("text").Value;
    WeatherCode = Convert.ToInt32(itemElement.Attribute("code").Value);
    WeatherTemp = Convert.ToInt32(itemElement.Attribute("temp").Value);
 
    if (Latitude != 0 && Longitude != 0)
    {
        RetrieveTime("demo");
    }
}

The preceding method also checks whether the Woeid property is empty and if so calls the RetrieveWoeid method. Also notice that we need to check whether the Latitude and Longitude properties contain values and if they do, we need to retrieve the current time using the RetrieveTime method. The code for both methods are as follows:

private void RetrieveWoeid()
{
    string query = "select * from geo.places where text%3D\"" + 
        Uri.EscapeDataString(City) + "," + Uri.EscapeDataString(Country) + "\"";
    string url = string.Format("http://query.yahooapis.com/v1/public/yql?q=" + 
        query + "&format=json");
    string json = HttpGet(url);
    // Include reference to Microsoft.CSharp for dynamic object.
    dynamic jsonObject = JsonConvert.DeserializeObject(json);
    Latitude = Convert.ToDecimal(jsonObject.query.results.place[0].centroid.latitude);
    Longitude = Convert.ToDecimal(jsonObject.query.results.place[0].centroid.longitude);
    Woeid = jsonObject.query.results.place[0].woeid;
}
 
private void RetrieveTime(string userName)
{
    string json = HttpGet(string.Format(
        "http://ws.geonames.org/timezoneJSON?lat={0}&lng={1}&username={2}", 
        Latitude, Longitude, userName));
    dynamic jsonObject = JsonConvert.DeserializeObject(json);
    CurrentDateTime = jsonObject.time;
}

All three methods call the HttpGet method which simply performs an HTTP GET request to the specified URL and return the response. The code for the HttpGet method is:

private string HttpGet(string uri)
{
    var req = WebRequest.Create(uri);
    var resp = req.GetResponse();
    var sr = new System.IO.StreamReader(resp.GetResponseStream());
    return sr.ReadToEnd().Trim();
}

Combining our custom class with Outlook Recipients

With the code for our ContactWeather class working, it’s time to load Outlook e-mail recipient data into it. Open the frmWeather.cs ADX Outlook Form we’ve created earlier and add the following method, called LoadRecipients to it:

private void LoadRecipients()
{
    Outlook.Inspector currInsp = null;
    Outlook.MailItem mail = null;
    Outlook.Recipients recipients = null;
    Outlook.Recipient recipient = null;
    Outlook.AddressEntry addressEntry = null;
    Outlook.ContactItem contact = null;
 
 
    try
    {
        currInsp = OutlookApp.ActiveInspector();
        if (currInsp.CurrentItem is Outlook.MailItem)
        {
            mail = currInsp.CurrentItem as Outlook.MailItem;
            recipients = mail.Recipients;
 
            for (int i = 1; i <= recipients.Count; i++)
            {
                recipient = recipients[i];
                addressEntry = recipient.AddressEntry;
                contact = addressEntry.GetContact();
 
                if (contact != null)
                {
                    AddContactToList(contact);
                    Marshal.ReleaseComObject(contact);
                }
 
                if (recipient != null) Marshal.ReleaseComObject(recipient);
                if (addressEntry != null) Marshal.ReleaseComObject(addressEntry);
            }
 
            if (mail.Sent)
            {
                addressEntry = mail.Sender;
                contact = addressEntry.GetContact();
                if (contact != null)
                {
                    AddContactToList(contact);
                    Marshal.ReleaseComObject(contact);
                }
                Marshal.ReleaseComObject(addressEntry);
            }
 
            var ctrl = elementHost1.Child as weatherList;
            ctrl.SetData(weatherContacts);
        }
    }
    finally
    {
        if (recipients != null) Marshal.ReleaseComObject(recipients);
        if (mail != null) Marshal.ReleaseComObject(mail);
        if (currInsp != null) Marshal.ReleaseComObject(currInsp);
    }
}

The LoadRecipients method loops through all the recipients of an Outlook e-mail and gets the Outlook contact object associated with the recipient by calling the GetContact method of the AddressEntry object. We then add the contact to a generic list made up of our ContactWeather class using the AddContactToList method. We also check whether the e-mail is in read mode by using its Sent property and then get the contact associated with the sender of the e-mail instead of the recipients.

The code for the AddContactToList method follows below:

public void AddContactToList(Outlook.ContactItem contact)
{
    Outlook.UserProperties userProperties = null;
    Outlook.UserProperty woeIdProperty = null;
    Outlook.UserProperty latProperty = null;
    Outlook.UserProperty lonProperty = null;
 
    try
    {
        var contactWeather = new ContactWeather();
        contactWeather.Fullname = contact.FullName;
 
        contactWeather.City = string.Empty;
        if (!string.IsNullOrEmpty(contact.BusinessAddressCity))
            contactWeather.City = contact.BusinessAddressCity;
        else
        {
            if (!string.IsNullOrEmpty(contact.MailingAddressCity))
                contactWeather.City = contact.MailingAddressCity;
            else
            {
                if (!string.IsNullOrEmpty(contact.HomeAddressCity))
                    contactWeather.City = contact.HomeAddressCity;
                else
                {
                    if (!string.IsNullOrEmpty(contact.OtherAddressCity))
                        contactWeather.City = contact.OtherAddressCity;
                }
            }
        }
 
        if (string.IsNullOrEmpty(contactWeather.City))
            return;
 
        contactWeather.Country = string.Empty;
        if (!string.IsNullOrEmpty(contact.BusinessAddressCountry))
            contactWeather.Country = contact.BusinessAddressCountry;
        else
        {
            if (!string.IsNullOrEmpty(contact.MailingAddressCountry))
                contactWeather.Country = contact.MailingAddressCountry;
            else
            {
                if (!string.IsNullOrEmpty(contact.HomeAddressCountry))
                    contactWeather.Country = contact.HomeAddressCountry;
                else
                {
                    if (!string.IsNullOrEmpty(contact.OtherAddressCountry))
                        contactWeather.Country = contact.OtherAddressCountry;
                }
            }
        }
 
        userProperties = contact.UserProperties;
 
        woeIdProperty = userProperties.Find("woeid");
        if (woeIdProperty != null)
            contactWeather.Woeid = woeIdProperty.Value.ToString();
 
        latProperty = userProperties.Find("latitude");
        if (woeIdProperty != null)
            contactWeather.Latitude = Convert.ToDecimal(latProperty.Value);
 
        lonProperty = userProperties.Find("longitude");
        if (woeIdProperty != null)
            contactWeather.Longitude = Convert.ToDecimal(lonProperty.Value);
 
        contactWeather.RetrieveInfo();
 
        if (woeIdProperty == null)
        {
            woeIdProperty = userProperties.Add("woeid", Outlook.OlUserPropertyType.olText);
            woeIdProperty.Value = contactWeather.Woeid;
            contact.Save();
        }
 
        if (latProperty == null)
        {
            latProperty = userProperties.Add("latitude", Outlook.OlUserPropertyType.olNumber);
            latProperty.Value = contactWeather.Latitude;
            contact.Save();
        }
 
        if (lonProperty == null)
        {
            lonProperty = userProperties.Add("longitude", Outlook.OlUserPropertyType.olNumber);
            lonProperty.Value = contactWeather.Longitude;
            contact.Save();
        }
 
        weatherContacts.Add(contactWeather);
    }
    finally
    {
        if (lonProperty != null) Marshal.ReleaseComObject(lonProperty);
        if (latProperty != null) Marshal.ReleaseComObject(latProperty);
        if (woeIdProperty != null) Marshal.ReleaseComObject(woeIdProperty);
        if (userProperties != null) Marshal.ReleaseComObject(userProperties);
    }
 
}

In the preceding code of the AddContactToList method, we created a new instance of the ContactWeather class, added the necessary information about the Outlook contact to it and then added it to the global list of ContactWeather objects called weatherContacts.

We also first checked if the Outlook Contact already have a Woeid, latitude and longitude user property before we called the RetrieveInfo method of the ContactWeather object. If the contact already has values for these fields, we do not have to make another web request to the Yahoo Geo service.

Next, open the weatherList.xaml code behind the file and add the following property and method which will bind the WPF list view to the list of contacts:

public string Contacts { get; set; }
 
public void SetData(List<ContactWeather> contacts)
{
    lvDataBinding.ItemsSource = null;
    lvDataBinding.ItemsSource = contacts;
}

Finally, open the frmWeather.cs file and add code to a link label's LinkClicked event handler which will call the LoadRecipients method.

private void lblRefresh_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
    weatherContacts = new List<ContactWeather>();
    LoadRecipients();
}

The LoadRecipients method will in turn call the SetData method of the WPF user control in the following manner:

var ctrl = elementHost1.Child as weatherList;
ctrl.SetData(weatherContacts);

That should do it. You can build, register and run your Outlook add-in project and if the intergration with the Yahoo web-service goes well, you should see the weather conditions and time for all the recipients of your e-mail.

Thank you for reading. Until next time, keep coding!

Available downloads:

This sample Outlook Add-in was developed using Add-in Express for Office and .net:
C# Outlook Yahoo Weather add-in

Post a comment

Have any questions? Ask us right now!