REST API Metadata Now Exposed by Sites in SharePoint Online

Published on: Author: Rob Windsor 1 Comment
I was sitting in on one of Andrew Connell’s talks this week and he mentioned that the REST API for most SharePoint Online sites now exposes metadata. I immediately went to my SharePoint Online site, added /_vti_bin/Client.svc/$metadata to the site URL and, lo and behold, it worked!

Capture101

 

What follows is the tale of my adventure trying to create a client that uses this metadata to build a service proxy. I started by opening up Visual Studio 2012 to create a Console Application project and trying to add a service reference. It didn’t work:

Capture102

 

I clicked the Details link to see what the issue was and got, “There was an error downloading ‘https://robwindsor2.sharepoint.com/sites/dev/_vti_bin/client.svc/_vti_bin/ListData.svc/$metadata’”.  Look at the URL in that error message closely. What is /_vti_bin/ListData.svc doing in there – I didn’t add it. Well it appears that when you add a service reference in either Visual Studio 2012 or Visual Studio 2013, the dialog automatically adds /_vti_bin/ListData.svc to the URL for SharePoint sites. That was quite a bummer, it looked like I was going to have to resort to using DataSvcUtil on the command line to create the service proxy.

So I opened a Visual Studio Command Prompt and entered DataSvcUtil /out:SPProxy.cs /uri:https://robwindsor2.sharepoint.com/sites/dev/_vti_bin/client.svc. The call failed with the message, “error 7001: The remote server returned an error: (500) Internal Server Error”. I wanted some more information so I started up Fiddler and tried to create the proxy again. Taking a look at the body of the response I saw that the actual error message was, “Access denied. You do not have permission to perform this action or access this resource”. So I went to Google to see how to add credentials to the call being made by DataSvcUtil but it appears that you can’t. Many answers suggested that you download the metadata from a request in the browser and use the resulting file as input to DataSvcUtil so I did just that.

I downloaded the metadata to a file named metadata.edmx and tried to create the proxy again using  DataSvcUtil /out:SPProxy.cs /in:metadata.edmx. No luck – this time the error was, “error 7001: The element ‘DataService’ has an attribute ‘DataServiceVersion’ with an unrecognized version ‘3.0’. Some more Googling suggested that I needed to download the most recent WCF Data Service tools from Microsoft (http://www.microsoft.com/en-ca/download/details.aspx?id=39373). I downloaded the tools and installed them but to no avail, running DataSvcUtil again gave me the same error.

At this point I gave up for a while but then it hit me. I had the metadata in a file on disk, why don’t I create another service that returns it and then use that service to create the proxy in my Console Application. So, I created a new project using the Empty ASP.NET Web Application template. I added a Generic Handler named Handler1 (I know, I’m creative that way, it’s a gift) and modified the ProcessRequest method as follows:
public void ProcessRequest(HttpContext context)
{
    var xml = File.ReadAllText(@"C:\temp\metadata.edmx");
    context.Response.ContentType = "application/xml";
    context.Response.Write(xml);
}

I started the project and navigated to http://localhost:port
/Handler1.ashx to ensure I got back the metadata, and I did. So, leaving the ASP.NET project running I opened another instance of Visual Studio, created a Console Application and added a service reference for the handler. It worked, I now had a proxy!

Now this isn’t just any old proxy – this is a PROXY! Five namespaces and about a billion types in this sucker. That made finding the context class a little difficult but after some time in the Object Browser and some educated guesses I finally found it. Now to write a LINQ query to get some list data. Here’s what I started out with:
private const string _siteUrl = "https://robwindsor2.sharepoint.com/sites/dev";

static void Main(string[] args)
{
    var context = new SPProxy.SP.Data.ListData(
        new Uri(_siteUrl + "/_vti_bin/client.svc"));

    var items = from p in context.ProductsListItems
                where p.Category.Title == "Produce"
                select p;

    foreach (var item in items)
    {
        Console.WriteLine(item.Title);
    }
}

I didn’t try to run to run yet because I knew I needed to add credentials. My first thought was to use the SharePointOnlineCredentials class the way I have in the past with the Client Object Model:
private const string _siteUrl = "https://robwindsor2.sharepoint.com/sites/dev";

static void Main(string[] args)
{
    // Credentials is a class I created to hide my user name and password
    var loginName = Credentials.Login;    
    var password = Credentials.Password;  

    var securePassword = new SecureString();
    password.ToCharArray().ToList().ForEach(c => securePassword.AppendChar(c));
    var creds = new SharePointOnlineCredentials(loginName, securePassword);

    var context = new SPProxy.SP.Data.ListData(
        new Uri(_siteUrl + "/_vti_bin/client.svc"));
    context.Credentials = creds;

    var items = from p in context.ProductsListItems
                where p.Category.Title == "Produce"
                select p;

    foreach (var item in items)
    {
        Console.WriteLine(item.Title);
    }
}

I ran Fiddler and then ran my program and it failed. Going to Fiddler I saw that the message was, “Access denied. You do not have permission to perform this action or access this resource”. I knew the credential information needed to be passed in a cookie when using the REST API but when I checked the request in Fiddler I saw there were none. So, after a little bit more Googling I found that you use the SendingRequest2 event on the context object to set the cookie. Some quick changes to the code resulted in this:
private const string _siteUrl = "https://robwindsor2.sharepoint.com/sites/dev";
private static string _authCookie;

static void Main(string[] args)
{
    // Credentials is a class I created to hide my user name and password
    var loginName = Credentials.Login;
    var password = Credentials.Password;

    var securePassword = new SecureString();
    password.ToCharArray().ToList().ForEach(c => securePassword.AppendChar(c));
    var creds = new SharePointOnlineCredentials(loginName, securePassword);
    _authCookie = creds.GetAuthenticationCookie(new Uri(_siteUrl));

    var context = new SPProxy.SP.Data.ListData(
        new Uri(_siteUrl + "/_vti_bin/client.svc"));
    context.SendingRequest2 += context_SendingRequest2;

    var items = from p in context.ProductsListItems
                where p.Category.Title == "Produce"
                select p;

    foreach (var item in items)
    {
        Console.WriteLine(item.Title);
    }
}

static void context_SendingRequest2(object sender, 
    System.Data.Services.Client.SendingRequest2EventArgs e)
{
    e.RequestMessage.SetHeader("Cookie", _authCookie);
}



I ran my console application again and what to my wondering eyes should appear:

Capture103

 

Success! At least the first stage of it. Stay tuned for more.

One Response to REST API Metadata Now Exposed by Sites in SharePoint Online Comments (RSS) Comments (RSS)

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>