~/blogs/IvanDDimitrov/posts.aspx
Categories
Bloggers
Blogs RSS feed

Adding Image to a Blog RSS Feed in Sitefinity

by Ivan D.Dimitrov

RSS feeds are a convenient way to inform users instantly when a new blog post is available for them to review and comment. This post will show you how to add an image in the feed’s list.

The first thing you will need to do is add a custom field to your blog posts. This field needs to be an image selector that persists the image URL as its value. I have used the resource available in Slavo Ingilizov’s blog. This selector persists the ID of the image in the form of a Guid, so in order for the image to be properly passed, we need to alter the Javascript so it persists the URL. The changes needed are in the SimpleImageSelector.js file, where you need to replace:

var imageUrl = args.get_dataItem().ThumbnailUrl;

with

var imageUrl = args.get_dataItem().MediaUrl;


And in the
SimpleImageSelectorDialog.js where you need to locate the _doneLinkClicked function and replace it with the one below:

_doneLinkClicked: function (sender, args) {      
        var selectedValue = this.get_imageSelector().get_selectedImageUrl();
        if (!selectedValue || selectedValue === "") {
            alert("No image selected.");
        }
        else {
            dialogBase.close(selectedValue);
        }
    }

 

After that is done you can apply the custom field in the blog posts as per Slavo’s instructions.

After this is done we need to stream the image in the RSS. Sitefinity uses RSS 2.0. This means that all of the elements that are visible to the end user have to be part of the <channel> tag. Naturally streaming the custom field image with the default <image> xml tag will not work here as it is not a sub tag of the <channel> one. In order to be able to properly pass an image we need to do so with html so as to get freedom for sizing and styling. The only xml tag in the <channel> one that is able to display html is the <description> tag and it needs some modification in order to properly do so.

The first thing we need to do is to create a custom RSS outbound pipe. This way we can extend the default properties of Sitefinity’s default pipe. You need to create a class that inherits from our inbuilt RssOutboundPipe class. The code below presents a pipe with the name customRssOutboundPipe:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.ServiceModel.Syndication;
using System.Xml;
using System.Xml.Linq;
using Telerik.Sitefinity.Data.Summary;
using Telerik.Sitefinity.Modules.GenericContent;
using Telerik.Sitefinity.Modules.Libraries;
using Telerik.Sitefinity.Publishing;
using Telerik.Sitefinity.Publishing.Model;
using Telerik.Sitefinity.Publishing.Pipes;
using Telerik.Sitefinity.Publishing.Translators;
using Telerik.Sitefinity.Web.Utilities;
 
namespace SitefinityWebApp.RssOutboundPipe
{
    // specifies the pipe designer in the backend
    [PipeDesigner(null, typeof(CustomRssAtomPipeDesignerView))]
    public class CustomRssOutboundPipe : RSSOutboundPipe
    {
        public override string Name
        {
            get
            {
                return CustomRssOutboundPipe.PipeName;
            }
        }
 
 
 
