Welcome to my blog, stay tunned :
Home | Blogs | Stephane Eyskens's blog

Building and consuming a custom oData service for SharePoint 2010

For a recent project, I’ve been involved together with another fellow SharePoint developer Jeroen Van Bastelaere (http://www.skavi.be) in optimizing a SharePoint portal application.
The first idea we had was to use caching, so we built a lightweight caching framework that is flexible and maintainable enough and that allowed us to reach our main objective which was gaining performance.
I tell you this little story because the topic of this post is actually inherited from the above context. Thus, still having the performance objective in mind, we’ve also been asked to build asynchroneous controls (webparts, user controls…) for user friendlyness and fast initial page loads.
Of course, one could have chosen a common approach which is to make use of jQuery together with ListData.svc/Lists.asmx or involve the ECMAScript COM and that might have been ok but…I prefered to consume our data from our caching system to make sure this would be the most performant approach (server side at least).
At first, I thought of simply writing a REST service using the built-in SharePoint MultipleBaseAddressWebServiceHostFactory . That could have paid the bill but I wanted something very flexible in terms of query engine so I ended up in building a custom oData service that consumes our cached data.
One of the main cached object we dealt with is User Profiles. For scalability and performance reasons, we decided to get all the user profiles via a search query, cache them during the entire day since they get synchronized only once a day, and then, target this cache for any subsequent User Profile query. Our business scenario allowed us to cache them an entire day.

That works very well and prevents also the system from overloading the User Profiles store with thousands of connections. This is threfore not only more performant but also more robust.

That said, the example I will show you in this post demonstrates how to consume that User Profile cache with a custom oData service.
The advantage of oData is its ability to build rich RESTFUL Urls and also to be able to consume it from a server-side control just like we do with ListData.svc.
So, I’ll cover the following topics in this article:

  • Building a the cache framework (basic subset of what we’ve done to avoid unnecessary complexity)
  • Building a CachedData.svc service that will for this example expose oData operations on the cached User Profiles (this could of course be extended to any kind of data)
  • Consume it with jQuery
  • Consume it from a Console Application


ADO.NET Data Services must be installed on the system, you can download it from there :

Creating the project

  • Create an empty SharePoint 2010 Project
  • Add the following references :



Building the cache framework

As said before, this light framework enables storing data in cache very easily. It’s made of a few classes, I’ve removed most of its complexity in order to have something as understandable as possible since this is not the main topic of this post.
I’ve also hard-coded some values and removed exception handling for sake of simplicity.
First, the interface that defines what any cached member should implement:

public interface ISharePointData<T>
        List<T> GetItems();              
        int CacheDurationInSeconds { get; set; }
        int CacheDurationInMinutes { get; set; }
        int CacheDurationInHours { get; set; }        

Here, the contract specifies that any member should implement a GetItems() method and the time to be set in cache before expiration.

Now, a base class that implements our interface and specifies some defaults. In this case (cleaned out), its usage is less interesting than in the real implementation…

public abstract class BaseSharePointData<T> : ISharePointData<T>
        public abstract List<T> GetItems();              
        private int _cacheDuration = 0;
        public int CacheDurationInSeconds
                return _cacheDuration;
                _cacheDuration = value;
        public int CacheDurationInMinutes
                return CacheDurationInSeconds / 60;
                CacheDurationInSeconds = value * 60;
        public int CacheDurationInHours
                return CacheDurationInMinutes / 60;
                CacheDurationInMinutes = value * 60;

At last, the definition of a member, in this case, our User Profile object:

[DataContractAttribute(IsReference = true)]
public class UserProfileObject : BaseSharePointData<UserProfileObject>
    public UserProfileObject()
        CacheDurationInMinutes = 10;
    public UserProfileObject(string userName, string email, string pictureURL, string path, string jobTitle)
        : base()
        UserName = userName;            
        PictureURL = pictureURL;
        Email = email;
        Path = path;
        JobTitle = jobTitle;
    public string UserName { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }             
    public string Email { get; set; }
    public string PictureURL { get; set; }
    public string Path { get; set; }
    public string JobTitle { get; set; }

    public override List<UserProfileObject> GetItems()
        List<UserProfileObject> allProfiles = new List<UserProfileObject>();
        FullTextSqlQuery q = new FullTextSqlQuery(ServerContext.Current);
        q.QueryText = "SELECT AccountName,FirstName,LastName, WorkEmail,PictureUrl,Path,JobTitle FROM Scope() WHERE \"Scope\"='People'";
        q.ResultTypes = ResultType.RelevantResults;
        q.RowLimit = 20000;
        ResultTableCollection results = q.Execute();
        ResultTable queryResultsTable = results[ResultType.RelevantResults];
        DataTable queryDataTable = new DataTable();
        queryDataTable.Load(queryResultsTable, LoadOption.OverwriteChanges);
        AddProfiles(allProfiles, queryDataTable.Rows);                        
        return allProfiles;

    public void AddProfiles(List<UserProfileObject> profiles, DataRowCollection rows)
        for (int i = 0; i < rows.Count; i++)

            DataRow row = rows[i];
            profiles.Add(new UserProfileObject
                UserName = (row["AccountName"] != null) ? row["AccountName"].ToString() : string.Empty,                    
                FirstName = (row["FirstName"] != null) ? row["FirstName"].ToString() : string.Empty,
                LastName = (row["LastName"] != null) ? row["LastName"].ToString() : string.Empty,
                Email = (row["WorkEmail"] != null) ? row["WorkEmail"].ToString() : string.Empty,
                PictureURL = (row["PictureURL"] != null) ? row["PictureURL"].ToString() : string.Empty,
                JobTitle = (row["JobTitle"] != null) ? row["JobTitle"].ToString() : string.Empty,
                Path = (row["Path"] != null) ? row["Path"].ToString() : string.Empty                    

This one needs some extra comments :

  • 1st, we already specify that the primary key of this object is the UserName property, that is required by oData.
  • 2nd, we implement the GetItems() method since it is required by our Interface. This method performs the search query taking all the profiles from the People scope with the properties we want. In this case, I only take a subset of them.
  • Then, we create the helper method AddProfiles() that builds our list of UserProfileObject.

The expected behavior is that when called for the first time, the code will perform the search query and then, it will just consume its data from the cache.

Now, here is the dataHelper class that puts objects in cache.

public class DataHelper
    public List<T> GetItems<T>() where T : ISharePointData<T>, new()
        T objectToCache = new T();
        object cache = HttpContext.Current.Cache[objectToCache.GetType().ToString()];

        if (objectToCache.CacheDurationInSeconds != 0)

            if (cache == null)
                // Cache is not yet filled, so retrieve it
                List<T> itemsRetrieved = objectToCache.GetItems();


                /* --if using distributed cache such as AppFabric...---
                    * CacheUtility.CurrentCache.Add(
                            new TimeSpan(0,0,objectToCache.CacheDurationInSeconds));*/

                return itemsRetrieved;
                // Return the cached value
                return (List<T>)cache;
            // Do not get items from the cache
            return objectToCache.GetItems();

    public void ClearItems(string key)

Building the service

Adding the .svc file

As for every WCF service, you’ll need to create a .SVC file.

  • Create a SharePoint mapped folder onto ISAPI
  • Create an empty text file with the extension .SVC and add it the following:
<%@ ServiceHost 
    Factory="Microsoft.SharePoint.Client.Services.MultipleBaseAddressDataServiceHostFactory, Microsoft.SharePoint.Client.ServerRuntime, Version=, Culture=neutral, PublicKeyToken=71e9bce111e9429c"

The most important thing here is that we tell SharePoint to use the MultipleBaseAddressDataServiceHostFactory factory. This is the one that allows us to build ADO.Net Data Services and to deploy them into SharePoint.
Regarding the service deployment into SharePoint, that’s all about what you have to do, not a big effort as you can see.

Adding the services classes

Now that we’ve created the .SVC file, we need to build the code which is failry simple as you will see.
The service class looks like this:

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
IncludeExceptionDetailInFaults = true)]
public class CacheoDataService : DataService<CacheDataContext>
    public static void InitializeService(DataServiceConfiguration config)
        config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);            
        config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;

