The content you're reading is getting on in years
This post is on the older side and its content may be out of date.
Be sure to visit our blogs homepage for our latest news, updates and information.
In Sitefinity everyone is using RelatedData and usually we want to display the related data items on the front-end. In this blog post I will describe how you can create a single control that will resolve an item related data fields and show the data items. The item is resolved using Url parameters and the control is intended to work with the out of the box Sitefinity widgets. When the widget resolve an item in a detail view, the related data control will resolve its fields and items related. This way you do not have to modify the items’ widget template to show the related items and do not need to define different controls for each field.
The widget provides an option to show the items using either simple links or items widget. This is configured through the control designer.
Our control inherits from ContentView and contains only a DetailView. This way we stick to the original built-in controls and separate the logic between the main control and the views.
In the post will be also shown how you can get a manager and resolve an item using the type of the content and how to cache already resolved item to prevent unnecessary querying it again.
We will use the ContentView ResolveDetailItem method to resolve the item by overriding it. We first try to resolve a dynamic item using the Url parameters. We will check the item locations, so we are sure this is the item resolved on the current page. This is necessary because there could be items from different types with equal urls.
internal
static
IDataItem TryGetDynamicItemFromUrl(
string
urlParams)
{
var typeStr =
typeof
(DynamicContent).FullName;
Type typeCurrent = TypeResolutionService.ResolveType(typeStr);
var items = DynamicModuleManager.GetManager().GetDataItems(typeCurrent)
.Where(i => i.ItemDefaultUrl == urlParams && i.Status == ContentLifecycleStatus.Live);
var count = items.Count();
if
(count > 1)
{
foreach
(var item
in
items)
{
var service = Telerik.Sitefinity.Services.SystemManager.GetContentLocationService();
var isThisItem = service.GetItemLocations(item).Any(s => s.PageId ==
new
Guid(SiteMap.CurrentNode.Key));
if
(isThisItem)
{
return
item;
}
}
}
else
{
return
items.FirstOrDefault();
}
return
null
;
}
Using the default content items type, we will try to resolve an item using the items type manager.
internal
static
IDataItem TryGetContentItemFromUrl(
string
urlParams)
{
var typeNames = RelatedDataControlUrlHelper.Types;
foreach
(var itemType
in
typeNames)
{
var typeString = itemType;
Type type = TypeResolutionService.ResolveType(typeString);
var manager = ManagerBase.GetMappedManager(typeString);
string
redirectUrl =
string
.Empty;
var currentItem = ((IContentManager)manager).GetItemFromUrl(type, urlParams,
out
redirectUrl);
if
(currentItem !=
null
)
{
return
currentItem;
}
}
return
null
;
}
We will cache the item resolved, using the Url parameters and the page key:
protected
override
void
SubscribeCacheDependency()
{
if
(
this
.DetailItem !=
null
)
{
string
key = ConstructCacheKey();
var inCache =
this
.CacheManager.Contains(key);
var item =
this
.DetailItem;
if
(!inCache)
{
this
.CacheManager.Add(key,
item,
CacheItemPriority.Normal,
null
,
new
DataItemCacheDependency(item.GetType(), item.Id),
new
SlidingTime(TimeSpan.FromMinutes(30)));
}
}
}
protected
virtual
string
ConstructCacheKey()
{
var pageId = SiteMap.CurrentNode.Key;
if
(!
string
.IsNullOrEmpty(
this
.urlParameters) && !
string
.IsNullOrEmpty(pageId))
{
var key =
string
.Format(
"{0}_{1}"
,
this
.urlParameters, pageId);
return
key;
}
return
null
;
}
Getting the related data fields is also split on dynamic type items and content items. In the first case we will use the ModuleBuilderManager and in the second we will get the fields which are RelatedDataPropertyDescriptor type.
protected
virtual
void
ProcessDynamicItem(DynamicContent item)
{
ModuleBuilderManager moduleBuilderManager = ModuleBuilderManager.GetManager();
var type = moduleBuilderManager.GetItems(
typeof
(DynamicModuleType), String.Empty, String.Empty, 0, 0)
.OfType<DynamicModuleType>()
.FirstOrDefault(dmt => item.GetType().FullName.Equals(dmt.GetFullTypeName()));
var mainShortTextField = type.MainShortTextFieldName;
var fields = moduleBuilderManager.Provider.GetDynamicModuleFields()
.Where(f => f.ParentTypeId == type.Id && (f.FieldType == FieldType.RelatedData || f.FieldType == FieldType.RelatedMedia));
var itemType = item.GetType();
GetRelatedData(item, itemType, fields);
}
var item = currentItem
as
IDynamicFieldsContainer;
if
(item !=
null
)
{
var fields = TypeDescriptor.GetProperties(type).OfType<RelatedDataPropertyDescriptor>();
var models =
new
List<Model>();
foreach
(var field
in
fields)
{
var current =
new
Model();
current.Item = item;
current.FieldName = field.Name;
var attributesCollection = field.Attributes[
typeof
(MetaFieldAttributeAttribute)]
as
MetaFieldAttributeAttribute;
if
(attributesCollection !=
null
)
{
string
childItemTypeName =
null
;
string
childItemProviderName =
null
;
attributesCollection.Attributes.TryGetValue(
"RelatedType"
,
out
childItemTypeName);
attributesCollection.Attributes.TryGetValue(
"RelatedProviders"
,
out
childItemProviderName);
current.FieldType = childItemTypeName;
current.RelatedDataProvider = childItemProviderName;
}
models.Add(current);
}
}
We query the related items data and filter using the parent item status, so we get only single version of an item. It is needed to query a cached item once again using its id, since the cached one is out of the object scope and it is not able to get the related items.
// Get the related items - when using simple links.
// Return IEnumerable object - common DataSource
public
IEnumerable<
object
> GetRelatedItems(
object
item)
{
var it = item
as
Model;
if
(
this
.isItemFromCache)
{
// Query the item, since the cached one is not in the object scope
if
(cachedItem ==
null
)
{
var type = it.Item.GetType();
var manager = ManagerBase.GetMappedManager(type);
var attachedItem = manager.GetItem(type, it.Item.GetValue<Guid>(
"Id"
));
this
.cachedItem = attachedItem
as
IDataItem;
}
// filter the related items by the parent item status (or both master/live will be queried)
ContentLifecycleStatus status = (
this
.cachedItem
as
ILifecycleDataItemGeneric).Status;
var result =
this
.cachedItem.GetRelatedItems(it.FieldName).ToList().OfType<ILifecycleDataItemGeneric>()
.Where(i => i.Status == status);
return
result;
}
else
{
ContentLifecycleStatus status = it.Item.GetValue<ContentLifecycleStatus>(
"Status"
);
var result = it.Item.GetRelatedItems(it.FieldName).ToList().OfType<ILifecycleDataItemGeneric>()
.Where(i => i.Status == status);
return
result;
}
}
Using the RelatedDataDefinition in the default widgets, we will show the related items:
public
static
class
RelatedDataControlExtensions
{
internal
static
void
SetRelatedDataDefinitionProperties(
this
IRelatedDataView control, Model dataItem)
{
control.RelatedDataDefinition.RelatedFieldName = dataItem.FieldName;
control.RelatedDataDefinition.RelatedItemType = dataItem.Item.GetType().FullName;
control.RelatedDataDefinition.RelationTypeToDisplay = RelationDirection.Child;
control.RelatedDataDefinition.RelatedItemSource = Telerik.Sitefinity.RelatedData.Web.UI.RelatedItemSource.Url;
}
}
Here is a video demonstration of the widget:
You can get the full source code and fork it from GitHub here.
Nikola Zagorchev is a Tech Support Engineer at Telerik. He joined the Sitefinity Support team in March 2014.
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
Progress collects the Personal Information set out in our Privacy Policy and the Supplemental Privacy notice for residents of California and other US States and uses it for the purposes stated in that policy.
You can also ask us not to share your Personal Information to third parties here: Do Not Sell or Share My Info
We see that you have already chosen to receive marketing materials from us. If you wish to change this at any time you may do so by clicking here.
Thank you for your continued interest in Progress. Based on either your previous activity on our websites or our ongoing relationship, we will keep you updated on our products, solutions, services, company news and events. If you decide that you want to be removed from our mailing lists at any time, you can change your contact preferences by clicking here.