Frederik Vig – ASP.NET developer

Follow me

Archive for the ‘ASP.NET’ Category.

ASP.NET ListView and GroupTemplate

The other day I was building a mega menu for a customer – one of the requirements was to let the editor choose the number of columns of links to display. This turned out be quite easy with ASP.NET’s ListView control.

Here’s the code I ended up with.

<asp:ListView runat="server" ID="Menu" ItemPlaceholderID="itemContainer" GroupPlaceholderID="groupContainer">
<LayoutTemplate>
    <div class="mega-menu">
        <asp:PlaceHolder runat="server" ID="groupContainer" />
    </div>
</LayoutTemplate>
<GroupTemplate>
    <ul>
        <asp:PlaceHolder runat="server" ID="itemContainer" />
    </ul>
</GroupTemplate>
<ItemTemplate>
    <li>
        <EPiServer:Property runat="server" PropertyName="PageLink" DisplayMissingMessage="false" />
    </li>
</ItemTemplate>
</asp:ListView>

I could then use the GroupItemCount property to set the number of items to display in each list.

protected override void OnLoad(EventArgs e)
{
	base.OnLoad(e);
 
	if (this.ParentPage == null)
	{
		return;
	}
 
	this.SetupMenu();
}
 
private void SetupMenu()
{
	PageDataCollection children = this.ParentPage.GetChildren();
	children.FilterForVisitor();
	children.FilterCompareTo("PageVisibleInMenu", "true");
	this.ItemsInColumn();
 
	this.Menu.DataSource = children;
	this.Menu.DataBind();
}
 
private void ItemsInColumn()
{
	if (ParentPage.IsValue("MegaMenuLinksInColumn"))
	{
		this.Menu.GroupItemCount = (int)ParentPage["MegaMenuLinksInColumn"];
	}
	else
	{
		this.Menu.GroupItemCount = 4;
	}
}

As you can see the trick was to use the GroupItemCount together with GroupTemplate.

The markup rendered looked something like this.

<div class="mega-menu">
    <ul>
        <li><a href="/Medlem/Kjopeutbytte/">Kjøpeutbytte</a> </li>
        <li><a href="/Medlem/Min-side/">Min side</a> </li>
        <li><a href="/Medlem/Sporsmal-og-svar/">Spørsmål og svar</a> </li>
        <li><a href="/Medlem/Samtykker/">Samtykker</a> </li>
    </ul>
    <ul>
        <li><a href="/Medlem/Medlemsrabatter/">Medlemsrabatter</a> </li>
        <li><a href="/Medlem/Samarbeidspartnere/">Samarbeidspartnere</a> </li>
        <li><a href="/Medlem/Medlemsinformasjon/">Medlemsinformasjon</a> </li>
        <li><a href="/Medlem/Bli-medlem/">Bli medlem</a> </li>
    </ul>
    <ul>
        <li><a href="/Medlem/Medlemskupp/">Medlemskupp</a> </li>
    </ul>
</div>

EPiServer Dropdown CheckList Property

Today I wanted to create a custom property in EPiServer that uses a dropdown list where the user can select multiple options. In HTML you have the select element which when having the attribute multiple=”multiple” actually does what I want, but can get quite long if you have many options to choose from. So I ended up creating my own with the help of the jQuery plugin Dropdown Check List which transforms a HTML select element to a dropdown check list.

I started by creating four classes: CategorySettings.cs, CategorySettingsUI.cs, PropertyDropdownCheckList.cs, and PropertyDropdownCheckListControl.cs. The two classes for the settings I just copied from my previous post on custom settings, they’re used for setting a root EPiServer category to get the options from (easier to maintain than storing them in appSettings for instance).

The code for PropertyDropdownCheckList.cs and PropertyDropdownCheckListControl.cs

using System;
using EPiServer.Core;
using EPiServer.Core.PropertySettings;
using EPiServer.PlugIn;
 
