Sunday, March 21, 2010

Client Side AJAX Applications in SharePoint 2010 - Part 1

If you haven't played with it yet, one of the things you'll immediately notice about SharePoint 2010 is that the UI is extremely AJAX centric. For instance modifying or adding list items is either done in-line with partial page refreshes or via lightbox popups. It's very pleasant to use.

One of the consequences of this Web 2.0 design is that the expectations of SharePoint end users will significantly increase. No longer will long delays and/or full page refreshes in your SharePoint based custom applications be acceptable. End users may not be able to clearly articulate it, but unless as developers we update our techniques to match the new technology our SharePoint based applications will begin to feel stale and will stick out from the rest of SharePoint 2010.

Fortunately Microsoft has stepped up the support it offers Web 2.0 developers in a big way. One of the most compelling features is in how SharePoint exposes all list data via REST/ATOM and JSON. This feature combined with the new templating feature of ASP.Net AJAX 4 allows developers to easily write fast, responsive user interfaces. I will explore these two technologies in the following series:

Part 1 – Exploring WCF Data Services in SharePoint 2010
Part 2 – Creating a read-only page with ASP.Net AJAX 4.0 Templates
Part 3 – Writing data back to SharePoint with ASP.Net AJAX 4.0 Templates
Part 4 - jQuery and Manual JSON Object Manipulation

Why do we need yet another framework?

If you attempt to develop Web 2.0 applications in SharePoint with the techniques you have today there are really only two approaches: ASP.Net AJAX and server side controls or manually coding JavaScript, probably with jQuery or other JavaScript Libraries. If you take the ASP.Net AJAX approach you'll undoubtedly be using an UpdatePanel to get partial page refreshes and then writing ASP.Net code like you always have. There are several problems with this approach:

  • It sends the entire ViewState with every asynchronous request – this can result in slow, unresponsive apps (this can actually sneak up on you once you have a lot of production data as happened to me recently)
  • Even without a ViewState the UpdatePanel transmits large amounts of HTML (rather than lightweight JSON data)
  • It runs through the entire page lifecycle on updates, executing everything on the page even if it isn't relevant to what you're doing
  • ASP.Net AJAX doesn't have the simplicity of an ETag based HTTP caching like with a REST solution
  • Finally SharePoint just doesn't work well with UpdatePanel, I can't tell you how many issues I've had with various controls not working out of the box

And if you take the manual JavaScript rout you are faced with a number of different problems:

  • You have to write a lot of plumbing code
    • You have to write a service to return your data
    • You have to pay particular attention to security and permissions since you're exposing your API's to the world
    • You have to convert your data (ideally JSON) into HTML with probably string concatenation or document.createElement statements
  • Your UI code is not clearly separated from your data access code making your code hard to maintain
  • Saving data back to the server involves even more plumbing code

SharePoint 2010 combined with ASP.Net AJAX 4.0 solves these problems. It probably introduces new problems too, but I haven't run into too many thus far. So over the next couple of posts I hope to convince you that we do need yet another web framework and that Microsoft is headed in a great direction with these technologies.

So How Does it Work?

