+1-888-365-2779
Try Now
More in this section
Categories
Bloggers
Blogs RSS feed

Resolve any item Related Data with a single widget

by Nikola Zagorchev

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.

Getting the item from Url

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;
       }

Get the Related data fields

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);
                }
       }

Get the related items - when using simple links

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 default widgets to show related items

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:

Unable to display content. Adobe Flash is required.  You can get the full source code and fork it from GitHub here.

Leave a comment