Having fun with code
  • Blog
  • Favorites
    • Favorite Books
    • Favorite Libraries
    • Favorite Tools
    • Good Reads
  • Contact
  • Home /
  • .NET /
  • Building a Web API in SharePoint 2010 with ServiceStack

Building a Web API in SharePoint 2010 with ServiceStack

Posted on May 4, 2012 by Matt C. in .NET, SharePoint
Tweet

Recently, while building a custom service application in SharePoint, I was exploring various ways of creating a web API, hosted within SharePoint. Obviously, using WCF is the standard in SharePoint, but it certainly is not the only way. In this post we will create the barebone essentials for building out a web service API, hosted within SharePoint 2010, using ServiceStack.

Background

Recently, while building a custom service application in SharePoint, I was exploring various ways of creating a web API, hosted within SharePoint. Obviously, using WCF is the standard in SharePoint, but it certainly is not the only way.

That’s when the thought of trying ServiceStack came into existence.

Why ServiceStack? Because it gives you XML, JSON, JSV, CSV, SOAP 1.1 and SOAP 1.2 response types out-of-the-box! That’s pretty cool, and it’s just a start. It also provides a consistent way of developing services and great performance (check out these benchmarks). Check out this interesting read by Phillip Haydon as well as he takes ServiceStack for a spin. Most of all, ServiceStack is super fun.

In this post we will create the barebone essentials for building out a web service API, hosted within SharePoint 2010, using ServiceStack.

Getting Started

Recent Update (Dec. 26, 2012)

Some mods to the ServiceStack platform have recently made it easier to setup the App Host, and we don’t need to modify the ServiceStack code any longer (as described in this post). The rest remains the same. That said, SharePoint still requires that the ServiceStack assemblies be strongly signed. I have added a new project to my skydrive folder for this blog post called “SP123″, please see the readme.txt file in that *.zip file with more details.

Let’s flesh out the SharePoint project. I’ll create 1 class library project, and 1 empty SharePoint project in my solution. The solution MUST be a Farm Solution, because we’re going to have to do a web.config mod, BUT, only to deploy the infrastructure. This will play really nicely with sandboxed solutions once the API is deployed, especially with jQuery or whatever other JS framework you fancy.

You will need to download the zip files or clone the Git repositories if you want to deploy ServiceStack to the GAC. Oh, and I’ll be making 1 tiny little change to the ServiceStack source code (BUT, you don’t have to if you are ok with the alternative, which I’ll describe later). So all in all, you can still make it work without this unfortunate thing, by accepting the alternative web.config mods (described later in this post), and NOT deploying the dlls to the GAC (deploy to the bin instead).

Project Structure

My final solution looks like this:

image

Deploying ServiceStack

To deploy to the GAC, I strongly typed SSPLoveSS.WebApi, as well as all the ServiceStack libraries, and then I froze the versions on all the libraries for this exercise as well.

image

To deploy the ServiceStack dlls, either use gacutil on your dev (if you strongly type them like I’m doing), or add the dlls to the SharePoint deployment package and then deploy to the GAC or the bin:

image

API Development

We’ll create an API that – RESTfully:

  • Lists all the groups in a web
  • Lists all the members of a group
  • Lists all the users in a web
  • Allows us to create a new group
  • Allows us to delete a group
  • Allows us to modify a group name

Our RESTful resources “could” look like this.

  • /api/groups: lists all groups in a web (verb=GET), create a group (verb=PUT)
  • /api/groups/{groupName}: deletes a group (verb=DELETE)
  • /api/groups/{groupName}/members: lists all members of a group (verb=GET), adds a member to a group (verb=POST)
  • /api/groups/{groupName}/members/{memberName}: removes a member from a group (verb=DELETE)

You certainly could do this! But, you don’t take advantage then of SharePoint 2010 built-in url rewriting that automatically gives you SPSite and SPWeb context, like it does for /_layouts/ and /_vti_bin/ urls. We want our API to be context specific to the SPWeb you are in, so let’s change the API resource urls to the following, which will inherit the SharePoint url rewriting magic (small price to pay for the benefit).

  • /_layouts/api/groups: lists all groups in a web (verb=GET), create a group (verb=PUT)
  • /_layouts/api/groups/{groupName}: deletes a group (verb=DELETE)
  • /_layouts/api/groups/{groupName}/members: lists all members of a group (verb=GET), adds a member to a group (verb=POST)
  • /_layouts/api/groups/{groupName}/members/{memberName}: removes a member from a group (verb=DELETE)

The web.config changes (just add this to the feature receiver to insert the changes during deployment, see source code download at the end of this post) are as follows:

image

