Serving up JSON Data from a SharePoint Application Page Pseudo Service


Sometimes, the SharePoint JSOM is just too unwieldy.

So on a recent project I needed to conjure up SharePoint group information, and for each of these groups also the users which are group members. I wanted this data in JSON format since I’m using knockoutjs for the UI and presentation.

After taking a look at the JSON API for groups, I resolved to find a better easier way of building this data, essentially what I want is a simple array of JSON objects representing groups and users in those groups, like so;

Simple JSON Data
Simple JSON Data

Easy and straight-forward, but I didn’t want to have to write a bunch of grungy code to do this either. I like the SharePoint JSOM, but for a lot of data-retrieval type tasks, the interface is just too chatty and iterative, it has an imperative rather than declarative feel to it.

So, the solution to this for me was to write an Application Page, which does nothing more than accept parameters describing what to do, and return the results of that ‘operation’ as JSON data, a bit like page methods. From the client-side I’ll send POST requests to this page using an operation name (the name of a method implemented in the pages code-behind) and the parameters used by that method.

Sounds very much like what I’m wanting is a proper full blown WCF service hosted in SharePoint, and thats true, but the effort to do that and face the deployment considerations, versus, the scope and complexity of what I was trying to do didn’t stack up – this was a simple solution for a simple problem.

So, in your Visual Studio solution, add an Application Page project item, the ASPX part of this is dead simple;

<%@ Assembly Name="$SharePoint.Project.AssemblyFullName$" %>
<%@ Import Namespace="Microsoft.SharePoint.ApplicationPages" %>
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="asp" Namespace="System.Web.UI" Assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Assembly Name="Microsoft.Web.CommandUI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="MyAppService.aspx.cs" Inherits="Pdogs.ApplicationPageService.Layouts.Pdogs.ApplicationPageService.MyAppService" DynamicMasterPageFile="~masterurl/default.master" %>

<asp:Content ID="PageHead" ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server" />
<asp:Content ID="Main" ContentPlaceHolderID="PlaceHolderMain" runat="server" />
<asp:Content ID="PageTitle" ContentPlaceHolderID="PlaceHolderPageTitle" runat="server" />
<asp:Content ID="PageTitleInTitleArea" ContentPlaceHolderID="PlaceHolderPageTitleInTitleArea" runat="server" />

In the code behind file, first add the Page_Load method

private const string OpNameParameter = "op";

private const string JsonErrorFmt =
@"{{
		""error"" : {{
		""code"" : ""{0}"",
		""message"" : {{
			""lang"" : ""{4}"",
			""value"" : ""Method {1}: {2}{3}""
		}}
	}}
}}";

protected void Page_Load(object sender, EventArgs e)
{
	var opName = "Unknown";
	try
	{
		// get the operation name
		opName = Request.Params[OpNameParameter];
		if (string.IsNullOrEmpty(opName))
			throw new Exception(string.Format("{0} parameter not found in request!", OpNameParameter));

		// reflect on the method named by operation
		var opMethodInfo = GetType().GetMethod(opName);
		if (opMethodInfo == null) 
			throw new Exception("Operation not found!");

		// get reflected parameter value(s) from request
		var parameters = opMethodInfo.GetParameters()
								.Select(pn => Convert.ChangeType(Request.Params[pn.Name], pn.ParameterType))
								.ToArray();
		// invoke method
		var opResult = parameters.Length > 0
									? opMethodInfo.Invoke(this, parameters)
									: opMethodInfo.Invoke(this, null);
		// write response
		WriteResponse(200, opResult);
	}
	catch (Exception ex)
	{
		var exResult = string.Format(JsonErrorFmt, 
												400, 
												opName,
												ex.Message,
												ex.InnerException != null
													? string.Format(" &raquo; {0}", ex.InnerException.Message)
													: string.Empty,
												Web.Locale.Name);
		WriteResponse(400, exResult);
	}
	Response.End();
}

private void WriteResponse(int httpStatusCode, object opResult)
{
	Response.Clear();
	Response.StatusCode = httpStatusCode;
	Response.ContentType = "application/json; charset=utf-8";
	Response.Write(opResult);
}