        /// <summary>
        /// Creates a SyndicationItem object from the given data.
        /// </summary>
        /// <param name="values">The data used to build a SyndicationItem object.</param>
        /// <returns>A SyndicationItem object.</returns>
        protected override SyndicationItem BuildSyndicationItem(WrapperObject values)
        {
                 
            SyndicationItem item = new SyndicationItem();
            
 
            
         
 
            var properties = TypeDescriptor.GetProperties(values);
 
            var title = this.GetPropertyValue(values, properties, PublishingConstants.FieldTitle);
            title = PublishingHelper.SanitizeStringForXml(title);
            item.Title = new TextSyndicationContent(title);
            if (this.RssPipeSettings.OutputSettings != RssContentOutputSetting.TitleOnly)
            {
                var content = this.GetPropertyValue(values, properties, PublishingConstants.FieldContent);
                //Resolve to real urls links and media(images, video, docs) pointing to internal sitefinity content by ID
                content = LinkParser.ResolveLinks(content, DynamicLinksParser.GetContentUrl, null, false, true);
 
                if (this.RssPipeSettings.OutputSettings == RssContentOutputSetting.TitleAndTruncatedContent)
                {
                    content = SummaryParser.GetSummary(content, new SummarySettings(SummaryMode.None, -1, true));
                    if (content.Length > this.RssPipeSettings.ContentSize)
                    {
                        if (this.RssPipeSettings.ContentSize == 0)
                            content = String.Empty;
                        else
                        {
                            content = content.Substring(0, this.RssPipeSettings.ContentSize);
                            content += "...";
                        }
                    }
 
                }
                content = PublishingHelper.SanitizeStringForXml(content);
 
               
 
            }
            //BACK LINK to the web page with the original posting
            if (properties.Find(PublishingConstants.FieldLink, true) != null)
            {
                var itemUrl = GetItemUrl(values);
                if (!string.IsNullOrEmpty(itemUrl))
                    item.Links.Add(SyndicationLink.CreateAlternateLink(new Uri(itemUrl)));
            }
 
            
             //AUTHOR
             var ownerFirstNameProp = properties.Find(PublishingConstants.FieldOwnerFirstName, true);
             var ownerLastNameProp = properties.Find(PublishingConstants.FieldOwnerLastName, true);
             var ownerEmailProp = properties.Find(PublishingConstants.FieldOwnerEmail, true);
             if (ownerFirstNameProp != null && ownerLastNameProp != null && ownerEmailProp != null)
             {
                 item.Authors.Add(new SyndicationPerson(
                     (string)ownerEmailProp.GetValue(values),
                     (string)ownerFirstNameProp.GetValue(values) + " " +
                     (string)ownerLastNameProp.GetValue(values),
                     null
                 ));
             }
 
             
 
            //PUBLICATION DATE
            var pubDateProp = properties.Find(PublishingConstants.FieldPublicationDate, true);
            if (pubDateProp != null)
            {
                var pubDateVal = pubDateProp.GetValue(values);
                if (pubDateVal != null)
                    item.PublishDate = new DateTimeOffset(((DateTime)pubDateVal).ToUniversalTime());
            }
             
 
            //ORIGINAL ITEM ID
            var itemIdProp = properties.Find(PublishingConstants.FieldItemId, true);
            if (itemIdProp != null)
            {
                var itemIdVal = itemIdProp.GetValue(values);
                if (itemIdVal != null)
                {
                    item.Id = String.Format("urn:uuid:{0}", itemIdVal.ToString());
                }
            }
 
            //CATEGORIES
            var catProp = properties.Find(PublishingConstants.FieldCategories, true);
            if (catProp != null)
            {
                var catVal = catProp.GetValue(values);
                if (catVal != null)
                {
                    var categoreis = ((string)catVal).Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
                    foreach (var cat in categoreis)
                        item.Categories.Add(new SyndicationCategory(cat));
                }
            }
 
            //SUMMARY
            var summaryProp = properties.Find(PublishingConstants.FieldSummary, true);
            if (summaryProp != null)
            {
                var summaryVal = summaryProp.GetValue(values);
                if (summaryVal != null && !summaryVal.ToString().IsNullOrEmpty())
                {
                    var summary = summaryVal.ToString();
                    summary = PublishingHelper.SanitizeStringForXml(summary);
                    item.Summary = new TextSyndicationContent(summary);
                }
            }
 
            return item;
        }
 
      
    
 
        // registers the pipe
        public static void AddCustomRssOutboundPipe()
        {
            // registers the pipe type
            PublishingSystemFactory.RegisterPipe(CustomRssOutboundPipe.PipeName, typeof(CustomRssOutboundPipe));
            // registers the pipe settings of the pipe
            var pipeSettings = RSSOutboundPipe.GetTemplatePipeSettings();
            //PublishingSystemFactory.CreateDefaultRssOutboundPipeSettings(CustomRssOutboundPipe.PipeName);
            pipeSettings.UIName = "Custom RSS feed";
            pipeSettings.PipeName = CustomRssOutboundPipe.PipeName;
            PublishingSystemFactory.RegisterTemplatePipeSettings(CustomRssOutboundPipe.PipeName, pipeSettings);
            PublishingSystemFactory.RegisterPipeSettings(CustomRssOutboundPipe.PipeName, pipeSettings);
            // registers the mappings for the pipe
            var mappingsList = RSSOutboundPipe.GetDefaultMappings();
            //PublishingSystemFactory.GetDefaultOutboundMappingForRss();
            CustomRssOutboundPipe.AddFieldNames(mappingsList);
            PublishingSystemFactory.RegisterPipeMappings(CustomRssOutboundPipe.PipeName, false, mappingsList);
            // registers the definitions for the pipe
            var definitions = PublishingSystemFactory.CreateDefaultRSSPipeDefinitions();
            PublishingSystemFactory.RegisterPipeDefinitions(CustomRssOutboundPipe.PipeName, definitions);
        }
        private static void AddFieldNames(List<Mapping> mappingsList)
        {
            foreach (var fieldName in CustomRssOutboundPipe.FieldNames)
            {
                mappingsList.Add(PublishingSystemFactory.CreateMapping(fieldName, TransparentTranslator.TranslatorName, true, fieldName));
            }
        }
 
 
 