namespace EPiServer.Templates.Public.CustomProperty
{
    [Serializable]
    [PageDefinitionTypePlugIn]
    [PropertySettings(typeof (CategorySettings), true)]
    public class PropertyDropdownCheckList : PropertyString
    {
        public override IPropertyControl CreatePropertyControl()
        {
            return new PropertyDropdownCheckListControl();
        }
    }
}
using System.Web.UI.WebControls;
using EPiServer.DataAbstraction;
using EPiServer.Web.PropertyControls;
 
namespace EPiServer.Templates.Public.CustomProperty
{
    public class PropertyDropdownCheckListControl : PropertyTextBoxControlBase
    {
        protected System.Web.UI.WebControls.ListBox listBox;
 
        public override bool SupportsOnPageEdit
        {
            get { return false; }
        }
 
        public PropertyDropdownCheckList CategoryCheckBoxList
        {
            get { return PropertyData as PropertyDropdownCheckList; }
        }
 
        public override void CreateEditControls()
        {
            this.listBox = new ListBox();
            listBox.SelectionMode = ListSelectionMode.Multiple;
 
            CategorySettings settings = (CategorySettings) base.PropertyData.GetSetting(typeof (CategorySettings));
 
            Category mainCategory = Category.Find(settings.RootCategory);
            var values = CategoryCheckBoxList.Value as string;
 
            foreach (Category c in mainCategory.Categories)
            {
                ListItem li = new ListItem(c.LocalizedDescription, c.Name);
 
                if (!string.IsNullOrEmpty(values))
                {
                    foreach (string value in values.Trim().Split('|'))
                    {
                        if (value == c.Name)
                        {
                            li.Selected = true;
                        }
                    }
                }
 
                listBox.Items.Add(li);
            }
 
            ApplyControlAttributes(listBox);
            Controls.Add(listBox);
        }
 
        public override void ApplyEditChanges()
        {
            string values = string.Empty;
            foreach (ListItem listItem in this.listBox.Items)
            {
                if (listItem.Selected)
                {
                    values += listItem.Value + "|";
                }
            }
 
            SetValue(values);
        }
    }
}

I now ended up being able to choose a root category under my properties settings and rendering a HTML select element with the attribute multiple set to multiple.

This is how it now looked for me in EPiServer edit mode.
Multiple select element in EPiServer edit mode

Next thing was adding the jQuery plugin and implementing the code – which also was very straight forward. I added a new method for registering my resources (CSS and JavaScript files), and a helper method for my CSS files. Here’s how PropertyDropdownCheckListControl.cs ended up looking.

using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using EPiServer.DataAbstraction;
using EPiServer.Web.PropertyControls;
 
namespace EPiServer.Templates.Public.CustomProperty
{
    public class PropertyDropdownCheckListControl : PropertyTextBoxControlBase
    {
        protected System.Web.UI.WebControls.ListBox listBox;
 
        public override bool SupportsOnPageEdit
        {
            get { return false; }
        }
 
        public PropertyDropdownCheckList CategoryCheckBoxList
        {
            get { return PropertyData as PropertyDropdownCheckList; }
        }
 
        public override void CreateEditControls()
        {
            this.listBox = new ListBox();
            listBox.SelectionMode = ListSelectionMode.Multiple;
            listBox.ID = "DropdownCheckList";
 
            CategorySettings settings = (CategorySettings) base.PropertyData.GetSetting(typeof (CategorySettings));
 
            Category mainCategory = Category.Find(settings.RootCategory);
            var values = CategoryCheckBoxList.Value as string;
 
            foreach (Category c in mainCategory.Categories)
            {
                ListItem li = new ListItem(c.LocalizedDescription, c.Name);
 
                if (!string.IsNullOrEmpty(values))
                {
                    foreach (string value in values.Trim().Split('|'))
                    {
                        if (value == c.Name)
                        {
                            li.Selected = true;
                        }
                    }
                }
 
                listBox.Items.Add(li);
            }
 
            ApplyControlAttributes(listBox);
 
            Controls.Add(listBox);
 
            this.RegisterResources();
        }
 
