Developing a Web Services API for EPiServer


Background

Among web application providers, an API is increasingly becoming de rigueur. Facebook are increasingly pushing to become a passport for the entire web, Google perform complex calculations on demand via their Maps API and even the UK Government is sharing the data it collects.

With this in mind, it came as no surprise when a client with an EPiServer installation powering their interactive websites asked us to develop an API in order to enable functionality for a range of new client applications beyond the website itself, beginning with an Android mobile app.

EPiServer Web Services Integration

The existing EPiServer-based web application contained a substantial amount of complex business logic built around the Model View Presenter (MVP) framework, with configuration and custom data stored in EPiServer along with the website content.

The requirement was to expose the business logic via a new secure web services tier. These were built using Windows Communication Foundation (WCF), hooked in to the the MVP architecture, allowing us to re-use all of our existing code with no modification to our service objects and only minor modifications to our domain objects (in order to support serialisation).

Service Definition

As the amount of data we were required to transport was very small and would be very simply structured,  the problem lent itself to a simple REST service speaking JSON rather than a more complicated SOAP system.  SOAP is very useful for expressing complex data and applying constraints, but the messaging overhead of the SOAP envelope and XML encoding would not be insignificant when considering the limited bandwidth and latency of the mobile networks our data would be carried over.

Another advantage of using REST here was that we could take advantage of the HTTP caching our client was already using. A request to get example.org/api/resource/12345 would quite happily sit in the cache waiting for another request while the HTTP protocol explicitly states that put, post and delete requests should invalidate any cached resources affected. Again, not ideal for our already slow mobile network and also an issue for our servers.

As we now knew we were developing a JSON service with a RESTful interface, we could immediately define an interface in C#. Our example method is required to speak to EPiServer to select an item of data stored within the CMS. A function already performed by our own business logic currently powering the website.

[ServiceContract]
public interface IService
{
	[OperationContract]
	[WebGet(UriTemplate = "DataItem", ResponseFormat = System.ServiceModel.Web.WebMessageFormat.Json)]
	DataItem GetSuitableDataItem();
}

The various attributes applied to the interface are part of WCF. The first one, ServiceContract, tells the framework that this interface defines the contract of a web service while the OperationContract attribute marks a method that should be exposed via the service. The final attribute, WebGet, explains how the method should be accessed via HTTP and how it should behave. In this example WebGet means that the method should respond to HTTP get requests, the UriTemplate parameter explains that the URL /DataItem should be interpreted as a call to this method and the ResponseFormat parameter specifies that the service will respond with a JSON message. This is used to build a response later on, setting the Content-Type response header to the appropriate MIME type, application/json, and encoding the object returned by the method using the appropriate scheme.

Finally, comes the standard definition of the actual method requiring implementation.

Object Serialisation

As mentioned previously, we are adding this API to an existing application so our DataItem class already exists. However, as this is our first foray into exposing parts of this application, we have not yet needed to define how to serialise our business objects. This, again, is made very simple using WCF.

[DataContract(Name = "DataItemName")]
public class DataItem
{
	[DataMember(Name = "PropertyName")]
	public string Property { get; set; }
}

As with the service, everything is handled by attributes. The DataContract attribute sets a name for our object type, which could be anything we like not just DataItem, and the DataMember attribute labels which properties we want to expose in our serialised object and what they will be called.

The above example of our simple DataItem object would then be serialised in JSON as

{"PropertyName":"<em>The value of Property</em>"}

Service Implementation

With serialisation in place and the service defined in an interface, the only remaining task is to implement the service using our pre-existing business logic libraries.

public class Service : IService
{
	public DataItem GetDataItem()
	{
		DataItem theItem = StaticBusinessLogicService.Instance.GetItem()
		return theItem;
	}
}

Adding Security

A service built as easily as this is almost too good to be true and there is still one outstanding issue of how to ensure that only our registered users can access the data. Built in to our EPiServer application is a custom MembershipProvider, operated by the client across their entire range of web apps. We can access this via a standard Member interface but would rather not do so in every single web method. Fortunately, we have the RequestInterceptor interface which can be used to intercept all requests to the service.

Our very simple interceptor will examine every incoming request to look for username and password headers which can then be given to the Membership for validation.

public class BasicAuthenticationInterceptor : RequestInterceptor
{
	public BasicAuthenticationInterceptor() : base(false) { }
	public override void ProcessRequest(ref System.ServiceModel.Channels.RequestContext requestContext)
	{
		WebHeaderCollection headers = ((HttpRequestMessageProperty) requestContext.RequestMessage.Properties[HttpRequestMessageProperty.Name]).Headers;
		string username = httpProperties.Headers["x-header-username"];
		string password = httpProperties.Headers["x-header-password"];

		bool userValidated = Membership.ValidateUser(username, password);
		if (!userValidated)
		{
			Message reply = Message.CreateMessage(MessageVersion.None, null);
			HttpResponseMessageProperty responseProperty = new HttpResponseMessageProperty() {
				StatusCode = HttpStatusCode.Forbidden, SuppressEntityBody = true
			};
			reply.Properties[HttpResponseMessageProperty.Name] = responseProperty;
			requestContext.Reply(reply);
			requestContext = null;
		}
	}
}

The interceptor takes the headers from the intercepted request and selects the ones it needs for the validation. If they are not recognised as valid, then the a new HTTP response message is created and, crucially as REST is based around making use of what HTTP provides, the status code is set to an approriate Forbidden status. This also avoids the need to create and document custom error codes and messages.

Service Factory

When the application starts up, the ServiceHostFactory is responsible for creating the services and configuring them as specified in web.config. At this point the interceptor can be added to the service from where it will automatically step in every time the service is called.

public class AppServiceHostFactory : ServiceHostFactory
{
	protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
	{
		try
		{
			WebServiceHost2 result = new WebServiceHost2(serviceType, true, baseAddresses);
			result.Interceptors.Add(new BasicAuthenticationInterceptor());
			return result;
		}
		catch (Exception exc)
		{
			//handle exceptions appropriately
		}
		return null;
	}
}

Conclusion

Implementing a fully-featured RESTful web services API on top of EPiServer was successful, and use of WCF with the existing MVP architecture made development particularly straight forward. The API is now live and powering GSK Android apps in multiple countries.

Leave a Comment

(required)