The first attribute is used to expose a WCF MEX. The 2nd attribute is required by SharePoint to make the service ASP.NET friendly.
At last, the 3rd attribute is used to display the error in the response body in case something goes wrong. During the development phase, it’s useful.
Then, I specify that all the operations are accessible. In this case, I have only one operation and I don’t handle CRUD queries. I only implement the R from CRUD.
As you can see, our service inherits from the DataService class which allows specifying another type of object, in this case our custom CacheDataContext.

public class CacheDataContext
        DataHelper dataHelper = new DataHelper();

        public IQueryable<UserProfileObject> UserProfiles
                return dataHelper.GetItems<UserProfileObject>().AsQueryable();

The only important thing to remember here is that you need to return a IQueryable or another object that implements IQueryable. In this case, I make use of the cache framework to retrieve the list of user profile objects.
If I wanted to add another operation, I could just write it this way:

public IQueryable<News> News
        return dataHelper.GetItems<News>().AsQueryable();

Which would allow me to return news from the Cache.
If I had worked with a database as a datasource, I’d have most probably used Entity Framework but here, the data simply comes from memory so I can just return it like that.

So, the overall solution should look like this :

Note that you’ll have more info on the odataTestPage.htm later in this article.

Deploying the solution and checking if the service is working

By just typing the URL to the endpoint, you should get all the available operations, in our example, there is only one but you could have more of course. Do not forget to end the URL with the / character.

Now, if you just add the operation to the URL, you’ll get all the user profiles:

And you can start using oData querystring params such as $filter, $select etc…which makes your query engine much more PowerFull than a “simple” custom RESTFUL service where you’d have to handle those params yourself.

Consuming the service from a console application

  • Just create a Console application
  • Add a service reference and use the URL to the endpoint