        public override void ApplyEditChanges()
        {
            string values = string.Empty;
            foreach (ListItem listItem in this.listBox.Items)
            {
                if (listItem.Selected)
                {
                    values += listItem.Value + "|";
                }
            }
 
            SetValue(values);
        }
 
        private void RegisterResources()
        {
            Page.Header.Controls.Add(CreateCssLink("~/templates/public/customproperty/ui.dropdownchecklist.css", "screen"));
 
            if (!Page.ClientScript.IsClientScriptIncludeRegistered("jQuery"))
            {
                Page.ClientScript.RegisterClientScriptInclude("jQuery", "/templates/public/customproperty/jquery-min.js");
            }
 
            if (!Page.ClientScript.IsClientScriptIncludeRegistered("uicore"))
            {
                Page.ClientScript.RegisterClientScriptInclude("uicore", "/templates/public/customproperty/ui.core-min.js");
            }
 
            if (!Page.ClientScript.IsClientScriptIncludeRegistered("dropdownchecklist"))
            {
                Page.ClientScript.RegisterClientScriptInclude("dropdownchecklist", "/templates/public/customproperty/ui.dropdownchecklist-min.js");
            }
 
            Page.ClientScript.RegisterStartupScript(GetType(), "dropdownchecklist-setup" + this.GetHashCode(), string.Format("$('#{0}').dropdownchecklist();", listBox.ClientID), true);
        }
 
        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;
        }
    }
}

And the result in edit mode:
Dropdown Check List in EPiServer edit mode

There are a ton of things you can do with the plugin to customize it even more. Check out the demo page for some more samples.

Download the code

CSS and JavaScript compression and bundling

In the last post in Create an EPiServer site from scratch I go through some optimization tips and tricks from YSlow. One of these are CSS and JavaScript compression and bundling of files to create fewer HTTP requests and to send the least amount of data back to the client as possible, removing whitespaces, comments etc, that we don’t need to run our code.

In the past I’ve done this as part of my deployment process, but recently I’ve come across a tool called SquishIt. Which basically is a wrapper for YUI Compressor (you can easily replace it with your compressor of choice). It is dead simple to use, you simply add a reference to SquishIt.Framework.dll in your project, and replace your old CSS and JavaScript links and script tags with code like this.

<%= Bundle.Css()
          .Add("~/Styles/screen.css")
          .Add("~/Styles/print.css")
          .Add("~/Styles/mobile.css")
          .Render("~/Styles/combined-#.css")%>
 
<%= Bundle.JavaScript()
          .Add("~/Scripts/belatedPNG-0.0.8a-min.js")
          .Add("~/Scripts/ie6.js")
          .Render("~/Scripts/ie6combined-#.js") %>

Using a fluent interface makes it easy to add more file references, ending with the Render method that takes a parameter for where to store the combined and minimized file.

It is very easy to toggle SquishIt on and off. Simply set debug=”true” in your web.config file to turn it off and render CSS and JavaScript files as normal, great for when you need to debug your code. Set debug=”false” to enable compression and bundling again.

Best part is that it only took me 2 minutes to setup and add to my current project! Works like a charm so far :) .

Creating a contact form with ASP.NET MVC

We’re going to create a contact form in ASP.NET MVC 2.0, that uses Ajax to send the form data and that uses client side validation to improve the user experience for our users.

Start by creating a new ASP.NET MVC 2.0 web application project in Visual Studio.

New project dialog in Visual Studio

This will create a new project with the default ASP.NET MVC 2.0 sample code.

Inside the Models folder create a new class, and give it the name: Contact.cs. The class is very simple with just some properties for Name, Email and Comment.

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
 