In WSS 3.0 you were able to access list data with the SOAP based Lists.asmx. This technique worked well for .Net desktop apps or server side application pages, but not so well with client side JavaScript. SharePoint 2010 exposes its list data with REST based ListData.svc (though WCF Data Services which I'll describe in more detail below). Once you install WCF Data Services on a machine with SharePoint 2010 you can simply navigate to http://[server]/[optional site]/_vti_bin/ListData.svc and see all of the lists you have permission to access. The result will look like this:

<?xml version="1.0" encoding="iso-8859-1" standalone="yes"?>
<
service xml:base="http://localhost/mySite/_vti_bin/ListData.svc/"
   
xmlns:atom="http://www.w3.org/2005/Atom"
   
xmlns:app="http://www.w3.org/2007/app"
   
xmlns="http://www.w3.org/2007/app">
  <
workspace>
    <
atom:title>Default</atom:title>
    <
collection href="Iterations">
      <
atom:title>Iterations</atom:title>
    </
collection>
    <
collection href="UserStories">
      <
atom:title>UserStories</atom:title>
    </
collection>
  </
workspace>
</
service>

You can then append the names of lists (e.g. http://localhost/site/_vti_bin/ListData.svc/UserStories) and immediately view the list items in the list as an ATOM feed. A typical result looks like this:

<?xml version="1.0" encoding="iso-8859-1" standalone="yes"?>
<
feed xml:base="http://localhost/mySite/_vti_bin/ListData.svc/"
   
xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
   
xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
   
xmlns="http://www.w3.org/2005/Atom">
  <
title type="text">UserStories</title>
  <
id>http://localhost/mySite/_vti_bin/ListData.svc/UserStories</id>
  <
updated>2010-03-20T02:20:26Z</updated>
  <
link rel="self" title="UserStories" href="UserStories" />
  <
entry m:etag="W/&quot;3&quot;">
    <
id>http://localhost/mySite/_vti_bin/ListData.svc/UserStories(2)</id>
    <
title type="text">Blah</title>
    <
updated>2010-03-19T15:13:28-05:00</updated>
    <
author>
      <
name />
    </
author>
    <
link rel="edit" title="UserStoriesItem" href="UserStories(2)" />
    <
link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Predecessors"
       
type="application/atom+xml;type=feed"
       
title="Predecessors"
       
href="UserStories(2)/Predecessors" />
    <
link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Priority"
       
type="application/atom+xml;type=entry"
       
title="Priority"
       
href="UserStories(2)/Priority" />
    <
link
       
rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Status"
       
type="application/atom+xml;type=entry"
       
title="Status"
       
href="UserStories(2)/Status" />
    <
link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Iteration"
       
type="application/atom+xml;type=entry"
       
title="Iteration"
       
href="UserStories(2)/Iteration" />
    <
link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Attachments"
       
type="application/atom+xml;type=feed"
       
title="Attachments"
       
href="UserStories(2)/Attachments" />
    <
category
       
term="Microsoft.SharePoint.DataService.UserStoriesItem"
       
scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
    <
content type="application/xml">
      <
m:properties>
        <
d:ContentTypeID>0x01080070E2807D369BD94FBD6C057D3110E6D3</d:ContentTypeID>
        <
d:Title>Do Something</d:Title>
        <
d:PriorityValue>(2) Normal</d:PriorityValue>
        <
d:StatusValue>Not Started</d:StatusValue>
        <
d:Complete m:type="Edm.Double" m:null="true" />
        <
d:AssignedToID m:type="Edm.Int32" m:null="true" />
        <
d:TaskGroupID m:type="Edm.Int32" m:null="true" />
        <
d:Description>&lt;div&gt;&lt;/div&gt;</d:Description>
        <
d:StartDate m:type="Edm.DateTime">2010-02-20T00:00:00</d:StartDate>
        <
d:DueDate m:type="Edm.DateTime" m:null="true" />
        <
d:Points m:type="Edm.Double">3</d:Points>
        <
d:IterationID m:type="Edm.Int32">3</d:IterationID>
        <
d:ID m:type="Edm.Int32">2</d:ID>
        <
d:ContentType>Task</d:ContentType>
        <
d:Modified m:type="Edm.DateTime">2010-03-19T15:13:28</d:Modified>
        <
d:Created m:type="Edm.DateTime">2010-02-20T15:32:26</d:Created>
        <
d:CreatedByID m:type="Edm.Int32">1</d:CreatedByID>
        <
d:ModifiedByID m:type="Edm.Int32">1</d:ModifiedByID>
        <
d:Owshiddenversion m:type="Edm.Int32">3</d:Owshiddenversion>
        <
d:Version>3.0</d:Version>
        <
d:Path>/mySite/Lists/User Stories</d:Path>
      </
m:properties>
    </
content>
  </
entry>
</
feed>

You can append various parameters to filter, sort, or paginate your data from this page. For example http://localhost/site/_vti_bin/ListData.svc/UserStories?$filter=(PriorityValue eq '(2) Normal')&$orderby=Title will return list items whose priority is normal sorted by title. See Query Expressions on MSDN for more details.

Now if you're interested you can take it a step further by viewing the data as JSON. To do this you'll need to modify the HTTP headers. Fortunately there is a tool from Microsoft for viewing, replaying, and modify HTTP sessions called Fiddler. After you install it open Fiddler from the add-in in your web browser (I used IE, but Firefox should work too). Then refresh the web page for a list (in my case ListData.svc/UserStories). Now in Fiddler drag the last request to the Request Builder, ensure “Automatically Authenticate” is checked in options, and replace “Accept: */*” with “Accept application/json”. When you click Execute you should see something like:

{
"d" : {
"results": [
{
"__metadata": {
"uri": "http://localhost/demoprep/_vti_bin/ListData.svc/UserStories(2)",
"etag": "W/\"3\"",
"type": "Microsoft.SharePoint.DataService.UserStoriesItem"
}, "ContentTypeID": "0x01080070E2807D369BD94FBD6C057D3110E6D3",
"Title": "Blah",
"Predecessors": {
"__deferred": {
"uri": "http://localhost/demoprep/_vti_bin/ListData.svc/UserStories(2)/Predecessors"
}
}, "Priority": {
"__deferred": {
"uri": "http://localhost/demoprep/_vti_bin/ListData.svc/UserStories(2)/Priority"
}
}, "PriorityValue": "(2) Normal", "Status": {
"__deferred": {
"uri": "http://localhost/demoprep/_vti_bin/ListData.svc/UserStories(2)/Status"
}
}, "StatusValue": "Not Started",
"Complete": null,
"AssignedToID": null,
"TaskGroupID": null,
"Description": "<div></div>",
"StartDate": "\/Date(1266624000000)\/",
"DueDate": null,
"Points": 3,
"Iteration": {
"__deferred": {
"uri": "http://localhost/demoprep/_vti_bin/ListData.svc/UserStories(2)/Iteration"
}
}, "IterationID": 3,
"ID": 2,
"ContentType": "Task",
"Modified": "\/Date(1269011608000)\/",
"Created": "\/Date(1266679946000)\/",
"CreatedByID": 1,
"ModifiedByID": 1,
"Owshiddenversion": 3,
"Version": "3.0",
"Attachments": {
"__deferred": {
"uri": "http://localhost/demoprep/_vti_bin/ListData.svc/UserStories(2)/Attachments"
}
}, "Path": "/demoprep/Lists/User Stories"
}
]
}
}

That's SharePoint data as JavaScript Object Notation. Notice the etag and href's to associated list data. Cool.

But ListData.svc isn't just for reading data. To experiment with it try deleting a list item. To do this in fiddler set the HTTP verb to “DELETE”, set the URL to a particular list item's URL (e.g. http://localhost/site/_vti_bin/ListData.svc/UserStories(2) where 2 is the ID for a list item), leave Request Headers empty, and hit Execute. Refresh the list of items and yours is gone. Permanently. The item isn't even in the recycle bin.

Security in WCF Data Services

So at this point you are undoubtedly concerned about security. Just to give some background ListData.svc is provided by a technology called WCF Data Services, which was formerly called ADO.Net Data Services, which was code named Astoria. And WCF Data Services is an implementation of the open data protocol (OData) which Scott Hanselman has an excellent podcast about (no really, download it now and listen to it on your next commute).

This technology was originally designed for exposing database data. And in this scenario you do have to handle your own security. SharePoint 2010, however, takes things a step further by giving you WCF Data Services (nearly) out of the box while also handling your authorization security.

For instance if you modify the row level permissions for a list item to deny read permissions to a user then ListData.svc will not return that list item for that user. If you try to access the URL for that list item directly SharePoint returns a 404. If you try to modify or delete a list item you don't have permissions to modify then SharePoint returns a 401 Unauthorized.

Very cool. I find this especially nice because you don't have to worry about maintaining your security code. It's intimately tied to SharePoint 2010's security model which is already quite sophisticated.

Incidentally, security is not all that's included when you use these services. SharePoint also performs data validation and returns the appropriate HTTP error codes if you for instance violate the awesome new new uniqueness constraint feature of SharePoint 2010.

Summary

SharePoint offers everything you've seen so far (nearly) out of the box and without a line of code. This sets the stage for writing some nice client side applications with technologies like ASP.Net AJAX 4.0 client site templating. Which is exactly what I intend to describe in Part 2 – Creating a read-only page with ASP.Net AJAX 4.0 Templates.

No comments: