Sitefinity leaves a lot to be desired with the REST services. For example, take a look at the returned data from Sitefinity's news service out-of-the-box:
{
"Context":[
{
"Key":"String content",
"Value":"String content"
}
],
"IsGeneric":true,
"Items":[
{
"Author":"String content",
"AvailableLanguages":[
"String content"
],
"CommentsCount":2147483647,
"DateCreated":"/Date(928164000000-0400)/",
"DateModified":"/Date(928164000000-0400)/",
"DefaultPageId":"1627aea5-8e0a-4371-9022-9b504344e724",
"ExpirationDate":"/Date(928164000000-0400)/",
"Id":"1627aea5-8e0a-4371-9022-9b504344e724",
"IsDeletable":true,
"IsEditable":true,
"ItemsCount":2147483647,
"LastApprovalTrackingRecord":{
"DateCreated":"/Date(928164000000-0400)/",
"Note":"String content",
"Status":"String content",
"UIStatus":"String content",
"UserName":"String content"
},
"LifecycleStatus":{
"ErrorMessage":"String content",
"IsAdmin":true,
"IsLocked":true,
"IsLockedByMe":true,
"IsPublished":true,
"LastModified":"/Date(928164000000-0400)/",
"LastModifiedBy":"String content",
"LockedByUsername":"String content",
"LockedSince":"/Date(928164000000-0400)/",
"Message":"String content",
"PublicationDate":"/Date(928164000000-0400)/",
"SupportsContentLifecycle":true,
"WorkflowStatus":"String content"
},
"LiveContentId":"1627aea5-8e0a-4371-9022-9b504344e724",
"Owner":"String content",
"ParentUrl":"String content",
"ProviderName":"String content",
"PublicationDate":"/Date(928164000000-0400)/",
"Status":"String content",
"Title":"String content",
"UIStatus":"String content",
"Version":2147483647,
"VersionInfo":{
"ChangeDescription":"String content",
"ChangeType":"String content",
"Comment":"String content",
"CreatedByUserName":"String content",
"Id":"1627aea5-8e0a-4371-9022-9b504344e724",
"IsLastPublishedVersion":true,
"IsPublishedVersion":true,
"ItemId":"1627aea5-8e0a-4371-9022-9b504344e724",
"Label":"String content",
"LastModified":"/Date(928164000000-0400)/",
"NextId":"String content",
"NextVersionNumber":2147483647,
"Owner":"1627aea5-8e0a-4371-9022-9b504344e724",
"PrevVersionNumber":2147483647,
"PreviousId":"String content",
"Version":"String content",
"VersionNumber":2147483647
},
"WorkflowOperations":[
{
"ArgumentDialogName":"String content",
"ClosesForm":true,
"ContentCommandName":"String content",
"CssClass":"String content",
"DecisionType":"String content",
"OperationName":"String content",
"Ordinal":2147483647,
"PersistOnDecision":true,
"RunAsUICommand":true,
"Title":"String content",
"VisualType":0,
"WarningMessage":"String content"
}
]
}
],
"TotalCount":2147483647
}
Most of it is cryptic and unneeded. Although you could
implement your own RESTful WCF Service in Sitefinity, there is a gem Microsoft dropped into the MVC 4 Beta. I am referring to
Web API: a framework that makes it
easy to build HTTP services that reach a broad range of clients, including browsers and mobile devices. ASP.NET Web API is an ideal platform for building RESTful applications on the .NET Framework."
You know what this means? Gone are the days of fiddling with clunky class attributes, web.configs, serialization utilities, 3rd-party libraries, and other concoctions to get web services working for your ASP.NET website. Microsoft has unveiled the ultimate unification initiative for REST Web Services in ASP.NET WCF, MVC and WebForms. Its only dependency is ASP.NET 4.0 and can be used in production immediately! How elegant is this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web.Http;
namespace MyNamespace.MyApp.Services.Api
{
public class ValuesController : ApiController
{
// GET /api/<controller>
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET /api/<controller>/5
public string Get(int id)
{
return "value";
}
// POST /api/<controller>
public void Post(string value)
{
}
// PUT /api/<controller>/5
public void Put(int id, string value)
{
}
// DELETE /api/<controller>/5
public void Delete(int id)
{
}
}
}
Notice right away there is no need for serializing or adding any attributes with your strongly typed variables and return values. It is all abstracted away.. ready to be indulged with your code. This controller is accessible by the routes registration which links URLs to the methods by convention:
using System;
using System.Web.Http;
using System.Web.Routing;
namespace MyNamespace.MyApp
{
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
RouteTable.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
}
That's it! The routes are just URL rewrites to automagically wire it to the Controller class. It is the name of the class without the word "Controller". So for this example, I can get all "values" from "http://localhost/api/Values/Get". If you have used ASP.NET MVC before, then you should feel right at home. Now let us use this technology in our Sitefinity web app!
If you have not done so yet, install MVC 4 from Microsoft. It comes with Web API which is what we want. When this is done, open your SitefinityWebApp in Visual Studio and create a "Web API Controller Class" for news in the "~/Custom/Api" folder (it can be any folder you like).
Your news controller can get the data from the Sitefinity API and return them to the client in XML or JSON format automatically:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using SitefinityWebApp.Custom.Services.Models;
using Telerik.Sitefinity.GenericContent.Model;
using Telerik.Sitefinity.Modules.News;
namespace SitefinityWebApp.Custom.Services.Api.Controllers
{
public class NewsController : ApiController
{
private NewsManager _manager = null;
public NewsManager Manager
{
get
{
return _manager ??
(_manager = NewsManager.GetManager());
}
}
// GET /api/news/getall
public IEnumerable<NewsModel> GetAll()
{
var output = new List<NewsModel>();
this.Manager.GetNewsItems()
.Where(n => n.Status == ContentLifecycleStatus.Live)
.ToList()
.ForEach(n => output.Add(new NewsModel(n)));
return output;
}
// GET /api/news/getbyid/090F8BBC-7BF4-4755-8D0F-131CEBBE9621
public NewsModel GetById(Guid id)
{
var sfNewsItem = this.Manager.GetNewsItem(id);
return new NewsModel(sfNewsItem);
}
}
}
Instead of returning the native NewsItem object, let us use the constructor for translating it to a leaner model. This way, we get a slimmed down version of the news items without getting any unnecessary noise or confusion with our data. You can even add custom fields if you like:
using System;
using System.Text.RegularExpressions;
using Telerik.Sitefinity.News.Model;
namespace SitefinityWebApp.Custom.Services.Models
{
public class NewsModel
{
//DEFAULT PROPERTIES
public Guid Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public string Summary { get; set; }
public string Author { get; set; }
public string SourceName { get; set; }
public string SourceSite { get; set; }
public DateTime PublicationDate { get; set; }
public DateTime LastModified { get; set; }
public DateTime DateCreated { get; set; }
//CUSTOM PROPERTIES
//public string Custom1 { get; set; }
public NewsModel()
{
}
public NewsModel(NewsItem sfNewsItem)
{
this.Id = sfNewsItem.Id;
this.Title = sfNewsItem.Title;
this.Content = sfNewsItem.Content;
this.Summary = sfNewsItem.Summary;
this.Author = sfNewsItem.Author;
this.SourceName = sfNewsItem.SourceName;
this.SourceSite = sfNewsItem.SourceSite;
this.PublicationDate = sfNewsItem.PublicationDate;
this.LastModified = sfNewsItem.LastModified;
this.DateCreated = sfNewsItem.DateCreated;
//CUSTOM PROPERTIES
//this.Custom1 = (string)newsItem.GetValue("Custom1");
}
}
}
Finally, we will have to register the routes in the Global.asax. There is a catch though. You have to register this in the Sitefinity Bootstrapper instead of the the .NET application start. It seems the Sitefinity Bootstrapper gets rid of the routes or conflicts with it if it is defined before then. Here is what I ended up with:
using System;
using System.Web.Http;
using System.Web.Routing;
using Telerik.Sitefinity.Abstractions;
using Telerik.Sitefinity.Data;
namespace SitefinityWebApp
{
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
Bootstrapper.Initialized += new EventHandler<ExecutedEventArgs>(Bootstrapper_Initialized);
}
protected void Bootstrapper_Initialized(object sender, ExecutedEventArgs e)
{
if (e.CommandName == "Bootstrapped")
{
RegisterRoutes(RouteTable.Routes);
}
}
/// <summary>
/// Registers the routes.
/// </summary>
/// <param name="routes">The routes.</param>
public static void RegisterRoutes(RouteCollection routes)
{
routes.Ignore("{resource}.axd/{*pathInfo}");
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
}
Now here is what my REST URL http://localhost/api/news/getall gives me:
[
{
"Author":"Basem Emara",
"Content":"This is the description of my news item 2.<br />",
"DateCreated":"/Date(1340166495610)/",
"Id":"7ea9de79-c2ed-4879-b63c-37e7ec684dd0",
"LastModified":"/Date(1340166495610)/",
"PublicationDate":"/Date(1340166495597)/",
"SourceName":"Falafel",
"Summary":"Summary of my news item 2.",
"Title":"My News Title 2"
},
{
"Author":"Microsoft Team",
"Content":"This is the description of my news item 5.<br />",
"DateCreated":"/Date(1340166640923)/",
"Id":"e126a067-d5d8-4e75-8ae1-3c18b4cb2274",
"LastModified":"/Date(1340166640923)/",
"PublicationDate":"/Date(1340166640893)/",
"SourceName":"Microsoft",
"Summary":"Summary of my news item 5.",
"Title":"My News Title 5"
},
{
"Author":"Telerik Team",
"Content":"This is the description of my news item 4.<br />",
"DateCreated":"/Date(1340166594730)/",
"Id":"62642848-04bc-463b-b34f-821fd69cfa81",
"LastModified":"/Date(1340166594730)/",
"PublicationDate":"/Date(1340166594713)/",
"SourceName":"Telerik",
"Summary":"Summary of my news item 4.",
"Title":"My News Title 4"
},
{
"Author":"Sitefinity Team",
"Content":"This is the description of my news item 3.<br />",
"DateCreated":"/Date(1340166551220)/",
"Id":"109388ad-29ff-4509-80fe-dc3f8690f7b4",
"LastModified":"/Date(1340166551220)/",
"PublicationDate":"/Date(1340166551207)/",
"SourceName":"Sitefinity",
"Summary":"Summary of my news item 3.",
"Title":"My News Title 3"
},
{
"Author":"Basem Emara",
"Content":"This is the description of my news item 1.",
"DateCreated":"/Date(1340166319933)/",
"Id":"1d513a10-a9b4-4598-9683-ed8aa68972f5",
"LastModified":"/Date(1340166319933)/",
"PublicationDate":"/Date(1340166319713)/",
"SourceName":"Falafel",
"Summary":"Summary of my news item 1.",
"Title":"My News Title 1"
}
]
Isn't it a beauty? Now I can get my
Kendo UI and other JavaScript apps to consume my Sitefinity data without the extra complexities and weight :)
Happy coding!