namespace ContactForm.Models
{
    public class Contact
    {
        [Required(ErrorMessage = "You need to fill in a name")]
        [DisplayName("Name")]
        public string Name { get; set; }
 
        [Required(ErrorMessage = "You need to fill in an email address")]
        [DataType(DataType.EmailAddress, ErrorMessage = "Your email address contains some errors")]
        [DisplayName("Email address")]
        public string Email { get; set; }
 
        [Required(ErrorMessage = "You need to fill in a comment")]
        [DisplayName("Your comment")]
        public string Comment { get; set; }
    }
}

Notice the Required, DisplayName and DataType attributes. This is called DataAnnotations, and is a new feature in ASP.NET MVC 2.0, which helps us add validation logic to our models.

Next step is creating the /home/contact URL and the contact view. Open up HomeController.cs, and add the following method.

public ActionResult Contact()
{
    return View();
}

Place your cursor inside View() and press Ctrl+M, Ctrl+V, or just right-click and choose Add View. Check the “Create a strongly-typed view” and type in: ContactForm.Models.Contact.

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<ContactForm.Models.Contact>" %>
 
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
	Contact
</asp:Content>
 
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
 
    <h2>Contact</h2>
 
</asp:Content>

We can now create the form markup.

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<ContactForm.Models.Contact>" %>
 
<asp:Content ContentPlaceHolderID="TitleContent" runat="server">
	Contact us
</asp:Content>
 
<asp:Content ContentPlaceHolderID="MainContent" runat="server">
    <% Html.EnableClientValidation(); %> 
    <% using (Ajax.BeginForm(new AjaxOptions { HttpMethod = "Post", OnComplete = "Contact.onComplete"}))
        { %>
        <%: Html.ValidationSummary(true, "A few fields are still empty") %>
        <fieldset>
            <legend>Contact us</legend>
            <div class="editor-label">
                <%: Html.LabelFor(m => m.Name) %>
            </div>
            <div class="editor-field"><%: Html.TextBoxFor(m => m.Name) %>
                <%: Html.ValidationMessageFor(m => m.Name) %>
            </div>
            <div class="editor-label">
                <%: Html.LabelFor(m => m.Email) %>
            </div>
            <div class="editor-field">
                <%: Html.TextBoxFor(m => m.Email) %>
                <%: Html.ValidationMessageFor(m => m.Email) %>
            </div>
            <div class="editor-label">
                <%: Html.LabelFor(m => m.Comment) %>
            </div>
            <div class="editor-field">
                <%: Html.TextAreaFor(m => m.Comment, 10, 25, null) %>
                <%: Html.ValidationMessageFor(m => m.Comment) %>
            </div>
            <p>
                <input type="submit" value="Submit" />
            </p>
        </fieldset>
        <p id="result"><%: TempData["Message"] %></p>
    <% } %>
</asp:Content>

<%: … %> is just a shortcut for HTML encoding the data, a new feature in ASP.NET 4.0. <% Html.EnableClientValidation(); %> enables client side validation for us, and must be included right before the start of the form. Ajax.BeginForm is a helper method that generates the form tag for us and attaches a little JavaScript code for sending the form data with Ajax, if the client supports it, otherwise the form data will be sent like normal.

Lets create the JavaScript method that gets called when the form is finished. Create a new JavaScript file inside the Scripts folder and give it the name: Site.js. Add the Contact object with the property function onComplete.

var Contact = {
    onComplete: function (content) {
        var result = eval(content.get_response().get_object());
 
        var textNode = document.createTextNode(result.message);
 
        document.getElementById("result").appendChild(textNode);
    }
};

I take the JSON result from the server and add the message to the result paragraph.

If you press F5, the site should open up in your default browser. If you add Home/Contact behind the URL, you should be taken to the Contact Us page.

Contact us form

For the client side validation to work we need to reference a few JavaScript files. Open up Site.master (located in the shared folder), and add the following code right before the closing body tag.