Adding this piece of code:

CacheDataContext.CacheDataContext ctx = new CacheDataContext.CacheDataContext(
    new Uri("http://sp2010srv/_vti_bin/cachedodata.svc"));
ctx.Credentials = System.Net.CredentialCache.DefaultCredentials;
var query = from profile in ctx.UserProfiles
            where profile.JobTitle != string.Empty
            select profile;
foreach (var queryresut in query)

would result in :

Consuming the service with jQuery

To test that, you can just add a testpage.html into your /_layouts/ folder and include the following piece of code:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
        <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
        <script type="text/javascript">
            $(document).ready(function () {
                    url: "_vti_bin/CachedoData.svc/UserProfiles()",
                    dataType: "json",
                    success: function (data) {
                        if (data.d) {
                            var html = "";
                            for (var i = 0; i < data.d.length; i++) {

                                html += data.d[i].UserName + "<br/>";

                    error: function (XMLHttpRequest, textStatus) {
	<div id="response"></div>

This will just output all the usernames :

Note that the jQuery piece of code might need to be adjusted according to the reading browser, I only tested it with IE9 & Firefox 5.0.1. You might want to read an ATOM feed in return instead of JSON…

So far, I only wrote RESTFUL services using one of the other built-in factories. I found interesting to write a short article based on a real world scenario.

Happy Coding!


Hi Stephane, I followed the

Hi Stephane,

I followed the instructions and they worked perfectly. But why is there an annoying 'ntlm' in the URLS? When looking at feed in Firefox it is 'ntlm'. In IE it is 'negotiate'.


Is it possible to remove this from the response?


Authentication scheme

Hi Mike,

You're perfectly right and I'm happy that you're mentionning that. Guess what? I have a blog post in reserve on that topic :). Unfortunately, this is a bug (at least it's what I consider as a bug). Microsoft says that they need to include the authentication scheme in the URL because a given webapp may support multiple authentication types...However, in SharePoint 2013, the authentication scheme is gone....

What you probably noticed is that because of this, when you try to use your WCF Data Service from a server-side consumer, you cannot update/delete resources because the system cannot find them. Indeed, the URL that is sent back along with the PUT/MERGE/DELETE HTTP requests contain that scheme and the system returns a 404 (resource not found).

This is not a problem in 2013 but I don't think there is a means to get rid of that in 2010. However, while you won't be able to use all CRUD operations from a server-side proxy, you can still use HttpWebRequest...but that's not really something you'd like to do :).

Best Regards

How to fix it.

You can fix it easily by creating your own ServiceHostFactory and marking it with the [ServiceFactoryUsingAuthSchemeInEndpointAddress(UsingAuthSchemeInEndpointAddress = false)] attribute.

Link: http://codefluff.blogspot.com.br/2011/08/authentication-suffix-in-sharep...

How to fix it


Thanks for your comment. However, as I posted here http://www.silver-it.com/node/117 a while ago, this has no effect. At least, it had no effect at the time I wrote the above blog post. I haven't played with that recently.

Best Regards

Source code

Hi Stephane,

In your webcast, you spoke about 2 webparts "People and me" and "Advanced Search", how can I see the "People and me" one ? is it a configuration of Advanced search ?

Source code


No, I didn't provide the source code of the people webpart because I implemented it for a customer. But you have the plumbing. Reusing the query engine as shown in the advanced search and then, just work with the DOM will make the trick

Best Regards

Awesome Job!

Thank you Stephane for sharing this information! I am looking at hosting ADO.Net Data Services in our SharePoint 2010 farm and your article just helped out a lot!!!

Great to hear


Thanks for your comment, that's great to hear :). oData is definitively worthwile to use in SharePoint and will be more and more present in future projects.


Issues with SP 2010

Hi Stephane,

This is an excellent article. I just have a couple of issues with SP 2010.

1. ServerContext is obsolete. Use SPServiceContext (using VS 2010)
2. If I use ServerContext.Current as is, then result is NULL (in debug under VS 2010 or when deployed).

So the URL http://myserver/_vti_bin/CachedoData.svc/ works fine but not http://myserver/_vti_bin/CachedoData.svc/UserProfiles()

Any help on how to get this to work with SP 2010?



Indeed, ServerContext is obsolete that should be changed. However, that's not the reason why your context is null. It's probably due to something else. As you mentioned privately, the code runs fine on a SharePoint server that belongs to a domain. I indeed never tested it outside of a domain.

Best Regards

Great article! That´s a good

Great article!

That´s a good way to query UserProfile.

Could you provide the source code of that solution to make easy to test?


Source Code


Sure, here it is

Source Code

You'll even have a better version than what I posted because this one implements an index instead of a username for the key to be fully compliant with oData. The username also works but since you cannot query profiles like this _vti_bin/...svc/UserProfiles('dom\user'), I prefered to revert to an index, so that you can still get them like this _vti_bin/...svc/UserProfiles(1) etc..in case you need a random selection for instance.

Best Regards