27November 27, 2009, 18:22
Today I had to remove duplicate pages from a PageDataCollection. I checked for a method that would help me with this in the SDK, but couldn’t find one. I then went to the Filters namespace to see if there was a filter for this, but no luck.
I then remembered that System.Linq has a great extension method for collections that implement IEnumerable, called Distinct, that does exactly what I want.
The code is pretty simple.
// using System.Linq;
myPageDataCollection.Distinct();
Simple and elegant, but of course it didn’t work!
Custom comparer
If you take a look at the documentation for the Distinct method you’ll see an overload method that takes IEqualityComparer(T) for comparing.
Below is a custom comparer for PageData, that implements the IEqualityComparer interface. Pretty simple code.
public class PageDataComparer : IEqualityComparer<PageData>
{
public bool Equals(PageData a, PageData b)
{
return a.PageLink.CompareToIgnoreWorkID(b.PageLink);
}
public int GetHashCode(PageData pageData)
{
return pageData.PageLink.GetHashCode();
}
}
The updated call to the Distinct method now looks like this.
myPageDataCollection.Distinct(new PageDataComparer());
Custom filter
We can do the same with a custom filter.
public class FilterRemoveDuplicates : IPageFilter
{
public void Filter(PageDataCollection pages)
{
var pageReferences = new List<PageReference>();
for (int pageIndex = pages.Count - 1; pageIndex >= 0; pageIndex--)
{
PageData pageData = pages[pageIndex];
if (pageReferences.Contains(pageData.PageLink))
{
pages.RemoveAt(pageIndex);
}
else
{
pageReferences.Add(pageData.PageLink);
}
}
}
public void Filter(object sender, FilterEventArgs e)
{
this.Filter(e.Pages);
}
public bool ShouldFilter(PageData page)
{
throw new NotImplementedException();
}
}
new Filters.FilterRemoveDuplicates().Filter(myPageDataCollection);
18October 18, 2009, 15:29
In a recent project I had to work with the SlideShare API. Thankfully there was some good documentation to get me started, and even a .NET wrapper for the first version of the API.
From SlideShare
SlideShare is a business media site for sharing presentations, documents and pdfs.
After downloading and playing around with the code I decided to upgrade it to version 2 of the API. I encourage you to take a look at the documentation if you haven’t, to see what’s new in version 2 of the API, and what’s available.
SlideShare API Wrapper Methods
Here are the methods that I’ve exposed and made available for you to use. Big thanks to Brian Grinstead for his blog post Multipart Form Post in C# and for Gaurav Gupta for creating the wrapper class for the first version, which this code is based on.
- GetSlideshow
- GetSlideshowsForTag
- GetSlideshowsForGroup
- GetSlideshowsForUser
- SlideshowSearch
- GetUserGroups
- GetUserContacts
- GetUserTags
- EditSlideshow
- DeleteSlideshow
- UploadSlideshow
To use it simply download the code (see link at the bottom of this post), copy the SlideShareAPI.dll into your bin folder and make a reference to it in your project. After you’ve done that you’ll be able to create an instance of the SlideShare class which will expose the methods above.
Remember that you also need an API Key and Shared Secret. Go to the Apply for API Keys page and fill out the form. You should then receive an email with both keys.
Example
Lets get some slideshows that are tagged with web, bind them to a Repeater control and display them to the user.
using System;
using System.Linq;
using System.Xml.Linq;
using SlideShareAPI;
namespace FV.Templates.FV.Pages
{
public partial class SlideShareTest : System.Web.UI.Page
{
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
var slideShare = new SlideShare("API Key", "Shared secret");
var xml = XElement.Parse(slideShare.GetSlideshowsForTag("web", 0, 10));
var slideshows = from slideshare in xml.Elements("Slideshow")
select new
{
Title = slideshare.Element("Title").Value,
Description = slideshare.Element("Description").Value,
EmbedCode = slideshare.Element("Embed").Value
};
rptSlideshows.DataSource = slideshows;
rptSlideshows.DataBind();
}
}
}
I use Linq to XML and create anonymous objects that I then databind to my Repeater.
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="SlideShareTest.aspx.cs" Inherits="FV.Templates.FV.Pages.SlideShareTest" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<asp:Repeater runat="server" ID="rptSlideshows">
<HeaderTemplate><ul></HeaderTemplate>
<ItemTemplate>
<li>
<h3><%# HttpUtility.HtmlEncode(Eval("Title").ToString()) %></h3>
<p><%# HttpUtility.HtmlEncode(Eval("Description").ToString()) %></p>
<%# HttpUtility.HtmlDecode(Eval("EmbedCode").ToString())%>
</li>
</ItemTemplate>
<FooterTemplate></ul></FooterTemplate>
</asp:Repeater>
</form>
</body>
</html>
Most of the code is commented and available through Visual Studio Intellisense. You can also go through and change the code to your needs.
Download the code
When starting a new project I always create a Master Page that holds the content that is most used across the site. In my Master Page I have a PlaceHolder for my JavaScript files at the bottom of the page, right before the closing body tag.
<body>
<form id="form1" runat="server">
...
</form>
<asp:PlaceHolder runat="server" ID="phdBottomScripts" />
</body>
public PlaceHolder BottomScriptsPlaceHolder
{
get
{
return phdBottomScripts;
}
}
The reason for having it at the bottom instead of the head is performance.
Now I have the ability to add JavaScript files to the bottom and CSS and JavaScript files to the head (through the pages Header property).
Lets add two methods for adding CSS and JavaScript files.
// using System.Web.UI.HtmlControls;
public static HtmlLink CreateCssLink(string cssFilePath, string media)
{
var link = new HtmlLink();
link.Attributes.Add("type", "text/css");
link.Attributes.Add("rel", "stylesheet");
link.Href = link.ResolveUrl(cssFilePath);
if (string.IsNullOrEmpty(media))
{
media = "all";
}
link.Attributes.Add("media", media);
return link;
}
public static HtmlGenericControl CreateJavaScriptLink(string scriptFilePath)
{
var script = new HtmlGenericControl();
script.TagName = "script";
script.Attributes.Add("type", "text/javascript");
script.Attributes.Add("src", script.ResolveUrl(scriptFilePath));
return script;
}
The media attribute is to specify which media types the CSS file should be applied to.
Media types
- all
- braille
- embossed
- handheld
- print
- projection
- screen
- speech
- tty
- tv
Example
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
Page.Header.Controls.Add(Helper.CreateJavaScriptLink("~/Scripts/swfobject.js"));
Page.Header.Controls.Add(Helper.CreateCssLink("~/Styles/styles.css", "screen"));
}
<head>
...
<script type="text/javascript" src="/Scripts/swfobject.js"></script>
<link type="text/css" rel="stylesheet" href="/Styles/styles.css" media="screen" />
</head>
To add JavaScript files to the bottom we need to add MasterType to our .aspx file to make my Master Page strongly typed (and thus able to access the BottomScriptsPlaceHolder property).
<%@ MasterType VirtualPath="~/MasterPages/MasterPage.master" %>
Master.BottomScriptsPlaceHolder.Controls.Add(Helper.CreateJavaScriptLink("~/Scripts/master.js"));
...
<script type="text/javascript" src="/Scripts/master.js"></script>
</body>
Other methods
Response.Write method
<link href="<%= ResolveUrl("~/Styles/Styles.css") %>" rel="stylesheet" type="text/css" />
ClientScriptManager
From MSDN.
The ClientScriptManager class is used to manage client scripts and add them to Web applications
Methods of the ClientScriptManager class.
ResolveUrl and ResolveClientUrl
Simply put, the difference between these two methods is that ResolveUrl will create a path from the sites root (/Styles/Styles.css), while ResolveClientUrl will create a path that is relative from the page the user is on. (../Styles/Styles.css).
The EPiServer Link Collection Property allows you to add links to web pages, documents and e-mail addresses. For a nice overview see this post: EPiServer 5 R2 and Link Collection property
In this example I’m going to show you how to use the link collection property to get a page reference to EPiServer pages and retrieve some content from them. This could be used for a related content sidebar or something similar.
Start by creating a new link collection property in admin mode to an existing page type or a new one, name it RelatedContent. Then go to edit mode and add a few links to various pages.
Now we’ll start on the page template, open up your web form and add this markup.
<asp:Repeater runat="server" ID="rptRelatedContent" OnItemDataBound="rptRelatedContent_ItemDataBound">
<HeaderTemplate>
<ul id="relatedContent">
</HeaderTemplate>
<ItemTemplate>
<li>
<h3><asp:HyperLink runat="server" ID="lnkHeading" /></h3>
<p><asp:Literal runat="server" ID="ltlText" /></p>
</li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
Then in your code-behind.
protected void Page_Load(object sender, EventArgs e)
{
if (IsValue("RelatedContent"))
{
// using EPiServer.SpecializedProperties;
var relatedContent = (LinkItemCollection)CurrentPage["RelatedContent"];
if (relatedContent != null)
{
rptRelatedContent.DataSource = relatedContent;
rptRelatedContent.DataBind();
}
}
}
protected void rptRelatedContent_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
var linkItem = (LinkItem)e.Item.DataItem;
var lnkHeading = (HyperLink)e.Item.FindControl("lnkHeading");
var ltlText = (Literal)e.Item.FindControl("ltlText");
if (linkItem != null && lnkHeading != null && ltlText != null)
{
var url = new UrlBuilder(linkItem.Href);
// using EPiServer.Web;
bool isEPiServerPage = PermanentLinkMapStore.ToMapped(url);
if (isEPiServerPage)
{
var page = DataFactory.Instance.GetPage(PermanentLinkUtility.GetPageReference(url));
if (page != null)
{
lnkHeading.Text = page["Heading"] as string ?? page.PageName;
lnkHeading.NavigateUrl = page.LinkURL;
ltlText.Text = GetText(page, 255);
}
}
}
}
}
The GetText method I usually place in a static helper class. It is just to get 255 characters of text from the pages MainIntro property or if it is empty from the MainBody property:
public static string GetText(PageData page, int textLength)
{
if (page == null)
{
return string.Empty;
}
var text = page["MainIntro"] as string;
if (string.IsNullOrEmpty(text))
{
text = page["MainBody"] as string;
}
if (string.IsNullOrEmpty(text))
{
return string.Empty;
}
// using EPiServer.Core.Html;
return TextIndexer.StripHtml(text, textLength);
}
You could also add it as an extension method:
// Simply add the this keyword in front of PageData
public static string GetText(this PageData page, int textLength)
{
...
}
Then you can call it like this instead:
ltlText.Text = page.GetText(255);
This is a little code snippet for when you need to cast a nullable type to its value type.
if (startPage.ShowMainFeatured.HasValue)
{
// Safe to cast
if ((bool) startPage.ShowMainFeatured)
{
mainfeatured.Visible = true;
}
}