...
<script src="../../Scripts/MicrosoftAjax.js" type="text/javascript"></script>
<script src="../../Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>
<script src="../../Scripts/MicrosoftMvcValidation.js" type="text/javascript"></script>
<script src="../../Scripts/Site.js" type="text/javascript"></script>
</body>
</html>

You should now receive some friendly error messages when you click the submit button without filling out the form properly.

Contact us form validation

Next step is adding the action method to HomeController.cs that will handle the form data.

[HttpPost]
public ActionResult Contact(Contact model)
{
        string message = "There are a few errors";
 
	if (ModelState.IsValid)
	{
                message = "Thanks! We'll get back to you soon.";
	}
 
	if (Request.IsAjaxRequest())
	{
		return new JsonResult { ContentEncoding = Encoding.UTF8, Data = new { success = true, message = message } };
	}
 
        TempData["Message"] = message;
 
	return View();
}

Notice that I check if the request is an Ajax request, if it is I return a JSON object with the result, otherwise I return the whole view with the message.

You would also add some other logic here for saving the message in some way.

The finished form

That’s it!

ListControl AppendDataBoundItems and DataBind

By default when using code like this, the list item will be cleared when data binding is performed.

<asp:DropDownList runat="server" ID="ddlCategories">
    <asp:ListItem>Please choose</asp:ListItem>
</asp:DropDownList>
protected override void OnLoad(EventArgs e)
{
	base.OnLoad(e);
 
	string[] categories = new[] { "C#", "ASP.NET", "EPiServer", "Umbraco", "jQuery" };
 
	ddlCategories.DataSource = categories;
	ddlCategories.DataBind();
}

Will give us.

 <select name="ctl00$MainContent$ddlCategories" id="MainContent_ddlCategories">
	<option value="C#">C#</option>
	<option value="ASP.NET">ASP.NET</option>
	<option value="EPiServer">EPiServer</option>
	<option value="Umbraco">Umbraco</option>
	<option value="jQuery">jQuery</option>
</select>

As you see “Please choose” is not there any more.

However if you set the AppendDataBoundItems property to true (default is false), the list items will be added before data binding is performed.

protected override void OnLoad(EventArgs e)
{
	base.OnLoad(e);
 
	...
	ddlCategories.AppendDataBoundItems = true;
	...
}

The markup is now correct.

<select name="ctl00$MainContent$ddlCategories" id="MainContent_ddlCategories">
	<option value="Please choose">Please choose</option>
	<option value="C#">C#</option>
	<option value="ASP.NET">ASP.NET</option>
	<option value="EPiServer">EPiServer</option>
	<option value="Umbraco">Umbraco</option>
	<option value="jQuery">jQuery</option>
</select>

Detecting Ajax requests on the server

If you use jQuery or ASP.NET AJAX you can easily detect Ajax requests on the server by simply checking for the HTTP_X_REQUESTED_WITH HTTP Header. Both libraries automatically add this to the HTTP Header when they send a request.

Here’s a little code snippet that returns true for Ajax requests.

public static bool IsAjaxRequest()
{
    return HttpContext.Current.Request.Headers["X-Requested-With"] != null && HttpContext.Current.Request.Headers["X-Requested-With"] == "XMLHttpRequest";
}

The cool thing about this is that you can use this in a base page or a HTTP Module, and customize the returned data. You could for instance return JSON or XML.

using System;
using System.Web.UI;
 
namespace MyWebApplication
{
    public class BasePage : Page
    {
        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
 
            if (Helper.IsAjaxRequest())
            {
                Response.Headers.Clear();
                Response.ContentType = "application/json";
                Response.Write(json output);
                Response.End();
            }
        }
    }
}

Getting the Page and EPiServer CurrentPage object from HttpContext