        // list all custom fields of your custom type
        private static readonly string[] FieldNames = new[] { "Image" };
 
        public const string PipeName = "CustomRssOutboundPipe";
    }
}

 

We also need a designer for the pipe so it can be visible in the dialog placed under Administration->Alternative publishing:

namespace SitefinityWebApp.RssOutboundPipe
 
{
 
    public class CustomRssAtomPipeDesignerView : RssAtomPipeDesignerView
 
    {
 
        public override IEnumerable<System.Web.UI.ScriptDescriptor> GetScriptDescriptors()
 
        {
 
            // gets the base script descriptor
 
            var scriptDescriptor = (ScriptControlDescriptor)base.GetScriptDescriptors().Last();
 
            // replaces the component type in order not to inherit the js component
 
            scriptDescriptor.Type = typeof(RssAtomPipeDesignerView).FullName;
 
  
 
            // replaces the default pipe settings with the ones of the custom rss outbound pipe
 
            var settings = new Dictionary<string, object>();
 
            var defaults = PublishingSystemFactory.GetPipe(CustomRssOutboundPipe.PipeName).GetDefaultSettings();
 
            settings.Add("settings", defaults);
 
            settings.Add("pipe", new WcfPipeSettings(CustomRssOutboundPipe.PipeName, PublishingManager.GetProviderNameFromQueryString()));
 
  
 
            scriptDescriptor.AddProperty("_settingsData", settings);
 
            return new[] { scriptDescriptor };
 
        }
 
    }
 
}

The pipe needs to be registered in your Global asax as well:

protected void Application_Start(object sender, EventArgs e)
        {
 
            Telerik.Sitefinity.Abstractions.Bootstrapper.Initialized += new EventHandler<Telerik.Sitefinity.Data.ExecutedEventArgs>(Bootstrapper_Initialized);
        }
 
        void Bootstrapper_Initialized(object sender, ExecutedEventArgs e)
        {
            if (e.CommandName == "Bootstrapped")
            {
 
                CustomRssOutboundPipe.AddCustomRssOutboundPipe();
 
            }

 

After you have created and registered your custom pipe, it is time to extend it so as it can pass the image from the custom field. As you can see, Sitefinity’s RssOutboundPipe uses the Syndication Item (http://msdn.microsoft.com/en-us/library/system.servicemodel.syndication.syndicationitem(v=vs.110).aspx) with all of its properties in order to build the RSS. As you can see this class does not build the <description> tag so we need to add it. We use the SyndicationItem.Element extentions to do so. We do a query to match the name of the syndication item with the name of the post and then pass the value of the custom blog posts field to the feed. In order to display the image properly we need to embed it in html. This is done with a CDATA section that forces the xml to ignore the html contained in it:

BlogsManager blogMan = BlogsManager.GetManager();
                var blog = blogMan.GetBlogPosts().Where(b => b.Status == Telerik.Sitefinity.GenericContent.Model.ContentLifecycleStatus.Live).ToList();
                foreach (var oneblog in blog)
                {
                    if (item.Title.Text == oneblog.Title)
                    {
                        var Field = oneblog.GetValue("RSSImg");
 
                        item.ElementExtensions.Add(
                        new XElement("description",
                        new XCData(@"<img src=""" + Field + @""""  +" />" + "</div>" + content + "</div>")));
                    }
                }

 

As you can see I have named my custom field RSSImage. You can name yours as you like, but remember to pass it correctly in the GetValue extension method.

Steps to set the sample:

  1. Download the sample.
  2. Set it according to this video.

2 comments

Leave a comment
  1. Gary Apr 18, 2014
    I see how the custom blog property is populated into the description element, but I don't see how this relates to the adding of the "Image" field to the outbound pipe via the call to the AddFieldNames method. I was expecting to see some code in BuildSyndicationItem that sets the "Image" field name from the custom blog property. Am I not understanding something? I'm trying to build on this example to retrieve the "StartDate" from events, and use it to filter the items in BuildSyndicationItems, but I'm not seeing the connection.
  2. Saad Aug 02, 2014

    I'm using SF 7.1.

    When implementing custom RSS inbound pipe, how can we set custom field values? Like I have Category, Tags, Yes / No, and Image field. 

    Leave a comment