In order to make the path “_layouts/api” work above, I had to insert 2 lines into the ServiceStack source, in the “ServiceStackHttpHandlerFactory” class. This class, as written in ServiceStack makes assumptions about the virtual directory format (as far as I can tell, after a cursory look), and these assumptions don’t work for us. My changes are as follows:

public static IHttpHandler GetHandlerForPathInfo(string httpMethod, string pathInfo, string requestPath, string filePath)
{
    var pathParts = pathInfo.TrimStart(‘/’).Split(‘/’);
    if (pathParts.Length == 0) return NotFoundHttpHandler;
    // BEGIN MOD for SharePoint
    if (pathParts.Length > 2 && pathParts[0].Equals("_layouts", StringComparison.OrdinalIgnoreCase))
        pathParts = pathParts.Skip(2).ToArray();
    // END MOD for SharePoint
    var handler = GetHandlerForPathParts(pathParts);
    if (handler != null) return handler;

    // …. rest of method ….

}

The alternative to making these changes to the source code are to specify the individual Handlers in the web.config, instead of using the Handler factory class. This is the format used in the ServiceStack examples download. That would look something like this instead:

image

As far as building services, I won’t go over the details here, it’s extremely simple and ServiceStack has a very clear tutorial on how to do this. I highly suggest you follow it to get started. If you use Nuget, you can use the “Install-Package ServiceStack” command. Avoid the “ServiceStack.Host.AspNet” package in a .NET 3.5 project because the package download has some framework dependency issues with a couple 3rd party libraries that require .NET 4.0 (at the time of this writing, i.e.: Web Activator).

The services I implemented here to meet the use-case are straightforward, here’s what the “GroupService” looks like:

[CollectionDataContract(Name = "groups", ItemName = "group")]
public class GroupList : List
{
    public GroupList()
    {
    }

    public GroupList(IEnumerable collection)
        : base(collection)
    {
    }
}

[DataContract(Name = "group")]
public class Group
{
    public Group() { Members = new UserList(); }
    public Group(SPGroup group)
        : this()
    {
        this.Id = group.ID;
        this.Name = group.Name;

        this.Members = new UserList(group.Users.Cast().Select(u => new User(u)));
        this.MemberCount = Members.Count;
    }

    [DataMember(Name = "id", Order = 1)]
    public int Id { get; set; }
    [DataMember(Name = "name", Order = 2)]
    public string Name { get; set; }
    [DataMember(Name = "count", Order = 3)]
    public int MemberCount { get; set; }
    [DataMember(Name = "members", Order = 4)]
    public UserList Members { get; set; }
}

[RestService("/_layouts/api/groups", "GET,PUT")]
[RestService("/_layouts/api/groups/{Ids}", "GET,POST,DELETE")]
public class GroupRequest : Group
{
    public string Ids { get; set; }
}

public class GroupService : BaseService
{
    // Get 1 or more groups
    public override object OnGet(GroupRequest request)
    {
        if (!IsAuthenticated) return new HttpResult(HttpStatusCode.Unauthorized, "Requires authentication");

        try
        {
            var ids = string.IsNullOrEmpty(request.Ids)
                          ? null
                          : request.Ids.Split(',').Select(i => Convert.ToInt32(i)).ToArray();

            var groups = new List();
            var spGroups = CurrentWeb.Groups;
            foreach (SPGroup spGroup in spGroups)
            {
                if (ids == null || ids.Contains(spGroup.ID))
                    groups.Add(new Group(spGroup));
            }

            return new GroupResponse(CurrentWeb, groups, GetQueryStringValue("sort", "asc"));
        }
        catch
        {
            return new HttpResult(HttpStatusCode.BadRequest, "Invalid request");
        }
    }

    // Create a group
    public override object OnPut(GroupRequest request)
    {
        if (!IsAuthenticated) return new HttpResult(HttpStatusCode.Unauthorized, "Requires authentication");

        if(string.IsNullOrEmpty(request.Name))
            return new HttpResult(HttpStatusCode.BadRequest, "Name is missing");

        try
        {
            var web = CurrentWeb;
            var groupName = request.Name;
            var user = SPContext.Current.Web.CurrentUser;

            var groupArray = new string[] {groupName};

            web.AllowUnsafeUpdates = true;

            var groupCollection = web.SiteGroups.GetCollection(groupArray);
            if (groupCollection.Count == 0)
            {
                web.SiteGroups.Add(groupName, user, null, "The " + groupName + " group");
            }
            var spGroup = web.SiteGroups[groupName];

            var roleDef = web.RoleDefinitions.GetByType(SPRoleType.Contributor);
            var roles = new SPRoleAssignment(spGroup);
            roles.RoleDefinitionBindings.Add(roleDef);
            web.RoleAssignments.Add(roles);

            //// Assign to site
            spGroup.AddUser(user);
            spGroup.Update();

            web.AllowUnsafeUpdates = false;

            return new HttpResult(HttpStatusCode.OK, "SUCCESS");
        }
        catch (UnauthorizedAccessException uex)
        {
            return new HttpResult(HttpStatusCode.Forbidden, uex.Message);
        }
        catch (Exception ex)
        {
            return new HttpResult(HttpStatusCode.BadRequest, ex.Message);
        }
    }