Just a little quick tip when needing to use either the Page object or the EPiServer CurrentPage object from a class file. HttpContext.Current will give you access to the current request, what we can do is cast HttpContext.Current.Handler (since Page implements the IHttpHandler interface) to System.Web.UI.Page.

var page = HttpContext.Current.Handler as System.Web.UI.Page;
 
if (page != null)
{
    // do something with the page object
}

We can take this a step further and cast it to EPiServer.PageBase which gives us access to the CurrentPage object.

private static PageData CurrentPage
{
    get
    {
	var page = HttpContext.Current.Handler as EPiServer.PageBase;
 
	if (page == null)
	{
	    return null;
	}
 
	return page.CurrentPage;
    }
}

Visual Studio 2010 EPiServer Snippets

I finally got my hands on a copy of Visual Studio 2010 RC1! After playing around a bit, I stumbled across the new snippet functionality in Visual Studio 2010. You can now use snippets in the markup files as well (in previous versions you could only use the snippet functionality in code files like class/interfaces/code-behind files etc). This is very cool, and Microsoft has even included a few snippets for their ASP.NET controls and for HTML elements like: a, table, img, div etc. Quite a time saver!

To test the snippets out, simply type the shortcut (eg. ‘a’ or ‘table’) and press tab.

Visual Studio 2010 table snippet

Visual Studio 2010 table snippet

This is a simple, but very cool feature. Imaging all the typing you can get rid off!

Creating your own snippets

In Visual Studio 2010, under the Tools menu, you’ll find the Code Snippets Manager (ctrl+k, ctrl+b).

Visual Studio 2010 Code Snippets Manager

Here you can add new folders that contain your custom snippets, or check out the other snippets added by Microsoft. You’ll also see the path to the snippets folder (C:\Program Files (x86)\Microsoft Visual Studio 10.0\Web\Snippets\HTML\1033\ in my case). If you open up that folder in Windows Explorer you should see at least two folders there, ASP.NET and HTML. Inside both of these folders you’ll find the snippets for the ASP.NET web controls and for HTML elements. We can open up one of the files and take a look at the code.

<CodeSnippet Format="1.1.0" xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <Header>
    <Title>image</Title>
    <Author>Microsoft Corporation</Author>
    <Shortcut>image</Shortcut>
    <AlternativeShortcuts>
      <Shortcut>imagebutton</Shortcut>
      <Shortcut Value="image">asp:image</Shortcut>
      <Shortcut Value="imagebutton">asp:imagebutton</Shortcut>
    </AlternativeShortcuts>
    <Description>Markup snippet for a control that contains an image</Description>
    <SnippetTypes>
      <SnippetType>Expansion</SnippetType>
    </SnippetTypes>
  </Header>
  <Snippet>
    <Declarations>
      <Literal>
        <ID>imageurl</ID>
        <ToolTip>imageurl</ToolTip>
        <Default>imageurl</Default>
      </Literal>
    </Declarations>
    <Code Language="html"><![CDATA[<asp:$shortcut$ imageurl="$imageurl$" runat="server" />$end$]]></Code>
  </Snippet>
</CodeSnippet>

Very easy and readable XML markup, that we easily can tweak to our needs. Say we’re working on a project where 90% of the tables we create should have a class of “products”. Lets create a snippet for this, so that we don’t have to type the same thing every time.

Create a new snippet called table-products.snippet, open it up in your favorite code editor and add this code.

<CodeSnippet Format="1.1.0" xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <Header>
    <Title>table</Title>
    <Author>Frederik Vig</Author>
    <Shortcut>tablep</Shortcut>
    <Description>Markup snippet for a table with class products</Description>
    <SnippetTypes>
      <SnippetType>Expansion</SnippetType>
      <SnippetType>SurroundsWith</SnippetType>
    </SnippetTypes>
  </Header>
  <Snippet>
    <Declarations>
      <Literal>
        <ID>cellspacing</ID>
        <ToolTip>cellspacing</ToolTip>
        <Default>0</Default>
      </Literal>
    </Declarations>
    <Code Language="html"><![CDATA[<table cellspacing="$cellspacing$" class="products">
    <tr>
        <td>$selected$$end$</td>
    </tr>
</table>]]></Code>
  </Snippet>
