[This post is part in of the "getting started" series, you can find the others here:
Getting started with the Sitefinity’s MVC capabilities]
Before we start explaining how to work with the Sitefinity APIs and Providers, we need to understand the architecture of the product and how each layer interacts with the others.
We can all agree that everything could be categorized as an API, but there are two layers that should be distinguished. The first is the layer that contains the business logic and the second is the layer operating with the data and making sure that your business logic stays the same, no matter what kind of DB storage you have, by using a common Provider interface.
We will review how to use two different types of coding styles for the business logic on the server –Native and Fluent – and we will also see how to take advantage of the REST API at the client side. Last, we will implement a custom provider and explain why we need that flexibility.
Working with .NET API
There are two content types in Sitefinity: the built-in and the dynamically created. For the built-in types like news, taxonomies etc., there are specific managers that you must use to operate with them (see the diagram above). Sitefinity offers two ways to code your business logic using the managers – Native and Fluent API. Here is a very simple widget showing how to use them and get filtered news that contains the word “CMS”.
using
SitefinityWebApp.Mvc.Models;
using
System.Linq;
using
System.Web.Mvc;
using
Telerik.Sitefinity;
using
Telerik.Sitefinity.GenericContent.Model;
using
Telerik.Sitefinity.Modules.News;
using
Telerik.Sitefinity.Mvc;
namespace
SitefinityWebApp.Mvc.Controllers
{
[ControllerToolboxItem(Name =
"SimpleListNews"
, Title =
"Simple List News"
, SectionName =
"Custom MVC Widgets"
)]
public
class
SimpleListNewsController : Controller
{
// GET: SimpleListNews
public
ActionResult Index()
{
// Native
NewsManager newsManager = NewsManager.GetManager();
var nativeNews = newsManager.GetNewsItems().Where(newsItem => newsItem.Title.Contains(
"CMS"
) && newsItem.Status == ContentLifecycleStatus.Live).ToList();
// Fluent
var fluentNews = App.WorkWith().NewsItems()
.Where(newsItem => newsItem.Title.Contains(
"CMS"
) && newsItem.Status == ContentLifecycleStatus.Live).Get().ToList();
var model =
new
SimpleListNewsModel();
model.AllNews = nativeNews;
return
View(model);
}
}
}
using
System.Collections.Generic;
using
Telerik.Sitefinity.News.Model;
namespace
SitefinityWebApp.Mvc.Models
{
public
class
SimpleListNewsModel
{
public
List<NewsItem> AllNews {
get
;
set
; }
}
}
@model SitefinityWebApp.Mvc.Models.SimpleListNewsModel
<
div
>
@if (Model.AllNews.Count > 0) {
<
ul
>
@foreach (var news in Model.AllNews)
{
<
li
>@news.Title</
li
>
}
</
ul
>
} else {
<
p
>There aren't any news.</
p
>
}
</
div
>
With the dynamic content types the approach is similar. You have a dynamic manager and you need to pass the specific content type to work with the items. Here is a code snippet showing how to get items from a dynamically created module called “Releases” with one field – “Title”. Look for the differences compared to the previous example ;).
using
SitefinityWebApp.Mvc.Models;
using
System;
using
System.Web.Mvc;
using
Telerik.Sitefinity.DynamicModules;
using
Telerik.Sitefinity.Mvc;
using
Telerik.Sitefinity.Utilities.TypeConverters;
namespace
SitefinityWebApp.Mvc.Controllers
{
[ControllerToolboxItem(Name =
"SimpleReleaseList"
, Title =
"Simple Release List"
, SectionName =
"Custom MVC Widgets"
)]
public
class
SimpleReleaseListController : Controller
{
// GET: SimpleReleaseList
public
ActionResult Index()
{
//There is only the Native approach
var providerName = String.Empty;
var transactionName =
"someTransactionName"
;
DynamicModuleManager dynamicModuleManager = DynamicModuleManager.GetManager(providerName, transactionName);
Type releaseType = TypeResolutionService.ResolveType(
"Telerik.Sitefinity.DynamicTypes.Model.Releases.Release"
);
var myCollection = dynamicModuleManager.GetDataItems(releaseType);
var model =
new
SimpleReleaseListModel();
model.AllReleases = myCollection;
return
View(model);
}
}
}
using
System.Linq;
using
Telerik.Sitefinity.DynamicModules.Model;
namespace
SitefinityWebApp.Mvc.Models
{
public
class
SimpleReleaseListModel
{
public
IQueryable<DynamicContent> AllReleases {
get
;
set
; }
}
}
@using Telerik.Sitefinity.Model;
@model SitefinityWebApp.Mvc.Models.SimpleReleaseListModel
<
div
>
@if (Model.AllReleases.Count() > 0) {
<
ul
>
@foreach (var news in Model.AllReleases)
{
<
li
>@news.GetValue("Title")</
li
>
}
</
ul
>
} else {
<
p
>There aren't any news.</
p
>
}
</
div
>
The code snippets that show how to create and get items from dynamic content type are automatically generated by Sitefinity, and you can find them in Administration->Module Builder->[name of the module]->Code Reference. The difference here is that when we have a dynamic content type it is not possible to use Fluent API.
Even though Fluent API is limited when it comes to dynamic content types and testing, it provides better code readability. To learn more about how to use facades and chain them, review this documentation section.
Sitefinity automatically creates OData services that help you control the content. You can read more about how to create and set them up in this blog post. It is very important to learn to use these easily. The development team implemented a JavaScript SDK that helps you make calls from the same site or third party application. Also, it has integration with Kendo UI, component suite which comes free with Sitefinity CMS. There are few things that you need to be aware to kick start with the OData services.
First, if you want to test the web services bare bone and look at the requests, play with this collection in Postman. It is also a good helper for people that are not experienced with OData. It shows basic examples, from getting a collection of items to complex post operations requiring authentication.
Next, you must know how to setup the authentication in Sitefinity using the SDK or calling the web services directly, otherwise you will not be able to apply CRUD operations over content that needs authorization. In this help article you can read step by step instructions on how to setup the authentication and retrieve an access token.
The installation of the SDK comes with some samples in the documentation, but I want to emphasize how to set the retrieved access token and how to build queries.
var
token =
''
;
function
getToken() {
var
username =
'email@mail.com'
;
var
password =
'password'
;
var
clientId =
'testApp'
;
var
clientSecret =
'secret'
;
var
tokenEndPoint =
"http://yoursitefinitysite/Sitefinity/Authenticate/OpenID/connect/token"
;
//Call that gets the access and refresh token
$.ajax({
url:tokenEndPoint,
data:{
username:username,
password:password,
grant_type:
'password'
,
scope:
'openid offline_access'
,
client_id:clientId,
client_secret:clientSecret
},
method:
'POST'
,
success:
function
(data){
var
token = data.access_token);
},
error:
function
(err){
alert(err.responseText);
}
})
}
function
getReleases() {
var
serviceUrl = http:
//localhost:12345/api/myservice";
var
releaseDataType =
"releases"
;
var
sf =
new
Sitefinity({
serviceUrl: serviceUrl
});
sf.authentication.setToken(
"Bearer "
+ token);
var
data = sf.data({
urlName: releaseDataType
});
var
queryObj =
new
Sitefinity.Query();
var
query = queryObj.where().eq(
"Title"
,
"Some title"
).done();
data.get({
query:query,
successCb:
function
(result) {
if
(result.value.length > 0) {
//alert("there are releases")
}
else
{
//alert("no releases found")
}
},
failureCb:
function
(err) {
alert(
"Try again"
);
}
});
}
function
createRelease() {
var
serviceUrl = http:
//localhost:12345/api/myservice";
var
releaseDataType =
"releases"
;
var
sf =
new
Sitefinity({
serviceUrl: serviceUrl
});
sf.authentication.setToken(
"Bearer "
+ token);
var
data = sf.data({
urlName: releaseDataType
});
var
newRecord = {
'UrlName'
:
'someurl'
,
'Title'
:
'version 2'
};
data.create({
data: newRecord,
successCb:
function
(result) {
//alert("Successfully created");
},
failureCb:
function
(err) {
alert(
"Try again"
);
}
});
}
function
updateRelease(releaseId) {
var
serviceUrl = http:
//localhost:12345/api/myservice";
var
releaseDataType =
"releases"
;
var
sf =
new
Sitefinity({
serviceUrl: serviceUrl
});
sf.authentication.setToken(
"Bearer "
+ token);
var
data = sf.data({
urlName: releaseDataType
});
var
updateRecord = {
'Title'
:
'version 2.0'
};
data.update({
key: releaseId,
data: updateRecord,
successCb:
function
(result) {
//alert("Successfully updated");
},
failureCb:
function
(err) {
alert(
"Try again"
);
}
});
}
When you want to filter the data by permission level, e.g. the users to have access only to their own data, the FilterQueriesByViewPermissions property should be enabled in the administration panel. Then set permissions on the data type to owner.
If you look at the architectural diagram at the beginning, you will notice that the providers layer is between the managers and the DB. The idea is that the managers shouldn’t care about the underlying data source, whether it is an SQL database, XML or a text file. The role of the manager is to call the provider, ask for a specific action, pass the data (when it is necessary) and that’s all.
The whole CMS is based on the Provider Model Design pattern, e.g. all content modules, membership, taxonomies etc. Using providers allows Sitefinity to have great flexibility and plug into different data sources, or to have better control over data processing. Usually one provider is attached to one data source, but it is also common to have multiple providers for only one data source.
In the sample below we will review how to implement and use a custom provider for a dynamic content type. The example covers the case when we want to have unique URL without providing a unique title (the URL is based on the ‘Title’ field). For this case we will use the Releases type that we created at the beginning.
The first step is to inherit the OpenAccessDynamicModuleProvider and override the GetUrlFormat method.
using
System;
using
Telerik.Sitefinity.DynamicModules.Data;
namespace
SitefinityWebApp.App_Code
{
public
class
UniqueUrlProvider : OpenAccessDynamicModuleProvider
{
public
override
string
GetUrlFormat(Telerik.Sitefinity.GenericContent.Model.ILocatable item)
{
var guid = Guid.NewGuid();
var result =
"/[UrlName]/"
+ guid.ToString() +
""
;
return
result;
}
}
}
The second step is to register the provider in Administration->Settings->Advanced->DynamicModules->Providers:
Restart your application, and if you are in a multisite scenario go to Manage Sites->Actions(on the web site)->Configure Modules and choose the dynamic type to use the new provider:
If you go and add items with the same title, there won’t be any warnings for the URL because the provider appends a random GUID and makes it unique even if the title value is duplicated.
To retrieve items from a specific provider you must pass its name to the manager. In our case I will update the release list widget:
[ControllerToolboxItem(Name =
"SimpleReleaseList"
, Title =
"Simple Release List"
, SectionName =
"Custom MVC Widgets"
)]
public
class
SimpleReleaseListController : Controller
{
// GET: SimpleReleaseList
public
ActionResult Index()
{
//There is only a Native approach
//var providerName = String.Empty;
var providerName =
"UniqueUrlProvider"
;
var transactionName =
"someTransactionName"
;
DynamicModuleManager dynamicModuleManager = DynamicModuleManager.GetManager(providerName, transactionName);
Type releaseType = TypeResolutionService.ResolveType(
"Telerik.Sitefinity.DynamicTypes.Model.Releases.Release"
);
var myCollection = dynamicModuleManager.GetDataItems(releaseType).Where(
"Status=\"Live\""
);
var model =
new
SimpleReleaseListModel();
model.AllReleases = myCollection;
return
View(model);
}
}
Here are the results with the default provider and with the unique URL provider:
The same approach is used when you make calls through the web services. The provider should be specified as a parameter.
When it comes to the JS SDK the provider should be specified in the options pass to the data type e.g.:
var
data = sf.data({
urlName:
"releases"
,
providerName:
" UniqueUrlProvider"
});
We covered building and extending the business logic of a Sitefinity website. Think about the APIs and the Providers as the glue of your app. This should be one of the easiest parts of your development process. The CMS architecture provides flexibility, so you can override the current implementation with a few lines of code and fit your case without efforts.
If you need more information how to kick start with a specific topic, please let us know.
Peter Filipov (Pepi) is a Product Builder focused on building the future of Sitefinity, relying on the newest technologies such as .NET 6 (and up), React and Angular. His previous experience as a Developer Advocate and Manager of Engineering helps him to understand the customers’ and market needs of Sitefinity. He also is passionate about being active and healthy.
Let our experts teach you how to use Sitefinity's best-in-class features to deliver compelling digital experiences.
Learn MoreSubscribe to get all the news, info and tutorials you need to build better business apps and sites