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;
SimpleImageSelectorDialog.js where you need to locate the _doneLinkClicked function and replace it with the one below:
And in the
_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 categories = ((
string
)catVal).Split(
new
string
[] {
","
}, StringSplitOptions.RemoveEmptyEntries);
foreach
(var cat
in
categories)
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 extensions 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:
- Download the sample.
- Set it according to this video.