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

Building a Web API in SharePoint 2010 with ServiceStack

May 4, 2012 / Matt C. / .NET, SharePoint
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

[box style="blue"]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.[/box] 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. [box style="warning"]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).[/box]

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. [button link="http://sdrv.ms/VeD7nu" color="primary" target="_blank" size="large" title="Download Code" icon_before="download"]Download Code[/button] csv, json, jsv, rest, servicestack, sharepoint, soap, web api

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

  1. Roberto Mauro says:
    June 20, 2014 at 8:03 am

    Hi Matt,
    have you ever experienced issues with ServiceStack cache? All works well for a week until consuming the web service leads me to an ArgumentException that is “magically” resolved with an iisreset (that’s why I supposed is an issue related to the cache). Any ideas?

    • Matt C. says:
      July 14, 2014 at 8:51 am

      Hey Roberto, if you’re still having this problem, I suggest you post a question on StackOverflow with some code and details surrounding the argument exception.

  2. Declarative authorization in REST services in SharePoint with F# and ServiceStack | Sergey Tihon's Blog says:
    June 27, 2013 at 3:50 pm

    […] This solution was inspired by the work of Matt Cowan “Building a Web API in SharePoint 2010 with ServiceStack“. […]

    • Matt C. says:
      June 27, 2013 at 4:07 pm

      Awesome!

  3. Grant says:
    June 23, 2013 at 2:14 pm

    This is pretty awesome. Does this propagate through the SharePoint 2010 authentication or is there some other authentication in place here?

    • Matt C. says:
      June 23, 2013 at 3:24 pm

      In my experience, you can just let SharePoint handle the authentication. In an intranet situation users are usually logged in with their windows creds so using SPContext in your service is all ready to go, or if you have a proxy on the front-end which is usually what I run into, authentication is pretty much already covered. That said, I’ve also used this in situations where one or more particular web applications / site collections are public with anonymous access turned on, so the services are then wide open for public consumption. In that case, you can just use the ServiceStack authentication feature and tag your services with the ServiceStack [ServiceStack.ServiceInterface.Authenticate] attribute.

      If you want to use the [Authenticate] attribute to protect your services, you can pretty much handle any security paradigm you want, and you may want to optimize it for your particular environment (maybe do something that plays well with your proxy front-end, or your authentication needs). If you just want to ensure the user is properly authenticated within SharePoint, you can just add a generic auth provider feature in your AppHost.


      Plugins.Add(new AuthFeature(() => new AuthUserSession(), new IAuthProvider[] { new SharePointAuthProviderForServiceStack() }));

      “SharePointAuthProviderForServiceStack” is a class you would write, that implements SharePoint logic. Just extend ServiceStack.ServiceInterface.Auth.AuthProvider.

      Override the IsAuthenticated property (and make sure the SPContext.Current, SPContext.Current.Web, SPContext.Current.Web.CurrentUser are not null).
      Override the IsAuthorized property (implement any logic you want there, or just -> return IsAuthenticated; and then let your services handle specific authorization scenarios).
      You would also override Authenticate() and OnFailedAuthentication() (see ServiceStack samples for more info, or if you want, I can send you a basic sample class I’ve used).

  4. 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

    • 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.

  5. 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.

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

      Right on Jacques. Thanks for the blog post suggestion.

  6. 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) […]

  7. 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.

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

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

Categories

  • .NET (20)
  • ASP.NET MVC (4)
  • JAMstack (2)
    • Headless CMS (2)
  • Miscellaneous (2)
  • Python (1)
  • REST (3)
  • SharePoint (8)
  • WordPress (1)

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 2020 (2)
  • November 2013 (1)
  • June 2013 (1)
  • 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)
(c) 2013 Having fun with code - "FunCoding" theme designed by mattjcowan using the Theme Blvd framework