These days we are spending a lot of time on a series of sandbox applications that will run on Office 365. If you are a follower of this blog you know I was more than a bit disappointed in the restrictions the SharePoint 2010 sandbox implementation imposes. However, if you want to play in the sandbox, you can either just say no to requirements that aren't directly supported, or you can earn your money and get creative!
I've been getting creative. These applications are going to be great - that said, some elements of the architecture are unorthodox and even a little strange. This post details one of the elements.
Expose services to external clients that:
- Hide their underlying technology from the caller and hide the application's underlying implementation from prying eyes
- Support all current web development stacks
- Perform well
- Are easy to create and consume
What we really want is for the client to consume services via an abstraction that hides the details and helps minimize dependencies. Unfortunately, you can't deploy traditional asmx or WCF services to the SharePoint Sandbox.
Before we get into the solution, please be aware that I am not saying that this architecture is better than JSOM, SPServices, or the REST API – I'm just saying it is a better fit for our particular needs for certain specific applications. It also illustrates an interesting technique that you can add to your own arsenal.
While it is not possible to deploy traditional Web services, we can come close. A Web service is just an http endpoint to which we can pass arguments and from which we can read a response. Conceptually, the only differences between an aspx page and a Web service are the calling convention and (usually) the content type (e.g. 'text/html' versus 'text/xml' or 'application/json'). You can create an aspx page that works just like an asmx or WCF service if you want – you just have to handle the plumbing yourself.
serviceContainer.master Master Page
This is a very simple master page with a single ContentPlaceHolder that the content page overrides with the service web part.
Service pages are stored in a folder or library off the root of the web allowing a relative URL reference to serviceContainer.master. Each page uses SPUserCodeWebPart to load the a sandboxed Web Part that contains the actual implementation of the service.
A welcome byproduct of using a page in a library is the ability to use the standard SharePoint security model to control access to the page (and hence the service). This is a clear advantage for this technique compared to both the Client Object Model and SPServices.
The service Web part reads any required arguments from the query string and/or post parameters from the Page.Request and does its work returning the format of your choice. In the screen shot below it is returning a JSON string that contains the current Web's Title. Because the part is stateless all of the work is done in Render().
The result looks like this:
It could just as easily be XML or any other type of content. The master page and the page that contain the Web Part are empty. What comes out of the Render method is the body of the response.
Testing shows that this solution provides performance that is comparable (and at times significantly better) than the equivalent Client Object Model code. The tests used a site on SharePoint Online (Office 365).
The service page was invoked via a .NET console application using the HttpWebRequest class. The code below shows the function that makes the call.
Fiddler shows that this call sent 1258 bytes and received 866 bytes in approximately 150 milliseconds.
Client Object Model
Two passes were tested with the JSOM from the same console application. The first initialized the ClientContext each time and the second reused an existing ClientContext. It turns out that instantiating the client context calls the sites.asmx service. Therefore each call resulted in two round trips. The important lesson learned here is that you should hold on to the context and not recreate it for the best performance. The code for the second version of the test method is shown below. To make the comparison fair, the code explicitly asks for only the Title property of the current Web.
Fiddler shows that this call sent 2022 bytes and received 724 bytes in approximately 225 milliseconds.
The results for one set of tests are as follows:
10 calls to the sandbox page took 23544921 ticks
10 calls with the client object model took 39882812 ticks
10 calls with the client object model with one context took 22656250 ticks
10 calls to the sandbox page took 21337890 ticks
10 calls with the client object model took 40322266 ticks
10 calls with the client object model with one context took 30048828 ticks
10 calls to the sandbox page took 17460938 ticks
10 calls with the client object model took 43066406 ticks
10 calls with the client object model with one context took 22109375 ticks
Round 4 (With Fiddler On)
10 calls to the sandbox page took 15449219 ticks
10 calls with the client object model took 38222656 ticks
10 calls with the client object model with one context took 24550782 ticks
100 calls to the sandbox page took 171845703 ticks
100 calls with the client object model took 473261719 ticks
100 calls with the client object model with one context took 240097656 ticks
In the first round the JSOM performed slightly better than the service page. In subsequent rounds the service page's performance was significantly better. I speculate that the differences are due to unknown factors on my test Office 365 tenancy. Therefore, the only conclusion I am willing to stand by is that using this technique is at worst performance neutral but that it might be better.
The tests used a trivial scenario. In spite of the excellent batching capabilities found in the JSOM, real world scenarios often require multiple roundtrips where each operation depends on data from a previous operation that is not on the client. This architecture potentially offers much better performance by combining such operations into a single round trip.
Author: Doug Ware