</CodeSnippet>

I’ve saved table-products.snippet directly in the HTML snippet folder (C:\Program Files (x86)\Microsoft Visual Studio 10.0\Web\Snippets\HTML\1033\HTML in my case), but you can save it anywhere, just remember to add the snippet folder with the Code Snippets Manager in Visual Studio.

We can now test the snippet by typing tablep and pressing tab.

Visual Studio 2010 table products snippet

Visual Studio 2010 table products snippet

Simple example, I know, but you get the idea!.

EPiServer

Naturally, one of the first things I did was add the EPiServer Property web control to the snippet manager ;) . This turned out to be very easy! So I continued on adding snippets for the other EPiServer web controls as well. Here is the list of snippets with their shortcut.

Download the snippets

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.

Html Helpers vs Server controls

I received some very good comments on my post Extending PageData with some cool Html Helpers. Which made me want to explain my thoughts on the subject a bit more :) .

For me, most of the server controls abstract away to much of the details, that I feel we as web developers should be comfortable with, and want to have full control over. Server controls make use of viewstate, affecting both performance and increasing the size of the page. Many render old deprecated HTML markup, make it hard to work with ids in CSS and JavaScript, and have a complex life cycle.

I know there are ways to fix some of these issues (turn off viewstate, control adapters etc). But I feel that the added work load when using server controls, and trying to fix them, is higher than the benefit we get. With Html Helper, I type so much less code than before, and I have full control. I can easily add my own extension methods for stuff that I use a lot, I’ve one place to go to update the code. Combined with Page Type Builder I have intellisense of all my code, I can refactor the code without worrying about breaking it. Instead of having to check for runtime errors I can compile my .aspx/.ascx and be sure that I haven’t added a typo.

A little comparison

Lets say we are tasked with implementing this simple list.

<h3>Recent Articles</h3>
 
<div class="block odd">
 
	<a title="" href="index.html"><img src="images/thumb-1.jpg" class="thumbnail" alt="img" width="240px" height="100px"/></a>
 
	<div class="blk-top">
		<h4><a href="index.html">Aliquam Risus Justo Lorem Ipsum Dolor Sit Amet</a></h4>	
		<p><span class="datetime">August 27, 2009</span><a href="index.html" class="comment">2 Comments</a></p>		
	</div>						
 
	<div class="blk-content">
		<p>
		Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec libero. Suspendisse bibendum. 
		Cras id urna. Morbi tincidunt, orci ac convallis aliquam, lectus turpis varius lorem, eu 
		posuere nunc justo tempus leo. Donec mattis, purus nec placerat bibendum, dui pede condimentum 
		odio, ac blandit ante orci ut diam. Cras fringilla magna. Phasellus suscipit, leo a pharetra 
		condimentum, lorem tellus eleifend magna, eget fringilla velit magna id neque. 				
		</p>					
 
		<p><a class="more" href="index.html">continue reading &raquo;</a></p>
	</div>
</div>
 
<div class="block even">
 
	<a title="" href="index.html"><img src="images/thumb-2.jpg" class="thumbnail" alt="img" width="240px" height="100px"/></a>
 
	<div class="blk-top">
		<h4><a href="index.html">Aliquam Risus Justo</a></h4>	
		<p><span class="datetime">August 26, 2009</span> <a href="index.html" class="comment">2 Comments</a></p>
	</div>						
 
	<div class="blk-content">
		<p>
		Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec libero. Suspendisse bibendum. 
		Cras id urna. Morbi tincidunt, orci ac convallis aliquam, lectus turpis varius lorem, eu 
		posuere nunc justo tempus leo. Donec mattis, purus nec placerat bibendum, dui pede condimentum 
		odio, ac blandit ante orci ut diam. Cras fringilla magna. Phasellus suscipit, leo a pharetra 
		condimentum, lorem tellus eleifend magna, eget fringilla velit magna id neque. 	
 
		<a class="more" href="index.html">continue reading &raquo;</a>			
		</p>
	</div>
 