    // Update the group
    public override object OnPost(GroupRequest request)
    {
        if (!IsAuthenticated) return new HttpResult(HttpStatusCode.Unauthorized, "Requires authentication");

        if (string.IsNullOrEmpty(request.Name))
            return new HttpResult(HttpStatusCode.BadRequest, "Name is missing");

        try
        {
            var ids = string.IsNullOrEmpty(request.Ids)
                          ? null
                          : request.Ids.Split(',').Select(i => Convert.ToInt32(i)).ToArray();

            if (ids == null || ids.Length == 0)
                return new HttpResult(HttpStatusCode.BadRequest, "Id is missing");

            CurrentWeb.AllowUnsafeUpdates = true;

            var spGroup = CurrentWeb.Groups.GetByID(ids[0]);
            spGroup.Name = request.Name;
            spGroup.Update();

            CurrentWeb.AllowUnsafeUpdates = false;

            return new HttpResult(HttpStatusCode.OK, "SUCCESS");
        }
        catch (UnauthorizedAccessException uex)
        {
            return new HttpResult(HttpStatusCode.Forbidden, uex.Message);
        }
        catch (Exception ex)
        {
            return new HttpResult(HttpStatusCode.BadRequest, ex.Message);
        }
    }

    // Delete 1 or more groups
    public override object OnDelete(GroupRequest request)
    {
        if (!IsAuthenticated) return new HttpResult(HttpStatusCode.Unauthorized, "Requires authentication");

        try
        {
            var ids = string.IsNullOrEmpty(request.Ids)
                          ? null
                          : request.Ids.Split(',').Select(i => Convert.ToInt32(i)).ToArray();

            if (ids == null || ids.Length == 0)
                return new HttpResult(HttpStatusCode.BadRequest, "Ids are missing");

            CurrentWeb.AllowUnsafeUpdates = true;

            foreach (var id in ids)
            {
                CurrentWeb.Groups.RemoveByID(id);
            }
            CurrentWeb.Update();

            CurrentWeb.AllowUnsafeUpdates = false;

            return new HttpResult(HttpStatusCode.OK, "SUCCESS");
        }
        catch (UnauthorizedAccessException uex)
        {
            return new HttpResult(HttpStatusCode.Forbidden, uex.Message);
        }
        catch (Exception ex)
        {
            return new HttpResult(HttpStatusCode.BadRequest, ex.Message);
        }
    }
}

I added an event receiver to the feature (see source code download) to configure web.config and the global.asax file to enable the ServiceStack handlers. And that’s it, we’re ready to deploy.

See it in Action

ServiceStack automatically gives you a full metadata screen that documents the web services. ServiceStack automatically supports JSON, XML, JSV, CSV, SOAP 1.1 and SOAP 1.2 response types.

ss_1

ServiceStack automatically describes each method with supported VERBS and how to use it.

ss_2

ServiceStack automatically gives you a nice HTML5 view of the data for GET requests, and links to call the other response types.

ss_3_revised

ServiceStack supports the standard HTTP headers to call services as desired. In this screenshot, the Content Type specified is application/json on a GET request for all groups in an SPWeb.

ss_4a_json

In the following screenshot, the Content Type specified is application/xml, using an OVERRIDE in the URL. The serialized XML obeys all the DataContract serialization attributes for naming and ordering conventions.

ss_4b_xml

Here we create a SharePoint group using a PUT request.

ss_5_create_group

Here we update a SharePoint group name using a POST request.

ss_5_update_group

Here we delete a SharePoint group using a DELETE request.

ss_5_delete_group

ServiceStack also does great error handling, and gives you full control over what Status Codes you wish to return and any accompanying messaging.

ss_6_errorhandling

Conclusion

So there we have it. These APIs are obviously completely compatible with jQuery and any other javascript framework, and they provide a great foundation for hosting a nice self-documenting API within SharePoint. Once the infrastructure file is changed, updating the API with new methods just takes a simple DLL update.

Well, hopefully you enjoyed this post as much as I enjoyed putting this little prototype project together. Thanks for reading, and please do check out the download and modify as you see fit. You’ll have to download the ServiceStack code/binaries from Github.

Download Code csv, json, jsv, rest, servicestack, sharepoint, soap, web api

