Harin Sandhoo's Blog

June 13, 2011

Integrating SharePoint and Mobile devices using Azure AppFabric Service Bus

Filed under: Azure, SharePoint — Harin @ 8:30 am

Although the WP7 for Azure toolkit has been released, I recently ran into an issue exposing SharePoint data to a WP7 app that I thought was worth sharing.  In this case, we have a SharePoint 2010 farm inside a corporate firewall, needing to expose data from the SharePoint User Profile Service to an application written on a WP7 mobile device.  To implement this, we created a Windows Service that hosts a webHttpRelayBinding endpoint so the WP7 application can make simple REST calls to get at the data.  By using a REST based endpoint, that also allowed us to write equivalent client applications for Android and iOS.  Going forward, the windows service can be brought in and managed in SharePoint using the Service Application model, but for a demo, simply installing the windows service on the SharePoint box was sufficient. 

High level architecture:

 

 

WCF Service Contract:

 For our service we have a service contract defined to as:

    [ServiceContract]
    public interface IProfileService
    {
        [OperationContract,
        WebGet(
            BodyStyle = WebMessageBodyStyle.Bare,
            ResponseFormat = WebMessageFormat.Xml,
            UriTemplate = "/FarmInfo")
        ]
        FarmInfo GetFarmInfo();

        [OperationContract, 
        WebGet(
            BodyStyle = WebMessageBodyStyle.Bare,
            ResponseFormat = WebMessageFormat.Xml,
            UriTemplate = "/Users?q={searchTerms}")
        ]
        UserList GetUsers(string searchTerms);

        [OperationContract, 
        WebGet(
            BodyStyle = WebMessageBodyStyle.Bare,
            ResponseFormat = WebMessageFormat.Xml,
            UriTemplate = "/Users/{userId}")
        ]
        Profile GetProfile(string userId);
    }
 

WCF Service Implementation:

I created a class called SPProfileReader which encapsulates the call to the SharePont UserProfileManager.  The service implementation looks something like this:

    public class ProfileService : IProfileService
    {
        private static object _synch = new object();
        private static SPProfileReader reader = null;

        private SPProfileReader Instance
        {
            get
            {
                return new SPProfileReader(System.Configuration.ConfigurationManager.AppSettings["SiteUrl"]);
            }
        }

        public FarmInfo GetFarmInfo()
        {
            FarmInfo retVal = null;
            try
            {
                retVal = new FarmInfo()
                {
                    id = "AIS Demos Farm",
                    url = "http://<demo farm url>",
                    centraladminurl = "<central admin url>",
                    description = "This farm is a demo environment meant to showcase Azure and SharePoint integration scenarios."
                };
            }
            catch (Exception e)
            {
                Trace.WriteLine(e.ToString());
            }
            return retVal;
        }

        public UserList GetUsers(string searchTerms)
        {
            UserList retVal = null;
            try
            {
                using (var rdr = Instance)
                {
                    retVal = rdr.GetUsers(searchTerms);
                }
            }
            catch (Exception e)
            {
                Trace.WriteLine(e.ToString());
            }
            return retVal;
        }

        public Profile GetProfile(string userId)
        {
            Profile retVal = null;
            try
            {
                using (var rdr = Instance)
                {
                    retVal = rdr.GetUserProfile(userId);
                }
            }
            catch (Exception e)
            {
                Trace.WriteLine(e.ToString());
            }
            return retVal;
        }
    }
 

Hosting the WCF Service:

