Frederik Vig – ASP.NET developer

Follow me

Archive for the ‘C#’ Category.

EPiServer code walkthrough #1 – 404 handler

This is the first post in a new series called “EPiServer code walkthrough”. What I’ll do is go through one new EPiServer module in each post. Writing a little about what it does, learn by reading its code, and hopefully contributing a little back to the project, if I see any bugs or harmful/unnecessary code that is :) .

I’m a firm believer that reading code helps you become a better developer. There are lots of modules even I just recently came across, that I didn’t know existed just a few weeks back. I think I’m not the only one, especially since they are spread around on various blogs, CodePlex, EPiCode and other places. They all contain valuable code that we as EPiServer developers should read.

I’m starting with a module I personally have not used much before, but that I know a lot of people have used in their projects: the custom 404 handler.

From the project wiki page

This module has a more advanced url redirect feature than the built-in shortcut url feature in EPiServer. It handles extensions and querystring parameters too. If you have a lot of 404 errors in your logs, you can use this to redirect the user to the correct page. Especially useful if you move templates or pages around, or have just installed EPiServer and have a lot of old urls that is no longer available.

404 handlers wiki page explains in detail what you need to do to install the module, so I’m not going to repeat it here. Instead we’ll take a look at the code.

Start by doing a SVN checkout of the source code (the svn url is https://www.coderesort.com/svn/epicode/BVNetwork.404Handler/5.x/).

SVN checkout of 404 handler

When opening up the BVNetwork.404Handler folder you’ll see a readme.txt file explaining the purpose of the module, installation, configuration etc. This is something I like! Not everyone reads the wiki page, and we as developers are especially lazy when it comes to reading documentation. Having a readme.txt with everything you need makes this so much easier, and gives the module author less support questions to answer. When using a new module, always make sure to read the documentation before asking the author questions. Saves both parties time :) .

Lets open up BVNetwork.404Handler.csproj in Visual Studio.

404 handler project structure in Visual Studio

As you can see the project is structured pretty nice, with good separations between the different parts. Very good that the project uses languages files, and that they’ve included them for five different languages. Not all modules use the language files, and if they do, they seldom have any more than one update-to-date English language file. Kudos for this.

What I don’t like is the missing fnf_logo.gif image file in the Images folder. A quick search in Visual Studio shows that this file is no longer in use, and should therefore be removed from the project. A missing image file is not the end of the world, and I know people make mistakes/forget to commit all their files, but this can be very annoying and even lead to a lot of work for other developers using your code or taking over a project.

Always make sure that your project builds on other computers than your own. I recommend manually setting up the project on preferably a new computer to see that everything works as expected, you can also use the same computer, but make sure to do the normal process a new developer would use when setting up the project (new checkout etc). Especially on larger projects that have quite a few things to setup you should do this. You’ll also receive some valuable feedback if your setup process is to complex (again on larger projects this happens quite frequently). Another thing you should use is a build server. There are quite a few out there, I know CodeResort just added Bitten as their build server for CodeResort projects. In a nutshell what a build server does is make sure you’ve included all the files and resources needed to build your project, run various tests (if you have any), deploy your files, create reports, notify team members if their build fails, and a bunch of other stuff, automatically for you. I’ll try to write a blog post up with more information on build and continuous integration servers.

Lets get back to the Visual Studio and the project! One of the things you should start out with is actually making sure the project builds after doing a checkout. So, either press ctrl + shift + b or go to Build -> Build BVNetwork.404Handler. You should have a successful build.

If we take a look through the code we see that most of the code is well documented and follows a nice naming convention making it easy to know what the purpose of the class is (CustomRedirectHandler.cs, CustomRedirectCollection.cs, Custom404Handler.cs etc). But we also see a few minor things we can clean up: unused using statements, commented out code, unused field variables etc. These are minor, but still important things to get rid of. Always strive for a clean code base, commented out code is something you should never commit into a repository, the whole reason for having a source control system is so that you can look at previous changesets and their code, there is no reason to keep the commented out code there, it will only be forgotten by the developer who commented out the code, and the other developers don’t know what to do with it, and will most likely just let it be. When I come across this I show no mercy and just delete it!

You’ll also see a few System.Diagnostics.Debug.WriteLine that are commented out. The project now uses Log4Net to log errors and warnings, System.Diagnostics.Debug.WriteLine is legacy code that we can safely remove. Another thing that I prefer is using string.Empty(); instead of “”, I’ve not updated the code because I feel this is more a personal preference. Same thing when it comes to over-documenting-code, like in this example.

/// <summary>
/// The refering url
/// </summary>
public static string GetReferer(System.Web.UI.Page page)
{
    string referer = page.Request.ServerVariables["HTTP_REFERER"];
    if (referer != null)
    {
	// Strip away host name in front, if local redirect
	string hostUrl = EPiServer.Configuration.Settings.Instance.SiteUrl.ToString();
	if (referer.StartsWith(hostUrl))
	    referer = referer.Remove(0, hostUrl.Length);
    }
    else
	referer = ""; // Can't have null
    return referer;
}

The document header can safely be removed since the method name does a good job at describing what the purpose of the method is. I’ve not removed the document header, since again this is my personal preference.

A thing I just noticed is that this is actually a Visual Studio class project, and not a Web Application project. I can only guess, but this is probably a project that got upgraded from Visual Studio 2003, I’ve previously had some problem upgrading a project from Visual Studio 2003 to Visual Studio 2008. The conversion usually goes okay (just an update to the csproj file mostly), but the ProjectTypeGuids doesn’t always get added. This is easy to fix, open up BVNetwork.404Handler.csproj in notepad or another source editor, and add this line.

<ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>

Save, and open up the project in Visual Studio. You should now see a slightly different icon at the top of the solution explorer window.

New project icon in Visual Studio

This will allow us to add a designer file to default.aspx from the admin folder. To do this simply right-click the file in solution explorer and choose “Convert to Web Application”. We can now delete the “Web Form Designer generated code”-region.

After all these updates its very important to make sure that the project still builds and that everything functions as expected by testing it.

What I liked the most with this project is how it uses logging. I’m personally very bad at using logging, and know that not all projects use logging as much as they should. Kudos to the authors for this!

I hope you’ve learned a few new things while reading this code, and be sure to try out the 404 handler! For SEO and user experience purposes it is crucial that the users have a smooth transition. They shouldn’t even notice the change when switching to EPiServer from another CMS (that uses different urls). A 404 (file not found) error message can also quickly become a major leak of traffic for your clients site.

Removing duplicates from a PageDataCollection

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

SlideShare .NET API Wrapper

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

Adding CSS and JavaScript files dynamically to your ASP.NET page

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).

EPiServer Link Collection Property

In EPiServer CMS 5 R2, EPiServer added a new property called the link collection. This 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);

Safely Cast from bool? to bool

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

© Copyright Frederik Vig. Based on Fluid Blue theme