Background
Tridion 2011 provides us with a WCF 3.5 Web Service named the “Core Service”. Previous versions shipped with the Business Connector, a SOAP-enabled webservice that accepts XML to perform actions in the GUI. However, if we want a simple RESTful web service to call from jQuery or .NET in any version of Tridion we need to roll it ourselves. In this article I want to demonstrate how to create a web service using the ServiceStack framework, jQuery, and Tridion.
Of course, I have to admit that this is the first step in my ultimate goal of having a GUI-based content porter – a ‘smart porter’ if you will, that will send a POST request to another CMS with Component XML and create the new item, but if the linked items are not available, it will call back to the original CMS, get the linked items, and continue. The example here proves it is not that difficult and with jsonp we can make cross-domain calls to various CMS servers – something that was not available at the time when Content Porter was created.
A better web service
In my current project we have 1 Dev server, multiple test servers, 1 Pre-Production and 1 Production server. All needing good content – specifically web application content in the form of string key/value pairs. For new strings we would like our Production Content editors to create the content once, and only create it from 1 custom page in Production for all systems. I want to create a jQuery friendly web service that could give me the ability to talk to the Tridion API on multiple CMS servers from 1 custom page on the Production CMS.
Benefits of this approach:
- Separate UI from server side Tridion API calls
- Easy to upgrade custom page in future by only changing Tridion API code, no GUI changes needed
- Follows similar approach as the Tridion 2011 Powertools
- Could be easily integrated in Tridion 2011 as a GUI extension
- Web Service can also be called from the Event System instead of a custom page
Behind the scenes we use jQuery jsonp to do a post of the form contents to each CMS. Also, we don’t want to have to content port each new string resource to all environments, since this incurs an extra manual task.
Enter ServiceStack
After searching for a simple and clean way to setup a web service in .Net, reading numerous WCF tutorials and not feeling convinced it was what I needed, I was relieved to find ServiceStack. It is a great alternative to a WCF Web Service.
Advantages of ServiceStack:
- Excellent examples
- No config
- Active project, great Google groups
- Native json support
- Host the web service in MVC, Windows Service, or Console app
- Easy to get started with NuGet MVC package
- Lean and clean
Creating a web service for Tridion using ServiceStack
I am working on Tridion version 5.3 and using Visual Studio 2010 SP1 ASP.NET MVC 3, but this code works on all Tridion versions. In a future article I will explain how we can solve the same scenario using the CoreService of 2011.
1. Getting ServiceStack installed
Create a new MVC 3 Web app. Use Web Platform Installer (WebPI) if you do not have it. It is also possible to host a ServiceStack web service with a Console app and also a Windows Service.
NuGet
NuGet is a package manager for Visual Studio and is awesome. It saves about 10-15 minutes per external assembly you might want to use. It is also the quickest way to get started with ServiceStack.
Get ServiceStack MVC Example
Right-click the solution, choose Add Library Reference, Select ServiceStack.Host.Mvc. If NuGet complains that your version of NuGet is incompatible, follow this StackOverflow post (http://stackoverflow.com/questions/6496640/nuget-upgrade-issue). For me on Windows 7 with a fresh install of VS 2010 SP1 I needed to open Visual Studio with ‘Run as Admin’, go to Tools, Extensions, Uninstall NuGet, then install it from the download. Good news is it installs in 2 seconds…
Re-open Visual Studio and right-click on the solution and select Manage NuGet Packages, select ‘Online’ in the left, then search for ServiceStack, and select ServiceStack.Host.Mvc. It will add references and a lot of items in the project.
Running the example code
1. Update global.asax with the ignore for /api route and the favicon.ico file.
routes.IgnoreRoute("api/{*pathInfo}");
routes.IgnoreRoute("favicon.ico");
2. Hit F5 and run the app. Run the project and see the Demo ToDo app. You can put a breakpoint in the Post method of the ToDoService and when you submit a ToDo you will step into the Post method. The ToDo App GUI is built using 100% Javascript using the BackboneJS framework and I will not cover it here. BackboneJS is an event-based JS framework with a steep learning curve. If you’d like to know more then head over to Tekpub.com and view their backbone.js screencast.
3. App_Start/ServiceStackFramework.cs – All the magic is here. In fact, EVERYTHING in the example is here, the model, services, etc. I like to split these into separate .cs files to make it more clear, and we’ll do that later in the code. For now, notice the DTO class contains the properties we are serializing back to the web form.
4. Structure – I prefer to keep the classes in separate folders based on their intent. I will use the following structure for the rest of this example:
- Models
- Repositories
- Services
The ServiceStackFramework.cs now only contains the very important URL Web Service routes, IoC injection, and service settings. Let’s leave it like that, add the using statements to our new .cs files for ServiceStack examples, and re-compile. Note, so far we have not changed and config files – and the good news is that we will not have to! ServiceStack is a no-config happy solution. 🙂
Creating the first Tridion Web Service:
Step 1: Creating the POCO (Plain Old CLR Object)
The first step is our model – the object that we will pass back and forth from the webpage to the web service. In this example we will create a KeyValuePair class. Right-click on the Models folder, select Add Class, and name the item ‘KeyValuePairComponent.cs’. Add the following properties to the class:
public class KeyValuePairComponent
{
public string Uri { get; set; }
public string Key { get; set; }
public string Value { get; set; }
public string ParentFolderUri { get; set; }
}
Step 2. Create the service class
Right-click the Services folder, select add class, and add KeyValuePairService.cs. The class needs to implement the RestServiceBase to be able to respond to service requests and the Type is our POCO created earlier. This is very important as it is the ‘glue’ to connect our service to our model. No config needed!
It is a good idea to follow the repository pattern and put our code in the Repository class we will create next to perform the actual work we are going to do instead of putting the code here in the service class. Note that the code will not compile yet since we do not have our Repository object. We will not implement the Delete action and for now I return null.
public class KeyValuePairService : RestServiceBase<KeyValuePairComponent>
{
public KeyValuePairRepository Repository { get; set; } //Injected by IOC
public override object OnGet(KeyValuePairComponent keyValuePair)
{
return Repository.GetByUri(keyValuePair.Uri);
}
public override object OnPost(KeyValuePairComponent keyValuePair)
{
return Repository.Store(keyValuePair);
}
public override object OnPut(KeyValuePairComponent keyValuePair)
{
return Repository.Store(keyValuePair);
}
public override object OnDelete(KeyValuePairComponent keyValuePair)
{
//Repository.DeleteById(request.keyValuePair);
return null;
}
}
Step 3. Create the Repository
Add a new class to the Repositories folder and call it ‘KeyValuePairRepository.cs’.
We need to add References to the Tridion DLLs, Tridion.ContentManager.Interop.cm_defines.dll and Tridion.ContentManager.Interop.cm_tom.dll, located in Tridion CMS server at /Tridion/Bin/Client/PIA folder. Logging is needed – so I will use NuGet to grab the log4net package and then updating my web.config with the log4net config. We also need to update the Global.asax with the log4net configuration call.
// add to Application_Start()
log4net.Config.XmlConfigurator.Configure()
Our new Repository class calling the Tridion API looks like this:
public class KeyValuePairRepository
{
string impersonationUser = "tst-vega28-cms\\curlettr";
private static readonly ILog log = LogManager.GetLogger("KeyValuePairRepository");
public KeyValuePairComponent GetByUri(string uri)
{
TDSE tdse = null;
Component component = null;
KeyValuePairComponent keyValuePair = new KeyValuePairComponent() { Uri = uri };
try
{
tdse = new TDSE();
tdse.Impersonate(impersonationUser);
tdse.Initialize();
component = tdse.GetObject(keyValuePair.Uri, EnumOpenMode.OpenModeView);
keyValuePair.Key = component.Fields["key"].value[1];
keyValuePair.Value = component.Fields["value"].value[1];
return keyValuePair;
}
catch (Exception ex)
{
log.ErrorFormat("Error getting Component. " + keyValuePair.ToString() + " Error:" + ex.Message + ", " + ex.ToString());
throw;
}
finally
{
if (component != null)
Marshal.ReleaseComObject(component);
if (tdse != null)
Marshal.ReleaseComObject(tdse);
}
return keyValuePair;
}
public KeyValuePairComponent Store(KeyValuePairComponent keyValuePair)
{
log.Debug("Start KeyValuePairComponent Store");
if (keyValuePair.ParentFolderUri == null)
throw new Exception("Must specify a parent folder");
if (keyValuePair.Key == null)
throw new Exception("Must specify a Key");
TDSE tdse = null;
Schema schema = null;
Component component = null;
string schemaWebDavUrl = "/webdav/30%20Content%20-%20Global/Building%20Blocks/System/Schemas/Generic%20Schemas/S_String.xsd";
try
{
tdse = new TDSE();
tdse.Impersonate(impersonationUser);
tdse.Initialize();
schema = tdse.GetObject(schemaWebDavUrl, EnumOpenMode.OpenModeView);
component = tdse.GetNewObject(ItemType.ItemTypeComponent, keyValuePair.ParentFolderUri);
component.Schema = schema;
component.Title = keyValuePair.Key;
component.Fields["key"].value[1] = keyValuePair.Key;
component.Fields["value"].value[1] = keyValuePair.Value;
component.Save(true);
keyValuePair.Uri = component.ID;
}
catch (Exception ex)
{
log.ErrorFormat("Error creating Component. " + keyValuePair.ToString() + " Error:" + ex.Message + ", " + ex.ToString());
throw;
}
finally
{
if (component != null)
Marshal.ReleaseComObject(component);
if (schema != null)
Marshal.ReleaseComObject(schema);
if (tdse != null)
Marshal.ReleaseComObject(tdse);
}
return keyValuePair;
}
Step 4. Setting the WebService Endpoint URI
We need to specify the URL to access our new web service. The magic happens in the ServiceStackFramework.cs class. Look for the comment ‘//Configure User Defined REST Paths’ and add our new Service to the list of Routes, and in the end my Routes looks like this:
Routes
.Add<Hello>("/hello")
.Add<Hello>("/hello/{Name*}")
.Add<Todo>("/todos")
.Add<Todo>("/todos/{Id}")
.Add<KeyValuePair>("/tridion/component/keyvaluepair");
Now, our Post and Get methods in our new Service class are wired up. Notice the /api/ is not in the beginning of our route, but we will see later we must use it when calling our service.
Step 5. Wiring up the Repository to the IoC Container
The last step we need to do is to tell our Funq IoC container about our new Repository class. We have to do this every time we have a new Model object. In the same file as above, ServiceStackFramework.cs, add our new Repository to the container:
//Register all your dependencies
container.Register(new TodoRepository());
container.Register(new KeyValuePairRepository());
Add code to the ServiceStackFramework.cs class to allow cross-domain posts
// Allow cross-domain posts
SetConfig(new EndpointHostConfig
{
GlobalResponseHeaders =
{
{ "Access-Control-Allow-Origin", "*" }, // You probably want to restrict this to a specific origin
{ "Access-Control-Allow-Methods", "PUT, GET, POST, DELETE, OPTIONS" },
{ "Access-Control-Allow-Headers", "Content-Type" }
},
});
Step 6. Test the Add method
Add a new HTML page to the Solution. Right-click the Project, select New Item, Web type, HTML Page. Name it AddKeyValuePair.
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Add KeyValuePair</title>
</head>
<body>
<form method="post" action="http://TridionDev2011:8001/WebService4Tridion/api/tridion/component/keyvaluepair">
<label>Key:</label><input type="text" name="Key" id="Key" />
<label>Value:</label><input type="text" name="Value" id="Value" />
<label>Create in folder:</label><input type="text" name="ParentFolderUri" id="ParentFolderUri" />
<input type="submit" value="Save" />
</form>
</body>
</html>
The important part is the form tag – notice the method is ‘post’ and the action starts with ‘api’. This is very important as it tells MVC not to process the request with normal MVC routing. Remember the first step of adding the ignores to the api path in Global.asax? This is a very easy to miss and important to get right, otherwise you will get errors. Each text field uses the name attribute the specify the Class property it maps the form to in the .NET class, and if not the same, the web service will have a value of null for the property.
Open the KeyValuePairService.cs file and place a breakpoint on the Post method:
return Repository.Store(keyValuePair);
Set the AddKeyValuePair.html page as the Start page by right-clicking on it and select ‘Set as Start Page’. Run the solution by hitting F5. Fill out the form, hit the save button, and you should hit the breakpoint. Continue and you will get the default ServiceStack return page. If you inspect the keyValuePair object you should see the properties populated. Pretty cool, right?
Before we get into the Tridion API things, let’s test the Get method. Create an HTML page like in the previous step, name it GetKeyValuePair.html, and paste the following HTML.
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Get KeyValuePair</title>
</head>
<body>
<form method="get" action="/api/tridion/component/keyvaluepair">
<label>URI:</label><input type="text" name="Uri" id="Uri" />
<input type="submit" value="Get KeyValuePair" />
</form>
</body>
</html>
Remember to set a breakpoint in the OnGet method of the KeyValuePairService. Set it as the start page, hit F5, and the following breakpoint should be hit:
return Repository.GetByUri(keyValuePair.Uri);
Yeah! We have successfully created our web service and tested that our new URI endpoints are working.
Step 7. Test the Get Method – Let’s call our web service with jQuery instead of an HTML post / get.
Add the following code to our Get page:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script type="text/javascript" src="Scripts/jquery-1.5.1.js"></script>
<script type="text/javascript">
$(function () {
function GetKeyValuePair(dataString) {
$.ajax({
type: "GET",
url: "http://TridionDev2011:8001/WebService4Tridion/api/tridion/component/keyvaluepair",
dataType: "jsonp",
jsonp: true,
crossDomain: true,
data: dataString,
success: function (data) {
$('#KvpUri').text(data.uri);
$('#KvpKey').text(data.key);
$('#KvpValue').text(data.value);
},
error: function (xhr, ajaxOptions, thrownError) {
alert('status=' + xhr.status + ', err=' + thrownError);
}
});
}
$('#btnGet').click(function (event) {
var dataString = $('#frmKeyValuePair').serialize();
GetKeyValuePair(dataString);
});
});
</script>
<title>Get KeyValuePair</title>
</head>
<body>
<form method="get" id="frmKeyValuePair">
<label>URI:</label><input type="text" name="Uri" id="Uri" />
<input id="btnGet" type="button" value="Get KeyValuePair" />
</form>
URI:<span id="KvpUri"></span><br />
Key:<span id="KvpKey"></span><br />
Value:<span id="KvpValue"></span><br />
</body>
</html>
Notice that the url is the same as from our form tag before. Also, the ‘Uri’ property is all lowercase because ServiceStack removes CamelCase from the properites, specified in the ServiceStackFramweork.cs class. ServiceStack.Text.JsConfig.EmitCamelCaseNames = true;
Step 8. Create new Component with AJAX Post
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Add KeyValuePair</title>
<script type="text/javascript" src="Scripts/jquery-1.5.1.js"></script>
<script type="text/javascript">
$(function () {
function GetKeyValuePair(dataString) {
$.ajax({
type: "POST",
url: "http://TridionDev2011:8001/WebService4Tridion/api/tridion/component/keyvaluepair",
dataType: "jsonp",
jsonp: true,
crossDomain: true,
data: dataString,
success: function (data) {
$('#KvpUri').text(data.uri);
$('#KvpKey').text(data.key);
$('#KvpValue').text(data.value);
$('#saveResults').show();
},
error: function (xhr, ajaxOptions, thrownError) {
alert('status=' + xhr.status + ', err=' + thrownError);
}
});
}
$('#btnSave').click(function (event) {
document.body.style.cursor = 'wait';
var dataString = $('#frmKeyValuePair').serialize();
GetKeyValuePair(dataString);
document.body.style.cursor = 'default';
});
});
</script>
</head>
<body>
<form method="post" id="frmKeyValuePair" >
<label>Key:</label><input type="text" name="Key" id="Key" />
<label>Value:</label><input type="text" name="Value" id="Value" />
<label>Create in folder:</label><input type="text" name="ParentFolderUri" id="ParentFolderUri" />
<input id="btnSave" type="button" value="Save" />
</form>
<div id="saveResults" style="display:none">
<b>Save results</b><br />
URI:<span id="KvpUri"></span><br />
Key:<span id="KvpKey"></span><br />
Value:<span id="KvpValue"></span><br />
</div>
</body>
</html>
Summary
It is a real pleasure working with ServiceStack and the Tridion API together. It feels right – little config, separation of concerns, future-proof, and full of possibilities. I hope this article gave you some ideas and you can go further by creating your own web services and calling them from a web form, .NET Event System class, or even a PowerShell script. Enjoy!
Download the code (WebService4Tridion-Example.zip, ~2MB)