Hosting the WCF Service is pretty straight forward and for completeness looks like this:

        public void StartHost()
        {
            try
            {
                //// Build the endpoint URI
                string serviceNamespace = System.Configuration.ConfigurationManager.AppSettings["SBNamespace"];
                string endPoint = System.Configuration.ConfigurationManager.AppSettings["SBEndpoint"];
                Uri address = ServiceBusEnvironment.CreateServiceUri("https", serviceNamespace, endPoint);

                //// Build the service bus credential
                string serviceUser = System.Configuration.ConfigurationManager.AppSettings["SBUser"];
                string serviceKey = System.Configuration.ConfigurationManager.AppSettings["SBKey"];
                TransportClientEndpointBehavior sharedSecretServiceBusCredential = new TransportClientEndpointBehavior();
                sharedSecretServiceBusCredential.CredentialType = TransportClientCredentialType.SharedSecret;
                sharedSecretServiceBusCredential.Credentials.SharedSecret.IssuerName = serviceUser;
                sharedSecretServiceBusCredential.Credentials.SharedSecret.IssuerSecret = serviceKey;

                Trace.WriteLine("Opening service bus connection: " + address.ToString());

                //// Create the wcf service host
                host = new WebServiceHost(typeof(ProfileService), address);

                //// Set the binding to WebHttpRelayBinding
                Microsoft.ServiceBus.WebHttpRelayBinding binding = new WebHttpRelayBinding();
                binding.Security.RelayClientAuthenticationType = RelayClientAuthenticationType.None;

                //// Create the endpoint
                ServiceEndpoint se = host.AddServiceEndpoint(typeof(IProfileService), binding, address);
                se.Behaviors.Add(sharedSecretServiceBusCredential);
                ServiceDebugBehavior sdb = host.Description.Behaviors.Find<ServiceDebugBehavior>();
                sdb.HttpHelpPageEnabled = false;

                //// Open host
                host.Open();
                Trace.WriteLine("Host open.");
            }
            catch(Exception e)
            {
                Trace.WriteLine("Exception occured while opening host:");
                Trace.WriteLine(e.ToString());
            }
        }

 

Sample WP7 Client Code:

Since the relay service is exposing the endpoint as via REST, integrating with mobile devices becomes trivial.  Note, if this is sensitive / production data, I would recommend utilizing the ACS to secure access to the endpoint (examples of how to do that can be seen in the Azure WP7 SDK).  Here is a bit of code showing how to access some data from the REST endpoint.

    public class ServiceProxy
    {
        private const string SBUri = "https://<myaccount>.servicebus.windows.net/SPProfileViewer/";

        public static void GetAllUsers(DownloadStringCompletedEventHandler callback)
        {
            WebClient client = new WebClient();
            string uri = string.Format("{0}Users",SBUri);
            client.DownloadStringCompleted += callback;
            client.DownloadStringAsync(new Uri(uri));
        }

        public static void SearchForUsers(string queryTerm, DownloadStringCompletedEventHandler callback)
        {
            WebClient client = new WebClient();
            string uri = string.Format("{0}Users?q={1}", SBUri, queryTerm);
            client.DownloadStringCompleted += callback;
            client.DownloadStringAsync(new Uri(uri));
        }

        public static void GetUserProfile(string userId, DownloadStringCompletedEventHandler callback)
        {
            WebClient client = new WebClient();
            string uri = string.Format("{0}Users/{1}", SBUri,userId);
            client.DownloadStringCompleted += callback;
            client.DownloadStringAsync(new Uri(uri), userId);
        }

        public static void GetFarmInfo(DownloadStringCompletedEventHandler callback)
        {
            WebClient client = new WebClient();
            string uri = string.Format("{0}FarmInfo", SBUri);
            client.DownloadStringCompleted += callback;
            client.DownloadStringAsync(new Uri(uri));
        }
    }
 

An Important ‘gotcha’ when working with SharePoint and the AppFabric Service Bus:

When hosting our relay service, we noticed that after a while the service host lost the connection to the Service Bus and our service was no longer accessible from the clients.  Working off and on with Microsoft Support for a few weeks, we finally tracked down what the issue was.  When the service creates a connection to the AppFabric Service Bus, it requests a security token.  Every ten minutes or so, the host attempts to renew that token.  The trouble comes in when you try and integrate in with SharePoint.  When you access the SPServiceContext and UserProfileManager and probably a bunch of other SharePoint API calls (as we did in this case), the ServicePointManager.ServerCertificateValidationCallback delegate is changed so that it only trusts the certificates that SharePoint knows about.  Unfortunately, the GTE CyberTrust Root certificate that ultimately signed our Service Bus certificate is not trusted by SharePoint.  If you are experiencing this problem, you may notice errors in your WCF / System.NET trace log such as:

System.Net Error: 0 : [6256] Exception in the HttpWebRequest#3038911:: – The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.

System.Net Error: 0 : [6256] Exception in the HttpWebRequest#3038911::EndGetRequestStream – The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.

 

The work around is as follows (these are from Mog Liang from Microsoft’s support team):

  1. Download the ACS server certificate’s root CA ‘GTE CyberTrust Global Root’ by accessing ACS service ‘https://<myaccount&gt;.accesscontrol.windows.net/WRAPv0.9/
  2. Open ‘SharePoint 2010 Central Administration’ tool
  3. Open ‘Security -> Manage Trust’ page  
  4. Click ‘new’ button, in popup page, input the ‘GTE CyberTrust Global Root’ certificate, give a name, and click OK

 

Blog at WordPress.com.