Here we’re looking for a request parameter called op which indicates which ‘operation’ to perform, then we use reflection to find a code-behind method with that same name. Next, using the MethodInfo parameter data we attempt to retrieve values for the reflected methods parameters from within the request parameter data.

The method is then executed using reflection with the parameter (values) extracted from the request parameter data, and the return value of  the method is written to the response object with the ContentType set to application/json.

The method implemented in the application page code-behind is responsible for returning its result back in proper JSON format, and since this post started by whinging about groups and users, I’ll show you that method;

private class JsonGroup
{
	public int Id;

	public string FullName;
	public string DisplayName;
	public string Description;
	public JsonUser[] Users;
}

private class JsonUser
{
	public int Id;
	public string DisplayName;
	public string LoginName;
	public string Email;
	public bool IsSiteAdmin;
}

public string GetSiteAccessGroups(bool includeUsers)
{
	var re = new Regex(@"([^_ ]+)$");
	var associatedGroups = new[]
		{
			Web.AssociatedOwnerGroup,
			Web.AssociatedMemberGroup, 
			Web.AssociatedVisitorGroup
		};
	var data = 
		associatedGroups
			.Where(g => g != null)
			.Select(g => new JsonGroup
				{
					Id = g.ID,
					FullName = g.Name,
					DisplayName = re.Match(g.Name).Groups[1].Value,
					Description = g.Description,
					Users = includeUsers
								? g.Users.Cast<SPUser>()
									.Select(u=> new JsonUser
													{
														Id = u.ID, 
														DisplayName = u.Name, 
														LoginName = u.LoginName, 
														Email = u.Email, 
														IsSiteAdmin = u.IsSiteAdmin
													})
									.ToArray()
								: null
				}).ToArray();
	return CreateJsonResponse(data);
}

This method gets information about the Web in context’s (remember its an Application Page) 3 associated SharePoint security groups, and the users who are members of those groups – notice that I’m using a couple of classes to shape the data for JSON serialisation purposes.

I then use a helper method to serialise the data as a JSON encoded string, this string is then returned by the invoked method;

private static string CreateJsonResponse(object data)
{
	var js = new JavaScriptSerializer();
	string results;
	if (data is IList)
	{
		var list = (data as IList);
		var enumerable = list as object[] ?? list.Cast<object>().ToArray();
		var count = enumerable.Count();
		results = js.Serialize(new
			{
				d = new
					{
						results = enumerable,
						__count = count
					}
			});
	}
	else
	{
		results = js.Serialize(new
			{
				d = new
					{
						results = data
					}
			});
	}
	return results;
}

After this is built and deployed, you can call the application page ‘service’ from client-side using jQuery.post() as shown below;

var _els = {},
	_requestUrl = '';

function getGroups(e) {
	e.preventDefault();
	_els.results.empty();

	$.post(_requestUrl,
			{ op: 'GetSiteAccessGroups', includeUsers: true })
		.done(function (data) {
			$.each(data.d.results, function (i, el) {
				var li = $("<li/>")
							.append(el.Id + ": " + el.DisplayName + " (" + el.LoginName + ")"),
					ul = $("<ul/>");
				$.each(el.Users, function (e, uel) {
					ul.append("<li>" + uel.Id + ": " + uel.DisplayName + " (" + uel.LoginName + ")" + "</li>");
				});
				li.append(ul)
					.appendTo(_els.results);
			});
		})
		.fail(function (error) {
		});
}

$(function () {
	_requestUrl = _spPageContextInfo.webServerRelativeUrl
					+ '/_layouts/Pdogs.ApplicationPageService/MyAppService.aspx';
	_els.results = $("#results");
	_els.label = $("#label").text(_requestUrl);

	$("#getgroups").click(getGroups);
});

The results are then rendered out;

Simple UI showing results

And showing the transaction with fiddler;

Fiddler

If you’d like to download this project, its available on codeplex at http://spapas.codeplex.com/.

Published by

Phil Harding

SharePoint Consultant, Developer, Father, Husband and Climber.

2 thoughts on “Serving up JSON Data from a SharePoint Application Page Pseudo Service

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.