</div>
 
<div class="fix"></div>
...

We might implement this by using a Repeater control, and a user control with the news story.

<h3>Recent Articles</h3>
<asp:Repeater runat="server" ID="rptNewsList">
<ItemTemplate>
    <div class="block odd">
	<Jungle:NewsStory runat="server" NewsPage="<%# Container.DataItem %>" />
    </div>
</ItemTemplate>
<AlternatingItemTemplate>
    <div class="block even">
	<Jungle:NewsStory runat="server" NewsPage="<%# Container.DataItem %>" />
    </div>
    <div class="fix">
    </div>
</AlternatingItemTemplate>
</asp:Repeater>

The NewsStory user control might look like this when using Html helper methods.

<a href="<%= NewsPage.LinkURL %>">
    <%= NewsPage.HtmlImage("ListImage", "PageName") %>
</a>
<div class="blk-top">
    <h4><%= NewsPage.HtmlLink() %></h4>
    <p><span class="datetime"><%= NewsPage.StartPublish.ToString("dd MMMM yyyy")%></span></p>
</div>
<div class="blk-content">
    <p><%= NewsPage.PreviewText(200) %>
        <a class="more" href="<%= NewsPage.LinkURL %>">continue reading &raquo;</a>
    </p>
</div>

For me this code is very easy to change and update. It is very readable (for me HTML markup describe the meaning of the content and give it context). I can easily add ids where I need. Update the markup without having to change my code-behind (and compile). If I need to change some of the logic I have one extension method to update, not a bunch of code-behind files to go to.

By using server controls it might look something like this.

<asp:HyperLink runat="server" ID="lnkListImage" />
<div class="blk-top">
    <h4><EPiServer:Property runat="server" PropertyName="PageLink" ID="heading" /></h4>
    <p><asp:Label runat="server" CssClass="datetime" ID="lblDate" /></p>
</div>
<div class="blk-content">
    <p><asp:Literal runat="server" ID="ltlPreviewText" />
        <asp:HyperLink runat="server" ID="lnkReadMore" CssClass="more" Text="continue reading &raquo;" />
    </p>
</div>

And the code-behind.

protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);
 
    this.lnkListImage.ImageUrl = this.NewsPage["ListImage"] as string ?? string.Empty;
    this.lnkListImage.Text = this.NewsPage.PageName;
    this.lnkListImage.NavigateUrl = this.NewsPage.LinkURL;
 
    this.heading.PageLink = this.NewsPage.PageLink;
 
    this.lblDate.Text = this.NewsPage.StartPublish.ToString("dd MMMM yyyy");
 
    this.ltlPreviewText.Text = Helper.PreviewText(this.NewsPage, 200);
 
    this.lnkReadMore.NavigateUrl = this.NewsPage.LinkURL;
}

They both do the same thing, and at this point it really is just about what you prefer. But, (for me at least) those extra lines of repetitive code get quite boring to type after a while. There are of course possibilities to have a bunch of helper classes that generate these server controls for you, and then just add them to the various control collections. But why go to all that problem, when a simple extension method in most cases does a much better job?

What do I use? Both. I use what makes the most sense there and then. Server controls are here to help me as a developer, same with extension methods, they are here to help me. When I come to the point that I have to use a lot of hacks and fixes to make something work, I stop, take another look at my solution, and try to use a different approach instead. I feel we’ve reached a point where we need to fix/hack to much to make server controls work. That might be why my most used server control is the Repeater and recently the ListView :) .

© Copyright Frederik Vig. Based on Fluid Blue theme