Our goal is getting content from Kentico Cloud and presenting it on a page. In order to do so the first thing we need is Kentico Cloud account. Once you register/sign in create some content type and a couple of content items.
Next step would be installation of Kentico Deliver SDK: open your solution in Visual Studio, open Nuget Package Manager and search for KenticoCloud.Deliver package and install it.
So now we are in a good shape to start actual development. I’m not going to focus on data source web part development, as you can find this in Kentico Documentation. I’m going to focus on getting data from a cloud and processing it.
Kentico Deliver API is quite simple and obvious: you have to initiate DeliverClient and call GetItemsAsync method of it to get some data. In order to specify what data you want to receive you have to pass a list of IFilter objects. There are a couple of filters like InFilter, OrderFilter, EqualsFilter, etc., those allow us to filter out content we don’t need, order it or split into multiple pages. Here is a sample of Delivery client usage:
private readonly DeliverClient client = new DeliverClient("project_id");
var filters = new List<IFilter> {
new InFilter("system.type", ContentType)),
new Order("elements."+OrderBy, OrderDirection)
};
client.GetItemsAsync(filters);
I think everyone agrees that this looks very simple, however there are some challenges when doing this in a user control. First challenge is that DeliveryClient does async call, which does not work well in Web Forms. We can work around this using System.Threading.Task:
var data = System.Threading.Tasks.Task.Run(() => client.GetItemsAsync(filters)).Result.Items;
The next challenge is to convert received data to some structured collection. We receive a collection of dynamic objects. It is possible to process them using reflection: get a list of properties and their values. But I’ve chosen much easier way – using data table:
var content = BuildDataTable(Columns);
foreach(var a in data)
{
var res = content.NewRow();
foreach (var col in Columns)
{
res[col] = a.GetString(col.ToLower());
}
content.Rows.Add(res);
}
…
private DataTable BuildDataTable(string [] columns)
{
var dt = new DataTable();
dt.Columns.AddRange(columns.Select(x => new DataColumn(x)).ToArray());
return dt;
}
As you might notice we used collection of columns to build a data table. This puts a constraint onto our web part: Columns field is required. I don’t see this to be an issue as specifying Columns for any listing/viewer web part is best practice and it improves performance.
So, let’s put all those pieces together and build a web part.
According to Kentico documentation data source web part consists of two user controls: one implements CMSBaseDataSource and another – CMSAbstractWebPart.
Let’s start from CMSBaseDataSource. In addition to the code from documentation we need to add properties we will use for filtering content coming from a Cloud. I’ve added just a couple of very basic properties:
public string ContentType { get; set; }
public OrderDirection OrderDirection { get; set; }
public string[] Columns { get; set; }
You are more than welcome to add any other property you may need, e.g. Skip, Take, etc. By the way CMSBaseDataSource class already contains properties like OrderBy, WhereCondition, etc.
Also, we need an instance of Delivery client:
private readonly DeliverClient client = new DeliverClient("project_id");
You can find project ID by opening your project in Kentico Cloud and navigating to Development tab in the left pane. Also, it would be better to store project ID in the web.config, Kentico custom setting or even as web part setting.
Last step with this control is implementation of GetDataSource
protected override object GetDataSourceFromDB()
{
// Initializes the data properties according to the filter settings
if (SourceFilterControl != null)
{
SourceFilterControl.InitDataProperties(this);
}
var filters = new List<IFilter> {
new Order("elements
."+OrderBy, OrderDirection)
};
if(!string.IsNullOrEmpty(ContentType))
{
filters.Add(new InFilter("system.type", ContentType));
}
if (Columns == null || Columns.Length <= 0)
{
return null;
}
else
{
filters.Add(new ElementsFilter(Columns));
}
if(TopN > 0)
{
filters.Add(new LimitFilter(TopN));
}
var data = System.Threading.Tasks.Task.Run(() => client.GetItemsAsync(filters)).Result.Items;
//.Select(x => x.Elements);
var content = BuildDataTable(Columns);
foreach(var a in data)
{
var res = content.NewRow();
foreach (var col in Columns)
{
res[col] = a.GetString(col.ToLower());
}
content.Rows.Add(res);
}
return content;
}
private DataTable BuildDataTable(string [] columns)
{
var dt = new DataTable();
dt.Columns.AddRange(columns.Select(x => new DataColumn(x)).ToArray());
return dt;
}
Now we can switch to web part implementation. All we need is adding appropriate properties and passing their values to the child control we’ve just built. So here are properties:
public string OrderBy
{
get
{
return ValidationHelper.GetString(this.GetValue("OrderBy"), "");
}
set
{
this.SetValue("OrderBy", value);
CloudDataSource.OrderBy = value;
}
}
public string Columns
{
get
{
return ValidationHelper.GetString(this.GetValue("Columns"), "");
}
set
{
this.SetValue("TopN", value);
CloudDataSource.Columns = value.Split(',').Select(x => x.Trim()).ToArray();
}
}
public string ContentType
{
get
{
return ValidationHelper.GetString(this.GetValue("ContentType"), "");
}
set
{
this.SetValue("ContentType", value);
CloudDataSource.ContentType = value;
}
}
And we need to pass them to an actual data source in the SetupControl method like this:
protected void SetupControl()
{
if (this.StopProcessing)
{
}
else
{
this.CloudDataSource.OrderBy = this.OrderBy;
this.CloudDataSource.Columns = this.Columns.Split(',').Select(x => x.Trim()).ToArray();
this.CloudDataSource.ContentType = this.ContentType;
this.CloudDataSource.FilterName = (string)this.GetValue("WebPartControlID");
this.CloudDataSource.SourceFilterName = this.FilterName;
}
}
Last step in this process is registering of a web part in Kentico. While adding properties to the web part make sure Columns property is required. Once this is completed you’re ready to start using the data source with viewer web part, get content from the Cloud and presenting it on a page with a transformation. I’ve used Text/XML transformation with a macro for getting fields – just put {%field_name%} into the transformation. You may use any column name specified in Column property of a data source. The intension of this post is showing the simplicity of Deliver SDK and Cloud usage. Current implementation is very basic and requires extra logic for data verification, exception handling, etc. in order to use it on the live site. Also it states that you don’t need to rebuild your application with MVC or any other platform in order to start using Kentico Cloud, or, even if you plan to move there, it will make migration much smoother.
Please feel free to leave your feedback or share your experience with similar problems - we highly appreciate this!
Roman Hutnyk