7 comments on “Building a Web API in SharePoint 2010 with ServiceStack”

  1. holger says:
    December 21, 2012 at 5:52 am

    Hi Matt,

    I would like to use the specify the individual handlers, but it seems I don’t get it running. Do you have any help for me? I didn’t find anything in the samples for it.
    I do have another question: Will ADFS work within Sharepoint?

    thanks,
    holger

    Reply
    • Matt C. says:
      December 26, 2012 at 7:08 pm

      Hey holger,
      Sorry I didn’t get back sooner, holiday duties :-)
      I need to revisit this post slightly, because the code changes to the SS stack are no longer needed. The SS *.dlls do need to be signed still for SharePoint to work with them.
      When you configure the AppHost, you can set the factory as so:

      SetConfig(new EndpointHostConfig {
      ServiceStackHandlerFactoryPath = "_layouts/api"
      });

      After you deploy, you then just need to make sure you changed the global.asax and web.config files to give IIS what it needs to host servicestack.
      I have updated the download (which now points to a skydrive folder) with a “SP123″ project which basically does the out of the box todo repository implementation from servicestack.

      Read the README.txt file in that project to deploy and get further instructions on modifying the global.asax and web.config files. I have included some signed *.dlls if you just want to test.
      Let me know how that works out for you.

      Reply
  2. Jacques says:
    August 19, 2012 at 4:58 am

    Hmmm… apart from the code changes your making to ServiceStack, there’s also some interesting code to make the SharePoint site getting the feature to inherit from a custom SPHttpApplication by rewriting the global.asax, in order to initialize ServiceStack’s AppHostBase. Maybe another short post doing a quick discussion of the activate and deactivate is in order?

    Thanks for the great post, BTW.

    Reply
    • Matt C. says:
      September 4, 2012 at 12:58 am

      Right on Jacques. Thanks for the blog post suggestion.

      Reply
  3. Dew Drop – May 7, 2012 (#1,321) | Alvin Ashcraft's Morning Dew says:
    May 7, 2012 at 8:38 am

    [...] Building a Web API in SharePoint 2010 with ServiceStack (Matt Cowan) [...]

    Reply
  4. Demis Bellot says:
    May 5, 2012 at 2:26 am

    Hi Matt,

    First of all, Great writeup!

    Also Just letting you know the ServiceStack version on NuGet (https://nuget.org/packages/ServiceStack) only contains .NET 3.5 binaries and none of the WebActivator dependencies that the NuGet Host.* packages contains.

    Reply
    • Matt C. says:
      May 5, 2012 at 8:55 am

      Super. Thanks Demis for the clarification. I updated the post.

      Reply

Leave a Reply Cancel 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>

Categories

  • .NET (18)
  • ASP.NET MVC (3)
  • Miscellaneous (2)
  • Python (1)
  • REST (2)
  • SharePoint (8)

Tags

.net ado.net autofac binding C# chrome code generation command line console application csv dapper di entity framework integration ioc job scheduling engine json jsv learning llblgen load balancing micro-orm mycorp odata orm people editor people picker picker controls picker dialog plugins pmp python Quartz.NET rest saf service application servicestack sharepoint smo soap sql sqlalchemy tornado web server validation web api

Archives

  • May 2013 (1)
  • March 2013 (1)
  • December 2012 (1)
  • November 2012 (1)
  • October 2012 (3)
  • September 2012 (2)
  • June 2012 (2)
  • May 2012 (1)
  • April 2012 (1)
  • February 2012 (2)
  • January 2012 (1)
  • December 2011 (2)
  • September 2011 (2)

Latest Tweets

  • Part 2: RESTful Api and UI for Typed Views and Typed Lists with LLBLGen and ServiceStack | Having fun with code http://t.co/7kbEnS4eT5
    May 12, 2013
  • New look for my blog http://t.co/IjSD6SAEMf designed with @ThemeBlvd framework
    May 6, 2013
  • @dennisriis doesn't have to be, checkout http://t.co/4JkUx5Rdv0
    April 10, 2013

Recent Comments

  • Dew Drop – May 14, 2013 (#1,546) | Alvin Ashcraft's Morning Dew on RESTful Api and UI for Typed Views and Typed Lists with LLBLGen and ServiceStack
  • Matt C. on SharePoint 2010 Service Application Development 101 – Getting Started
  • SP13 on SharePoint 2010 Service Application Development 101 – Getting Started
  • Matt C. on SimpleMembershipProvider in MVC4 for MySql, Oracle, and more with LLBLGen
  • Matt C. on Generating a fully RESTful Api and UI from a database with LLBLGen and ServiceStack (templates on Github and Demo)

Ways to connect

    • Linkedin
    • Twitter
    • Github
    • Google

Meta

  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org
(c) 2013 Having fun with code - "FunCoding" theme designed by mattjcowan using the Theme Blvd framework