Building an EPiServer site - SITE DEVELOPMENT - EPiServer 7 CMS Development (2014)

EPiServer 7 CMS Development (2014)

II. SITE DEVELOPMENT

Chapter 6. Building an EPiServer site

We’ve finally reached the point where we’ll start looking at hands-on development. In this chapter you’ll learn how to create an EPiServer site from scratch – setting up a project, defining page types with properties and rendering them. After the chapter is done we’ll have created a simple but functional EPiServer CMS site for a fictive company named Fruit Corp. The site will have two page types with templates and navigation components making it possible for visitors to browse the site based on its structure in the page tree.

The goal of this chapter is to get up and running with, and get a feel for, EPiServer site development. Therefore we’ll focus on getting things done rather than the details, leaving closer looks at the specific development concepts that we use for later chapters to cover.

In this chapter, and throughout the book, we’ll be using ASP.NET MVC (with the default view engine, Razor), as opposed to ASP.NET Web Forms. For readers interested in Web Forms there will sidebars mapping what we have discussed in a MVC context to Web Forms.

Creating a site project

There are two ways of creating an empty EPiServer site suitable for further development, either by using EPiServer’s Deployment Center or by using EPiServer’s Visual Studio integration. When using Deployment Center follow the same steps as for creating an Alloy sample site described in chapter two, only don’t check the “Install Alloy Sample Site” checkbox.

In order to create an empty site using the Visual Studio integration select FileNew Project in Visual Studio and select either “EPiServer Web Site” or “EPiServer Web Site (MVC)”. Doing so creates a site using the same process as Deployment Center, you even see the same progress bars. However, there are no configurations to be made, everything is pre-configured.

Both ways of creating a site accomplishes roughly the same thing, but there are some differences. When using Deployment Center you get to choose some settings such as the UI path, database name etc. Deployment Center creates a site in the local IIS server. The Visual Studio integration doesn’t.

On the other hand the Visual Studio integration does a couple of things that Deployment Center doesn’t. One of those things is quite minor - it sets the site’s start page to the root page in episerver.config. When using Deployment Center the value for that setting is 0, which is an ID for a page that doesn’t exist. As a consequence, we see an exception messages pointing that out when viewing the site and have to manually change the setting to the root page in order to be able to proceed.

The other thing that the Visual Studio integration does that Deployment Center doesn’t is far more important. It creates a Visual Studio project. Using Deployment Center you get a site with everything needed to run it, but you don’t get a project in which you can place your own code, meaning that you’ll have to create a project of the correct type yourself.

As most templates for web projects in Visual Studio comes with some content, such as a web.config file, you can’t create the project directly in the web root of the site or you’ll overwrite files that Deployment Center put there. So, when creating a site from scratch using Deployment Center we have to jump through some hoops to get a Visual Studio project set up for it.

For the reasons mentioned above, and for the convenience of just selecting FileNew Project in Visual Studio I tend to recommend using the Visual Studio integration when setting up a new development project. That’s what we’ll do now, taking our first steps to creating an EPiServer site.

Creating a site using the Visual Studio integration

Fire up Visual Studio and (given that you’ve installed the Visual Studio integration) select FileNew Project. Navigate to the EPiServer group of project templates (located under Installed/Templates/Visual C#/EPiServer) and select EPiServer Web Site (MVC). For project name use “FruitCorp.Web” and make the solution name “FruitCorp”. You are of course free to choose a different project name should you want to, but doing so may mean you’ll have to alter namespaces when typing in/copying code from the book.

At the top of the dialog it’s possible to choose a specific .NET version. Make sure it’s either .NET Framework 4 or 4.5. The below image shows how the dialog should look. When it does, press the OK button.

Once the project has been created you should see a project structure like in the image below in Visual Studio’s Solution Explorer (press CTRL+ALT+L if you don’t see Solution Explorer).

Opening up the newly created projects folder in Windows Explorer (easily done by right clicking on the project in Solution Explorer and selecting “Open Folder in File Explorer”) reveals a bit more files and folders than what’s shown in Visual Studio.

Besides creating the project with the files shown above we will also have a new database in our local SQL Server instance. The database will have the same name as the project but prefixed with “db”.

Now that we’ve got ourselves a site we should run it and take a look at it. While the Visual Studio integration doesn’t create an IIS site like Deployment Center does we can use the built in development server in Visual Studio to do that instead. Simply select DebugStart Without Debugging or press CTRL+F5. Doing so starts the site in the development web server and opens up its start page in the computers default browser.

After starting the site we should see EPiServer’s login dialog in the browser. That may appear odd given that the browser opened up the site’s start page which should be public. However, recall that a site created with the Visual Studio integration has its start page set to the site’s root page (there isn’t yet any “real” page to set it to) and the root page is configured, in terms of access rights, not to be shown to public visitors. So, we’re not seeing the login dialog because we asked to log in to EPiServer’s UI but rather because the page that we’re requesting is protected.

Log in using your Windows user name and password and you should see the root page. After doing so, let’s go see how things look in edit mode. To do that we need to get to Online Center which is located under the UI path, which the Visual Studio integration has set to /episerver. In other words, type in /episerver after the current URL in the browser and hit enter.

Once in Online Center navigate to edit mode. Opening up the Pages gadget and looking at the page tree reveals a site that is completely empty.

If we try to remedy this by creating a new page below the root page we find that we can’t. While the dialog for creating a new page shows up and we can enter a name for the page there aren’t any page types to choose from.

Defining a start page

With a functional but completely empty site whose start page is the root page the first natural step is create a real, albeit simple, start page. In order to do that we need to:

1. Create a page type.

2. Create a page of that type.

3. Re-configure the site to use the new page as its start page.

Let’s create ourselves a page type! Create a new C# class in the Models folder by right clicking on it in Solution Explorer and selecting AddNew item… in the menu that pops up. Then select “Class” (located under Installed/Visual C#/Code). Name the new file “StartPage.cs” and click add.

The code in the newly created file looks like this:

usingSystem;

usingSystem.Collections.Generic;

usingSystem.Linq;

usingSystem.Web;

namespaceFruitCorp.Web.Models.Pages

{

publicclassStartPage

{

}

}

The first four lines of code, the using statements, were added by Visual Studios template for classes. At the moment all of them are redundant and can safely be removed.

In order to turn this ordinary class into a page type we need to make two changes to it. First of all we make it inherit from EPiServer’s PageData class.

1 usingEPiServer.Core;

2

3 namespaceFruitCorp.Web.Models.Pages

4 {

5 publicclassStartPage : PageData

6 {

7 }

8 }

Compared to the first version of the class the above code has three changes:

1. The original using statements have been removed.

2. On line 1 a using statement for the EPiServer.Core namespace (where the PageData class resides) has been added.

3. On line 4 the class is made to inherit from PageData.

Now we’ve got a class that if instantiated would result in objects representing pages in the CMS, although as we’ll see later we should never instantiate content objects ourselves. However, there’s one more thing we need to do in order for EPiServer to recognize the class as a page type, meaning that it can be saved using EPiServer’s API without errors and that it shows up in the list of available page types when creating a new page. We need to annotate it with the ContentTypeAttribute class, located in the EPiServer.DataAnnotations namespace.

1 usingEPiServer.Core;

2 usingEPiServer.DataAnnotations;

3

4 namespaceFruitCorp.Web.Models.Pages

5 {

6 [ContentType]

7 publicclassStartPage : PageData

8 {

9 }

10 }

That’s it! We have now created a class that defines a page type, a “page type class”. In order to do so we have:

1. Created an ordinary C# class.

2. Made it inherit the PageData class (line 1 + 7).

3. Added a ContentType attribute to it (line 2 + 6).

If the site is still running in IIS Express compile the project using BuildBuild Solution or CTRL+SHIFT+B. If not run the site (which automatically compiles the project) using DebugStart Without Debugging or CTRL+F5. Now, let’s try to create a page again.

This time around the list of page types to choose from in the dialog for creating a new page isn’t empty, it contains a page type named “StartPage”, the name of our class. Fill in “Home” as the name of the page and click on the page type to create a page.

Doing so creates a page of the StartPage type and the new page is opened up for editing. However, as opposed to when creating a new page on the Alloy sample site we’re not seeing the page in on-page editing mode but in forms editing mode.

The newly created page in forms editing mode.

The newly created page in forms editing mode.

That’s because EPiServer has no way of rendering the page as we have yet to create a template for it. We’ll get to that, but for now just publish the page using the button in the top right corner, next to the blue arrow that is pointing out that it’s not yet published.

Next, note the page’s ID. The ID is displayed, along with the type, in forms editing mode. It’s also possible to see the ID by looking at the URL in the browser when editing a content item.

Two ways of seeing a page's ID.

Two ways of seeing a page’s ID.

With the page’s ID noted head back to Visual Studio an open up episerver.config (located below web.config in Solution Explorer). In episerver.config you’ll find a siteSettings element. That element has an attribute named “pageStartId” whose value currently is “1”. Change that to the ID of the newly created page (most likely 4) and save the file.

Now, go back to the site and take a look at the page tree. There’s no need to compile the project as the configuration file isn’t compiled and the site will automatically restart upon making a change to it. Open up the Pages gadget and note that the page now has a house icon, indicating that it is the start page for a site.

The page tree after creating a page and configuring it as start page.

The page tree after creating a page and configuring it as start page.

Creating a template

We now have a start page but no way of rendering it. We saw that as we were taken directly to forms editing in EPiServer’s edit mode after having created it or later opened it for editing. If we were to look at the site outside of edit mode (by going to http://localhost:<some port number>/) we’d get a 404 Not Found error page.

To remedy that we’ll create an initial, bare bones template for the start page. When building EPiServer site’s with MVC a template for a page type corresponds to a controller and one or more views. Let’s create a controller!

Right click on the Controllers folder in Solution Explorer in Visual Studio and select AddController. Enter “StartController” as the controller name and leave all other settings as is (“Empty MVC Controller” as template under Scaffolding options) and click Add. The result should look something like this:

1 usingSystem;

2 usingSystem.Collections.Generic;

3 usingSystem.Linq;

4 usingSystem.Web;

5 usingSystem.Web.Mvc;

6

7 namespaceFruitCorp.Web.Controllers

8 {

9 publicclassStartController : Controller

10 {

11 //

12 // GET: /Start/

13

14 public ActionResult Index()

15 {

16 return View();

17 }

18

19 }

20 }

The default controller template in Visual Studio adds a few things that we can safely remove - all but the last using statements (line 1-4) and the comments (line 11-12). Now, let’s turn this ordinary ASP.NET MVC controller into a controller for EPiServer pages of our StartPage type. In order to do that we make it inherit from the generic base class PageController<T> found in the namespace EPiServer.Web.Mvc with our page type class as the type parameter. After doing so our controller looks like this:

1 usingEPiServer.Web.Mvc;

2 usingFruitCorp.Web.Models.Pages;

3 usingSystem.Web.Mvc;

4

5 namespaceFruitCorp.Web.Controllers

6 {

7 publicclassStartController : PageController<StartPage>

8 {

9

10 public ActionResult Index()

11 {

12 return View();

13 }

14

15 }

16 }

PageController<T> implements EPiServer’s IRenderTemplate<T> interface. IRenderTemplate<T> is a marker interface (it has no members) that the CMS uses to identify classes that claim to be able to render content of a specific type. As such, we have just created a controller that EPiServer will try to use when serving requests for pages of our StartPage type.

However, the controller doesn’t yet do anything useful. Our next step is to implement the Index action method so that it gets a hold of the page object for which it is being invoked, and pass it along to a view. Developers used to the MVC pattern may find it strange that we don’t create a separate view model to pass to the view. Indeed we could, but for now we’re keeping it simple and we’ll discuss view models later on. Anyhow, in order to implement the Index action method we add a parameter of type StartPage named “currentPage” to it. Note that the exact spelling and casing of the parameter name here is important. If misspelled the parameter will be null.

public ActionResult Index(StartPage currentPage)

{

return View(currentPage);

}

Now our action method will be invoked with the StartPage object that corresponds to the page a visitor has requested. This happens through model binding in MVC, a feature of the ASP.NET MVC framework that maps HTTP request data to a model object automatically.

Under the hood: Model binding content objects

Normally the MVC framework only utilizes the data associated with the request (such as the URL, post data etc) and wouldn’t be able to provide a complex PageData object with values fetched from the database. However, EPiServer handles this for us by extending the frameworks standard model binding allowing for content objects to be model bound. This way simple information from the request such as the URL is mapped to the id of a page and then conveniently translated into a complex PageData object, fetched using EPiServer’s API, by EPiServer’s custom routing and model binding.

The component that identifies action method parameters that should be bound to content objects is the EPiServer.Web.Mvc.ContentDataValueProvider class which looks for parameters named “currentData”, “currentPage” and “currentBlock”. In other words, in order for the above action method to work it’s vital that the parameter has exactly one of those three names, and here “currentPage” is clearly the most descriptive.

Now our simple action method handles StartPage objects and passes them along, as model objects, to a view. However, we have yet to add such a view. In order to do right click somewhere within the code for the method and select Add View. Let the view name be “Index” and check the “Create a strongly-typed view” checkbox. Type in StartPage as the Model class. If the type doesn’t appear in auto complete or in the dropdown in the dialog it’s because you haven’t compiled the project yet. If so, just type in the name of the class and adjust the namespace in the @model directive after the view has been created. Finally, un-check the “Use a layout or master page” checkbox (we’ll modify it to use a layout later) and click the Add button.

The code for the resulting view looks like this:

1 @model FruitCorp.Web.Models.Pages.StartPage

2

3 @{

4 Layout = null;

5 }

6

7 <!DOCTYPE html>

8

9 <html>

10 <head>

11 <meta name="viewport" content="width=device-width" />

12 <title>Index</title>

13 </head>

14 <body>

15 <div>

16

17 </div>

18 </body>

19 </html>

Add some hard coded greeting message inside the div tag (on line 16), like the code below and then either start the site if it’s not still running, or compile the project if it is. Note that if you start the site while having a view open the site will be opened with the URL of the view. This can sometimes be confusing as the view isn’t meant to be viewed stand-alone, without first going through a controller and you may see some strange error message. If so, just ignore that and type in the URL for edit mode in the browsers navigation tool bar.

<div>

<h1>Hello world, and welcome to our site!</h1>

</div>

With this very simple view in place compile the project and take a look at the site. You’ll find that when viewing the start page in edit mode you’re no longer automatically thrown directly into forms editing mode. Also, viewing the site outside of edit mode no longer returns a 404 page but instead a page corresponding to the view you just created.

It may not be the prettiest thing ever seen in a browser, but we now have a functional view. So far we haven’t rendered any properties though. What little is shown on the page is entirely hard coded. We’ll soon look at how to define properties in page types, but before we do that we can render one of the “built-in” properties inherited from the PageData class, the PageName property. To render the page’s name in the view remove the hard coded greeting in the H1 tag and replace it with @Model.PageName, like this:

<div>

<h1>@Model.PageName</h1>

</div>

Save the view and reload the start page either in EPiServer’s edit mode or publicly on the site. (There’s no need to compile the project as views aren’t compiled, or rather they are compiled Just-In-Time.)

Now the view is displaying a property from its model object, here meaning the PageData object for the currently viewed page as we’re passing it directly to the view. Should we change the page’s name in forms edit mode the change will be reflected in the view. However, we’re not able to edit the PageName property in on-page-edit mode. There are several ways to accomplish that, which we’ll look at later on, but for now we can make it happen using perhaps the simplest approach; with EPiServer’s PropertyFor HTML helper method.

In its simplest form the PropertyFor method (located in the EPiServer.Web.Mvc.Html namespace) takes a lambda expression as its single parameter. The expression is of type Func<TModel, TValue> meaning that the expression should be a function that receives an object of the same type as the views model and returns a value of any type. Using this we tell the method the name of the property we want to render in a strongly typed way. To use it to render the PageName property, update the code inside the H1 tag to look like this:

<div>

<h1>@Html.PropertyFor(x => x.PageName)</h1>

</div>

After saving the view (and possibly reloading the start page in edit mode) we can now edit the PageName property, which we currently use as the page’s heading, in on-page-edit mode.

The page name editable, and being changed, in on-page-edit mode.

The page name editable, and being changed, in on-page-edit mode.

Congratulations! We have just created, or seen how to create, a page type and a template. Albeit extremely minimalistic, we have just gone through how to create a fully functional EPiServer CMS site. To make the site a bit more useful we’ll now create a second page type and add some properties of our own to it. However, prior to doing that we should make it easier for ourselves to work with views by adding a layout. We’ll also add some CSS that will make it easier to position elements in views and make them look better.

Web Forms note

When using Web Forms there is no such concept as controllers and views. Instead we create pages. A page is made up of two components, a file with an .aspx file name extension containing HTML markup and various types of controls and a code-behind file with an .aspx.cs file name extension (when using C#) containing methods and properties. When an ASPX file is requested on the web server for the first time it’s JIT compiled with the class in the code-behind file (which is already compiled) as its base class.

When building an EPiServer site with Web Forms the equivalent of creating a template as a controller inheriting from PageController<T> and one or more views in MVC is to create an ASPX page and make it inherit from a class named TemplatePage found in the EPiServer namespace. As with the PageController class, the TemplatePage class implements IRenderTemplate<T> and has a type parameter (although there is one without it as well for legacy reasons) with which we tell EPiServer what types of pages the template handles. When creating such a template we can retrieve the specific page that it’s supposed to render using the CurrentPage property inherited from the TemplatePage<T> class. The CurrentPage property will be of type T.

While the two don’t work exactly the same the rough equivalent of the PropertyFor method in MVC is a server control simply named Property found in the namespace EPiServer.Web.Controls. The Property control has a string property named PropertyName with which we tell the control the name of the property that we want it to render from the current page (the CurrentPage property in the host ASPX page). The Property control also has a number of other properties that we may optionally use to customize its rendering. For instance, the Property control always wraps the value of the property in a HTML element of some sort depending on the property’s type and its CustomTagName property can be used to override the default behavior and render the property’s value within a specific tag.

In order to render the PageName property the same way as we did in the MVC view we would in Web Forms have written:

<div>

<EPiServer:Property PropertyName="PageName" CustomTagName="h1" runat="server" />

</div>

Layout and CSS

In the previous section we created a simple template for a page type in the form of a controller and a view. As soon as we add a second page type we run in to a maintainability problem with regards to views. Many of the elements are often the same on all or most of a sites views. For instance, all views for pages when they are rendered as full-scale pages (as opposed to as partials on other pages) need to have HTML and BODY tags. Also the content, or functionality to generate content, in both the HEAD and BODY part of them may be the same. Apart from addressing the code duplication problem, having some parts of views located in a common place help to create and maintain a consistent look and feel for them.

When using ASP.NET MVC and the Razor view engine, as we are, this problem is easily solved using something called layouts. A layout looks almost like a view, but it’s not “complete” and can’t be used on its own. Sort of like an abstract class it works as a template for other views. A layout must somewhere in its markup invoke a method called RenderBody. Views that use a layout define the content that is inserted in place of the RenderBody call. It’s also possible for layouts to define other sections that views can insert content into.

To make our existing view use a layout we’ll start by creating the layout. Right click on the folder named “Shared” inside the “Views” folder in Solution Explorer and select AddView…. Name the view “_Root” (by convention layout names a prefixed with an underscore). Un-check the “Use a layout or master page” checkbox and click the Add button. The resulting layout file should look something like this:

@{

Layout = null;

}

<!DOCTYPE html>

<html>

<head>

<meta name="viewport" content="width=device-width" />

<title>_Root</title>

</head>

<body>

<div>

</div>

</body>

</html>

As you can see, the layout looks almost identical to how the view we created in the previous section initially looked. As such there is little work needed for us here given that our current task is to make our existing view use a layout but generate the same output. Therefore all we have to do is insert a call to RenderBody between the two DIV tags.

<div>

@RenderBody()

</div>

Next we go back to the start page’s view and remove all markup except for the H1 tag in which we render the PageName property. After doing so the view should look like this:

1 @model FruitCorp.Web.Models.Pages.StartPage

2

3 @{

4 Layout = null;

5 }

6

7 <h1>@Html.PropertyFor(x => x.PageName)</h1>

Finally we need to instruct the view to use our newly created layout. We do that by setting Layout property to the path to the layout file on line 4, like this:

1 @model FruitCorp.Web.Models.Pages.StartPage

2

3 @{

4 Layout = "~/Views/Shared/_Root.cshtml";

5 }

6

7 <h1>@Html.PropertyFor(x => x.PageName)</h1>

That’s it. Our view is now using a layout. So far we’ve yet to reap any benefits from that, but as soon as we create another template we certainly will. Next, let’s add a style sheet that will help us with the graphical layout of the site. In order to focus on EPiServer development without getting tangled up in styling with CSS we’ll use an existing CSS framework. You are of course free to use any CSS framework or custom CSS that you’d like, both when developing sites and when following this book. However, in the examples in the book we’ll be using the Twitter Bootstrap framework. I choose Bootstrap for a couple of reasons. First of all it’s a popular, widely used framework. It’s also a framework that seems to suit back-end oriented developers well. Last but not least, EPiServer uses Twitter Bootstrap in several of their template and demo sites.

Bootstrap 101

Bootstrap is a front-end framework built at Twitter. It consists of three components; CSS files, a font used for icons and JavaScript files. There are two CSS files, plus two that are minified versions of the first two. These are named bootstrap.css and bootstrap-theme.css. The first is Bootstrap’s core style sheet while the second can optionally be used to make various elements look more three-dimensional.

Using Bootstrap’s style sheet it’s possible to create an entire site without ever writing a line of CSS as it provides ready-to-use CSS classes for grids, forms, tables and a host of other components. By using the JavaScript file as well it’s easy to add transitions, modal dialogs, tool tips, slideshows etc without having to write any JavaScript at all.

Of course, while it’s possible to only use Bootstrap when building a site most sites that uses it do extend it with custom styling and additional functionality in JavaScript. Nevertheless, Bootstrap provides a solid foundation to build upon and, which we use it for in this book, a simple way of creating well functioning user interfaces without having to write CSS.

At the heart of Bootstrap is its grid system. The grid system utilizes twelve columns. This means that we can use CSS classes from Bootstrap to create rows that can contain a variable number of “cells” where each cell can be anything from 1/12 of the total width of available (the width of the page or of the parent element) to the full width of the grid. Rows are elements, typically div tags, with a CSS class named “row”. Cells, or columns, within rows are elements, again typically div tags, with a “col-md-*” CSS class, where the “*” is a number between one an twelve. The number defines how many column widths wide it is. That is, the width of a col-md-* element is at least * times the total width pixels divided by 12. Here’s an example:

1 <!DOCTYPE html>

2 <html>

3 <head>

4 <link href="css/bootstrap.min.css" rel="stylesheet">

5 <style>

6 .col-md-8 {

7 background: red;

8 }

9 .col-md-4 {

10 background: green;

11 }

12 </style>

13 </head>

14 <body>

15 <div class="row">

16 <div class="col-md-8">

17 <h2>col-md-8</h2>

18 </div>

19 <div class="col-md-4">

20 <h2>col-md-4</h2>

21 </div>

22 </div>

23 </body>

24 </html>

The above code defines a simple HTML page with Bootstrap’s CSS added (assuming it exists in a folder named “css” relative to from where the page is loaded). It also contains a small piece of in-line CSS styling in order to make col-md-8 elements red and col-md-4 elements green. In the body it defines a single row with two columns, the first eight column widths wide and the other four column widths wide. Here’s how it looks in a browser:

To center the grid (in the above case the single row) and make it narrower than the full page width it can be wrapped in an element with a class named “container”. Also, besides adding col-md-* classes to columns it’s possible to position them at a certain number of column widths left of the previous column using a class named “col-md-offset-*” where the * is the number of column widths to offset it by.

Besides the grid system Bootstrap’s stylesheet also contains CSS classes for a host of components, typography etc. As an example there are classes for a number of different types of navigations. Below is an example with a more complex grid wrapped in a container as well as one of the navigation types.

1 <!DOCTYPE html>

2 <html>

3 <head>

4 <link href="css/bootstrap.min.css" rel="stylesheet">

5 </head>

6 <body>

7 <div class="container">

8 <nav class="navbar navbar-inverse">

9 <ul class="nav navbar-nav">

10 <li class="active"><a href="#">Home</a></li>

11 <li><a href="#">About</a></li>

12 <li><a href="#">Contact</a></li>

13 </ul>

14 </nav>

15 <div class="row">

16 <div class="col-md-8">

17 <h2>col-md-8</h2>

18 </div>

19 <div class="col-md-4">

20 <h2>col-md-4</h2>

21 </div>

22 </div>

23 <div class="row">

24 <div class="col-md-3">

25 <h2>col-md-3</h2>

26 </div>

27 <div class="col-md-7 col-md-offset-2">

28 <h2>col-md-7 + col-md-offset-2</h2>

29 </div>

30 </div>

31 </div>

32 </body>

33 </html>

Rendered in a browser the above markup produces this:

For more information about Bootstrap, documentation, examples and downloads see Bootstrap’s site, http://getbootstrap.com/. Also, note that what’s discussed here is version three of Bootstrap. Most of the discussion also applies to version two, but in that version we used “span*” classes instead of “col-md-*” classes.

Bootstrap can easily be added to a site using NuGet. Simply search for “twitter bootstrap” in NuGet’s UI and install it or type in “Install-Package Twitter.Bootstrap” into the package manager console. With that done, we can add Bootstrap’s style sheet to our layout file. The NuGet package places Bootstrap’s CSS files into the project in a folder called Content, meaning that we can link to the style sheet with <link href="~/Content/bootstrap/bootstrap.min.css" rel="stylesheet">. While we’re working with our layout let’s also center what ever is rendered in place of the RenderBody method by adding a “container” class to the DIV tag that wraps it. Here’s what the layout should look like:

1 @{

2 Layout = null;

3 }

4

5 <!DOCTYPE html>

6

7 <html>

8 <head>

9 <meta name="viewport" content="width=device-width" />

10 <link href="~/Content/bootstrap/bootstrap.min.css" rel="stylesheet">

11 <title>_Root</title>

12 </head>

13 <body>

14 <div class="container">

15 @RenderBody()

16 </div>

17 </body>

18 </html>

Web Forms note

When building sites with Web Forms, or when using the Web Forms view engine when using ASP.NET MVC, we can use “Master Pages” instead of layouts in the Razor view engine. When doing so EPiServer doesn’t provide or require any particular base class for the master page.

Creating a page type with properties

We’ve now got a simple start page and a basic layout to work with. The start page doesn’t look like much and that’s natural as it’s often based on other content on the site and therefore typically not the first page type and template to be completed first. Still, we needed to have some sort of start page in order to have a functioning site. So, while we’ll come back to it later, like in many real-world EPiServer projects the next natural step is to create a page type that will be more widely used across the site.

While EPiServer sites often have quite a few page types it’s common to have some sort of “standard” page type used for various purposes where editors need to publish information. On a corporate web site such a page type may be used for the majority of the sites page while on a newspaper’s site it may be used for “utility” pages such as “About the newspaper” while using more specialized page types for actual articles.

Anyhow, let’s create a typical standard page type suitable for our site. “Standard pages” often consist of a headline, a preamble and a body of copy. To begin with, let’s create the page type class. Just as the previous time we created a page type we start by right clicking on the Models folder in Solution Explorer and select to add a new C# class. We’ll name the class “StandardPage”. Once created we can clean up unused using statements and perform the steps necessary for turning the ordinary class into a page type class recognized by EPiServer.

As you may recall from when we created the StartPage class there are two steps to converting a regular C# class into a page type class. We make it inherit from PageData and we add a ContentType attribute to it. After doing so the StandardPage class looks like this:

1 usingEPiServer.Core;

2 usingEPiServer.DataAnnotations;

3

4 namespaceFruitCorp.Web.Models.Pages

5 {

6 [ContentType]

7 publicclassStandardPage : PageData

8 {

9 }

10 }

Now, let’s add some properties to the page type class. In it’s simplest form a C# property recognized as a page type property by the CMS is an ordinary C# property with automatic/compiler generated getters and setters. There’s just one little gotcha; it needs to be marked as virtual, meaning that it can be overridden in sub classes. If it isn’t, the project compiles but we get an exception saying that the property needs to be virtual when the site starts up. We’ll get back to the reason for this.

To start with we can use the PageName property inherited from PageData as headline but we need a preamble property and a property for the main body of copy. So, given that we want the preamble as a string (seems natural, right) we’ll add a string property to the class. We’ll call it MainIntro.

1 usingEPiServer.Core;

2 usingEPiServer.DataAnnotations;

3

4 namespaceFruitCorp.Web.Models.Pages

5 {

6 [ContentType]

7 publicclassStandardPage : PageData

8 {

9

10 publicvirtual string MainIntro { get; set; }

11

12 }

13 }

That’s it. When creating a page of this type editor we’ll see a property named MainIntro edited using a textbox in forms editing mode, as well as in on-page-edit mode if we render it in a template using the PropertyFor method. Next we need to add the property for the page’s main content. Here we want editors to be able to insert richer content such as headings and images and to use formatting such as font modifications (bold, italic etc.). In other words we need a property that stores HTML and is edited using a WYSIWYG editor. In order to do that we create a property of an EPiServer specific class named XhtmlString, found in the EPiServer.Core namespace.

usingEPiServer.Core;

usingEPiServer.DataAnnotations;

namespaceFruitCorp.Web.Models.Pages

{

[ContentType]

publicclassStandardPage : PageData

{

publicvirtual string MainIntro { get; set; }

publicvirtual XhtmlString MainBody { get; set; }

}

}

Looks good! We now have a page type that supports a headline (by using the PageName property), a simple string based preamble and a richer main text.

information

Property naming conventions

While EPiServer doesn’t enforce any naming policy for properties added to page types or other content types the names of the properties created above, MainIntro and MainBody wasn’t arbitrary. When developing sites using Web Forms EPiServer provides a number of server controls (templated controls used to easily render lists etc). Some of those controls look for properties with specific names, most prominently MainIntro and MainBody, and assume that they are used as the main content of a page and as a short introduction respectively.

While there aren’t any such controls for MVC and many EPiServer sites built with Web Forms don’t use those controls many EPiServer developers still name their properties MainIntro and MainBody. Especially with regards to MainBody the name has very much become a convention in the EPiServer development community and it is considered a best practice to name the main rich content property in page types MainBody. This helps other developers to quickly identify the property.

With the page type in place it’s time to create a template for it. We begin by creating a controller that handles pages of this type. In order to do that we repeat the steps performed when creating a controller for the StartPage page type. Meaning that we add a new, empty MVC controller in the Controllers folder in the project, only this time we name it StandardPageController. Next we make it inherit from PageController<StandardPage> and implement its Index action method to take a StandardPage object as parameter that it immediately passes on to the view. As before the name of the parameter is important, it needs to be exactly “currentPage” (or “currentData” or “currentBlock”). Here’s what the controller should look like:

usingEPiServer.Web.Mvc;

usingFruitCorp.Web.Models.Pages;

usingSystem.Web.Mvc;

namespaceFruitCorp.Web.Controllers

{

publicclassStandardPageController : PageController<StandardPage>

{

public ActionResult Index(StandardPage currentPage)

{

return View(currentPage);

}

}

}

Next we add a view for the Index method by right clicking somewhere in it and selecting Add View. Before clicking the Add button in the “Add View” dialog we set the model class to StandardPage. This time we also check the “Use a layout or master page” checkbox and enter (or browse to) the path to our layout file, ~/Views/Shared/_Root.cshtml. The resulting view looks like this:

1 @model FruitCorp.Web.Models.Pages.StandardPage

2

3 @{

4 ViewBag.Title = "Index";

5 Layout = "~/Views/Shared/_Root.cshtml";

6 }

7

8 <h2>Index</h2>

To start with we can remove the assignment to ViewBag.Title (line 4) and the H2 tag (line 8) added by Visual Studio’s view template. Next we add a simple grid with three columns to the view using CSS classes from Bootstrap.

1 @model FruitCorp.Web.Models.Pages.StandardPage

2

3 @{

4 Layout = "~/Views/Shared/_Root.cshtml";

5 }

6

7 <div class="row">

8 <div class="col-md-3"></div>

9 <div class="col-md-6">

10

11 </div>

12 <div class="col-md-3"></div>

13 </div>

For now the columns only serve to make the main content in the page, which we’ll put in the middle column, narrower than the full grid width. Later we can use the other columns for navigation and other components. Now, let’s render the three properties that we want to display on the page. As before we do this using the PropertyFor HTML helper method.

<div class="col-md-6">

<h1>@Html.PropertyFor(x => x.PageName)</h1>

<p class="lead">

@Html.PropertyFor(x => x.MainIntro)

</p>

@Html.PropertyFor(x => x.MainBody)

</div>

As shown above we wrap the PageName and MainIntro properties in HTML tags but not the MainBody property. We do this as we want the values in them to be rendered inside block level elements. Also, we may want them to have a certain element type, H1 for PageName, or be able to add CSS classes, as in the “lead” class from Bootstrap for MainIntro. However, we don’t wrap the MainBody property in an element. While we of course could do that should we have a reason to XhtmlString properties are typically edited using a WYSIWYG editor (TinyMCE) which normally ensures that the content is in one or more suitable elements. For instance, TinyMCE creates P tags for each paragraph entered.

Now, let’s take our new page type for a spin. Start the site, or only compile the project if it’s already running, and create a page named “About Fruit Corp” under the sites start page. Make sure you select the “StandardPage” page type and not the “StartPage” one. Once created, enter some text in the properties. A filler text generator, such as Veggie ipsum, may come in handy.

A new standard page just after having been created.

A new standard page just after having been created.

A standard page with text entered in the MainIntro and MainBody properties.

A standard page with text entered in the MainIntro and MainBody properties.

The page type and template looks pretty good. However, there are a couple of issues with the MainIntro property. First of all it’s looks different in on-page-edit mode than it does when viewing the page outside of edit mode. Second, it is edited using a textbox which is a bit small. A textarea would be better. We also have other work to do before we can claim that this page type has been created with good EPiServer developer craftsmanship, such as making the property names more descriptive and translated to different languages in edit mode. We’ll look at how to fix these issues and improve the quality of the page type in upcoming chapters, when we look at properties on content types in more detail and learn more about property rendering.

discussion

Why separate properties?

One may wonder why we used three different properties for the heading, preamble and main body of copy for the page type that we just created. After all, couldn’t we just have added a single HTML property and let editors create the headline and preamble with specific CSS classes inside it instead? Indeed we could have. However, using separate properties here have several benefits.

By using separate properties we help guide editors with regards to what content they can, or are expected, to enter in order to create pages that are in harmony with the site’s structure and design. Also, having the heading and preamble as separate properties, and as simple strings, is handy when we may want to render them separately. For instance, the heading property should probably be repeated in the title tag (as in <head><title>) and the preamble may be used as the pages meta description. The heading and preamble is often also used when rendering the page as part of other pages, for instance in listings.

Finally, as we’ll see later on, properties can be validated in order to ensure that editors enter values in them, or that entered match a certain condition. While we may not want to force editors to enter a preamble, having it as a separate property leaves that option open.

Creating a top menu

With our two page types and their templates we have what’s needed to create a simple site in terms of entering content. There is however something missing for the site to be usable. Visitors need to be able to navigate on the site. To address that we need to create a couple of navigation components. However, to be able to see these components in action in a meaningful way we need some more content on our site. So, let’s create ourselves some pages on multiple levels. Create another page below the start page named “Products” and create a number of pages below the about-page that we created in the previous section. Make them all using the StandardPage page type. The image below shows how the page tree should look.

The page tree after creating some content that could be considered realistic on a corporate website.

The page tree after creating some content that could be considered realistic on a corporate website.

With some content on our site, let’s create ourselves a first navigation component. Most sites tend to have a top navigation. On an EPiServer site, where the page tree is often used to structure the site, it’s convenient to build it so that it’s made up of the pages located directly below the start page. We’ll create a top menu that works in that way in the form of a HTML helper, an extension method for instances of ASP.NET MVC’s HtmlHelper class.

Begin by creating a new folder in the projects root level named “Helpers”. Inside the folder add a new C# class named “NavigationHelpers”. Make the class static, as that’s a requirement for being able to create extension methods in it.

namespaceFruitCorp.Web.Helpers

{

publicstaticclassNavigationHelpers

{

}

}

Next, add a static method named “RenderMainNavigation” with no return value (void). The method should have a parameter of typeHtmlHelper (in the System.Web.Mvc namespace), prefixed with the this keyword, so that the method can be used as an extension method forHtmlHelper objects.

usingSystem.Web.Mvc;

namespaceFruitCorp.Web.Helpers

{

publicstaticclassNavigationHelpers

{

publicstaticvoid RenderMainNavigation(this HtmlHelper html)

{

}

}

}

Before we implement the method, let’s use it in our layout so that we can look at the results once we do start implementing it. Open up the layout file (Views/Shared/_Root.cshtml) and invoke the method just before the call to the RenderBody method (line 15 below). Note that as our method doesn’t return anything, but instead will write directly to the response when invoked, we need to wrap the call to it in curly braces and terminate the statement with a semicolon. Also, as the method is located in one of our own custom namespaces we need to add a using statement (line one below).

1 @using FruitCorp.Web

2 <!DOCTYPE html>

3

4 <html>

5 <head>

6 <meta name="viewport" content="width=device-width" />

7 <title>_Root</title>

8 <link rel="stylesheet" href="~/Content/bootstrap/bootstrap.min.css" />

9 </head>

10 <body>

11 <div class="container">

12 <div class="row">

13 <div class="col-md-2"><h3 class="muted">AcmeFruit</h3></div>

14 </div>

15 @{ Html.RenderMainNavigation(); }

16 @RenderBody()

17 </div>

18 </body>

19 </html>

tip

Instead of placing using statements for required namespaces in views, like on the first line in the code above, you can add namespaces that should be automatically imported for all views to the configuration/system.web.webPages.razor/pages/namespaces node in the web.config file located in the Views folder. Below is an example (with the parts of the web.config file not relevant for this discussion omitted) :

<?xml version="1.0"?>

<configuration>

<system.web.webPages.razor>

<pages pageBaseType="System.Web.Mvc.WebViewPage">

<namespaces>

<add namespace="FruitCorp.Web.Helpers"/>

</namespaces>

</pages>

</system.web.webPages.razor>

</configuration>

Here in the book I’m not using this approach in order to make it clearer what methods and classes are used in views. However, in a real project I’d recommend adding the namespaces this way, otherwise the views tend to be bloated with a lot of using statements.

Now, let’s create a first simple implementation of the RenderMainNavigation method. The navigation will be an unordered list (UL+LI tags) with Bootstrap’s CSS classes “nav” and “navbar-nav” wrapped in a NAV element with “navbar” and “navbar-inverse” CSS classes. So, as a first step we write those elements to the response.

publicstaticvoid RenderMainNavigation(this HtmlHelper html)

{

var writer = html.ViewContext.Writer;

//Top level elements

writer.WriteLine("<nav class=\"navbar navbar-inverse\">");

writer.WriteLine("<ul class=\"nav navbar-nav\">");

//Close top level elements

writer.WriteLine("</ul>");

writer.WriteLine("</nav>");

}

discussion

Return MvcHtmlString or write to the response?

Most HTML helper methods in ASP.NET MVC return a MvcHtmlString object whose string value is to the view. Some methods come in pairs where one returns a MvcHtmlString and one writes directly to the response. The latter have the same names as the former but prefixed by “Render”, such as Action and RenderAction.

Methods that return a MvcHtmlString are slightly more convenient to use in views as the syntax is shorter, while those that write directly to the response may in theory be faster as they don’t have to build up a string. We can use either approach when creating custom HTML helper extension. Here in the book we use the “Render” approach for long methods as that produces more concise code suitable for reading.

Depending on the requirements from site to site it may be that the start page should be included in the top menu. At least for now, when we aren’t offering any other way to get to the start page from other pages, we want this behavior for our site. So, before we list any other pages we begin by writing a link to the start page, inside a LI tag, to the response.

In order to create a link to a page, complete with an A HTML tag with the href attribute set to the page’s URL and the PageName property as text, we can use one of EPiServer’s HTML helper methods, the PageLink method found in EPiServer.Web.Mvc.Html.StructureHtmlHelperExtensions. In its simplest form the PageLink method takes a single parameter, a PageReference representing the page to create a link to. To get a hold of a reference to the start page we can use the static StartPage property of the ContentReference class.

The PageLink method returns a MvcHtmlString, an object that wraps a string that has been HTML-encoded and therefore is safe to write to the response. In order to write the underlying string from that to the response we can use its ToHtmlString method.

Using the PageLink method to create link to the start page wrapped in a LI tag, the part of the method between writing the opening and closing tags for the UL element should look like this:

//Assuming using statements for EPiServer.Core and

// EPiServer.Web.Mvc.Html has been added to the class.

...

writer.WriteLine("<li>");

writer.WriteLine(html.PageLink(ContentReference.StartPage).ToHtmlString());

writer.WriteLine("</li>");

...

Next we need to write links to the pages located directly below the start page. As we learned in chapter four, we can get a hold of the children of a given page, or other type of content, using the GetChildren method defined in the IContentLoader interface. So, as a first step we useServiceLocator.Current to fetch an implementation of IContentLoader. Then we call the content loaders GetChildren method with ContentReference.StartPage as argument. We use PageData as type parameter as we’re interested in all pages but no other type of content.

//Assuming a using statement for EPiServer has been added to the class.

...

var contentLoader = ServiceLocator.Current

.GetInstance<IContentLoader>();

var topLevelPages = contentLoader

.GetChildren<PageData>(ContentReference.StartPage);

...

Next we iterate over the start page’s children, writing a LI element with a link for each page, using the same approach as we used when we added wrote the link to the start page.

...

foreach (var topLevelPage in topLevelPages)

{

writer.WriteLine("<li>");

writer.WriteLine(html.PageLink(topLevelPage).ToHtmlString());

writer.WriteLine("</li>");

}

...

That’s it! While we have yet to filter out pages that shouldn’t be visible in the navigation or highlight the link to the page in the currently viewed branch of the page tree, we now have an otherwise working method for our top menu. The complete class looks like this:

usingEPiServer;

usingEPiServer.Core;

usingEPiServer.ServiceLocation;

usingEPiServer.Web.Mvc.Html;

usingSystem.Web.Mvc;

namespaceFruitCorp.Web.Helpers

{

publicstaticclassNavigationHelpers

{

publicstaticvoid RenderMainNavigation(this HtmlHelper html)

{

var writer = html.ViewContext.Writer;

//Top level elements

writer.WriteLine("<nav class=\"navbar navbar-inverse\">");

writer.WriteLine("<ul class=\"nav navbar-nav\">");

//Link to the start page

writer.WriteLine("<li>");

writer.WriteLine(html.PageLink(ContentReference.StartPage).ToHtmlString());

writer.WriteLine("</li>");

//Link to the start pages children

var contentLoader = ServiceLocator.Current

.GetInstance<IContentLoader>();

var topLevelPages = contentLoader

.GetChildren<PageData>(ContentReference.StartPage);

foreach (var topLevelPage in topLevelPages)

{

writer.WriteLine("<li>");

writer.WriteLine(html.PageLink(topLevelPage).ToHtmlString());

writer.WriteLine("</li>");

}

//Close top level elements

writer.WriteLine("</ul>");

writer.WriteLine("</nav>");

}

}

}

Compile the project and take a look at the site, which should now contain a top menu allowing us to navigate between the site’s start page its children.

discussion

HTML as strings in C#? Yuck!

The RenderMainNavigation method that we have just created writes HTML directly to the response in the form of C# strings. This makes it quite long and not very flexible with regards to what HTML it outputs. This is intentional here in the book as I want to keep the code as straight forward and simple, but not necessarily as terse, as possible.

In the ASP.NET MVC version of the Alloy sample site, which is available for download on EPiServer World, you’ll find an HTML helper extension method named MenuList (located in the EPiServer.Templates.Alloy.Helpers.HtmlHelpers class). This method can be used to create navigation components, ranging from top menus to tree structures to breadcrumbs with custom templates for links. However, the flexibility comes at a price; it can only be used with the Razor view engine and its signature and implementation is quite complex. For a real project you may want to take a look at and copy the code for this method. However, for the sake of learning in this book we’re keeping it simple, albeit verbose.

Highlighting the current branch

Our top menu works as it should, linking to the start page and its children, but it doesn’t provide any visual indication of where on the site a visitor is. Therefore our next step is to add a CSS class, “active” from Boostrap, to a link if it links to the currently viewed page. In order to do that we need to figure out what page the current request to the site is for. In many situations EPiServer provides an easy way to do that, for instance through model binding in action methods.

However, in an HTML helper extension, which is just a regular C# method, there’s no such help. Thankfully our method takes a HtmlHelper as parameter through which we can access the current request context (System.Web.Routing.RequestContext) and EPiServer’s API provides an extension method for that, named GetContentLink in EPiServer.Web.Routing.RequestContextExtension, for retrieving a reference to the content item that the request routes to.

Using these components we can retrieve a reference to the current content in our method, like this:

//Assuming using statement for using EPiServer.Web.Routing.

var contentLink = html.ViewContext.RequestContext

.GetContentLink();

Now we can replace both places where we write an opening LI tag for a link (currently looking like this: writer.WriteLine("<li>");) with an if-else statements that adds the “active” CSS class if the link is for the currently viewed page. Here’s how the code for rendering the link to the start page looks after this modification:

if (ContentReference.StartPage.CompareToIgnoreWorkID(contentLink))

{

writer.WriteLine("<li class=\"active\">");

}

else

{

writer.WriteLine("<li>");

}

writer.WriteLine(html.PageLink(ContentReference.StartPage).ToHtmlString());

writer.WriteLine("</li>");

And here’s how the code for writing links to the start page’s children looks:

foreach (var topLevelPage in topLevelPages)

{

if (topLevelPage.ContentLink.CompareToIgnoreWorkID(contentLink))

{

writer.WriteLine("<li class=\"active\">");

}

else

{

writer.WriteLine("<li>");

}

writer.WriteLine(html.PageLink(topLevelPage).ToHtmlString());

writer.WriteLine("</li>");

}

Compile the project and take a look at the site. As expected the “active” CSS class is added to the link to the currently viewed page, manifesting it self as an open tab thanks to Bootstrap.

Looks good! There’s just one problem. Let’s look at what happens when we navigate to a page below one of the start page’s children:

In the screenshot above we’ve used the page tree to navigate to a page below the about-page. While we’re not on the about-page when viewing this page, we’re still in that part (or “section”, or “branch”) of the site and typically we want the about-page’s link in the top menu to be highlighted then as well. Note that on most sites this behavior is only implemented for the various sections of the site and not for the start page. After all, if we were to highlight the start page whenever a page below it is rendered it would most likely always be highlighted.

To fix the RenderMainNavigation method so that it not only highlights the currently viewed page but instead the current branch of the site’s hierarchy we can modify the condition for adding the “active” CSS class for the start page’s children so that it checks whether the currently viewed page is the linked to page OR if it’s located somewhere below it in the page tree. There are several ways to do that, but perhaps the easiest one is to use the GetAncestors method defined in the IContentLoader interface.

The GetAncestors method returns an IEnumerable<IContent> but we’re only interested in the references. So, we invoke the method and use LINQ’s Select method to project the list of IContent objects to a list of ContentReference objects.

//Assuming a using statement for System.Linq

//has been added to the class.

var currentBranch = contentLoader.GetAncestors(contentLink)

.Select(x => x.ContentLink);

The GetAncestors method return all content items up to, and including, the root page in the content tree for the given content reference. It does not however include the content item referenced by the argument it self. So, to make it easy for ourselves when we want to check if a link should be highlighted, we modify the above code to convert it to a list and then add the reference to the current page to it.

var currentBranch = contentLoader.GetAncestors(contentLink)

.Select(x => x.ContentLink)

.ToList();

currentBranch.Add(contentLink);

Using the list of references between the current page and the root page, including the current page, we can check if a given child of the start page should be highlighted by checking if a reference to it exists in the list. It’s tempting to use the List class’ Contains method to do that. However theContains method uses the Equals method to compare objects and the Equals method in the ContentReference class returns false if the compared references are for different versions, even if they otherwise are for the same content item. In other words, using the Contains method with collections of content references produces short and nice looking code that most likely will work in most situations, but unless we for some reason care about specific version, we probably should avoid it. Instead we can use LINQ’s Any method to check if the list contains the linked to page by giving it a delegate that uses the CompareToIgnoreWorkId method in the ContentReference class, like this:

if (currentBranch.Any(x =>

x.CompareToIgnoreWorkID(topLevelPage.ContentLink)))

{

writer.WriteLine("<li class=\"active\">");

}

With our new condition for highlighting links we get the desired behavior, a visual indication of where we are on the site in the form of the currently viewed branch of the site being highlighted in the top menu.

The full RenderMainNavigation method now looks like this:

publicstaticvoid RenderMainNavigation(this HtmlHelper html)

{

var contentLink = html.ViewContext.RequestContext

.GetContentLink();

var writer = html.ViewContext.Writer;

//Top level elements

writer.WriteLine("<nav class=\"navbar navbar-inverse\">");

writer.WriteLine("<ul class=\"nav navbar-nav\">");

//Link to the start page

if (ContentReference.StartPage.CompareToIgnoreWorkID(contentLink))

{

writer.WriteLine("<li class=\"active\">");

}

else

{

writer.WriteLine("<li>");

}

writer.WriteLine(html.PageLink(ContentReference.StartPage).ToHtmlString());

writer.WriteLine("</li>");

//Link to the start pages children

var contentLoader = ServiceLocator.Current

.GetInstance<IContentLoader>();

var topLevelPages = contentLoader

.GetChildren<PageData>(ContentReference.StartPage);

var currentBranch = contentLoader.GetAncestors(contentLink)

.Select(x => x.ContentLink)

.ToList();

currentBranch.Add(contentLink);

foreach (var topLevelPage in topLevelPages)

{

if (currentBranch.Any(x =>

x.CompareToIgnoreWorkID(topLevelPage.ContentLink)))

{

writer.WriteLine("<li class=\"active\">");

}

else

{

writer.WriteLine("<li>");

}

writer.WriteLine(html.PageLink(topLevelPage).ToHtmlString());

writer.WriteLine("</li>");

}

//Close top level elements

writer.WriteLine("</ul>");

writer.WriteLine("</nav>");

}

Making the method more flexible and testable

The RenderMainNavigation method is almost feature complete, except for one important aspect; we need to filter out pages that shouldn’t be linked to. We’ll get to that in the next section but before that there’s one other thing we may want to do. The method is currently hard coded to use the start page as the root page of the navigation and to retrieve the currently viewed page from the request context.

While this works the way we currently want on the site we can make it more flexible by creating optional parameters for those values. If the parameters aren’t null we use the supplied values, if not we use the same values as before. For the reference to the currently viewed page this would mean changing the method’s signature and implementation like this:

publicstaticvoid RenderMainNavigation(

this HtmlHelper html,

ContentReference contentLink = null)

{

contentLink = contentLink ??

html.ViewContext.RequestContext.GetContentLink();

//Rest of the method

}

tip

When modifying the signature of a method used in a view you may see an exception when accessing the site saying that the method with the old signature doesn’t exist. Such as “Method not found: ‘Void FruitCorp.Web.Helpers.NavigationHelpers.RenderMainNavigation(System.Web.Mvc.HtmlHelper)’.”. If you run in to this problem make a minor change to the view, such as adding a whitespace somewhere, and save it.

Also, retrieving the IContentLoader instance from the ServiceLocator is convenient as views that use the method doesn’t have to supply it as an argument. However, it makes the method hard to write unit tests for. We can fix that to by making it an optional parameter.

Finally, we can make users of the method decide whether it should render a method to the start page, or rather to the root page as we make that a parameter, by adding a boolean parameter for that. With all of these changes the method looks like this:

publicstaticvoid RenderMainNavigation(

this HtmlHelper html,

PageReference rootLink = null,

ContentReference contentLink = null,

bool includeRoot = true,

IContentLoader contentLoader = null)

{

contentLink = contentLink ??

html.ViewContext.RequestContext.GetContentLink();

rootLink = rootLink ??

ContentReference.StartPage;

var writer = html.ViewContext.Writer;

//Top level elements

writer.WriteLine("<nav class=\"navbar navbar-inverse\">");

writer.WriteLine("<ul class=\"nav navbar-nav\">");

if (includeRoot)

{

//Link to the root page

if (rootLink.CompareToIgnoreWorkID(contentLink))

{

writer.WriteLine("<li class=\"active\">");

}

else

{

writer.WriteLine("<li>");

}

writer.WriteLine(

html.PageLink(rootLink).ToHtmlString());

writer.WriteLine("</li>");

}

//Link to the root pages children

contentLoader = contentLoader ??

ServiceLocator.Current.GetInstance<IContentLoader>();

var topLevelPages = contentLoader

.GetChildren<PageData>(rootLink);

var currentBranch = contentLoader.GetAncestors(contentLink)

.Select(x => x.ContentLink)

.ToList();

currentBranch.Add(contentLink);

foreach (var topLevelPage in topLevelPages)

{

if (currentBranch.Any(x =>

x.CompareToIgnoreWorkID(topLevelPage.ContentLink)))

{

writer.WriteLine("<li class=\"active\">");

}

else

{

writer.WriteLine("<li>");

}

writer.WriteLine(html.PageLink(topLevelPage).ToHtmlString());

writer.WriteLine("</li>");

}

//Close top level elements

writer.WriteLine("</ul>");

writer.WriteLine("</nav>");

}

warning

Not yet ready for production

While the RenderMainNavigation method looks to be working perfectly on a site with a few published pages it’s crucial to note that it’s not yet ready for production. We need to filter out pages that shouldn’t be shown in listings and navigations before the method can be considered complete. We’ll cover that in the next section.

Web Forms note

When building a site with Web Forms EPiServer ships with a number of server controls that, using templates for items etc, make it easy to build navigations based on the page tree. In order to create a top menu like the one we’re building here the MenuList control (found in EPiServer.Web.WebControls) is a perfect fit.

In order to create a top menu that lists the children of the start page, including the filtering that we’ll look at in the next section, we can use code like this:

<nav class="navbar navbar-inverse">

<ul class="nav navbar-nav">

<li class="<%= CurrentPage.ContentLink.CompareToIgnoreWorkID(ContentReference.Start\

Page) ? "active" : "" %>">

<%-- The below databinding expression requires a call to DataBind

in the code behind file's OnLoad method. --%>

<EPiServer:Property PageLink="<%# ContentReference.StartPage %>"

PropertyName="PageLink" runat="server" />

</li>

<EPiServer:MenuList PageLink="<%# PageReference.StartPage %>" runat="server">

<ItemTemplate>

<li>

<EPiServer:Property PropertyName="PageLink" runat="server" />

</li>

</ItemTemplate>

<SelectedTemplate>

<li class="active">

<EPiServer:Property PropertyName="PageLink" runat="server" />

</li>

</SelectedTemplate>

</EPiServer:MenuList>

</ul>

</nav>

Of course, while the MenuList control makes it easy to build a top menu we’re not forced to use it but can instead take a more hands-on programmatic approach should we want to.

Filtering content in listings and menus

When programmatically listing pages and other types of content on an EPiServer site there are a number of important things to evaluate before showing content from, or linking to, a specific page.

· Is the page published?

· Does the current user, or an anonymous user for public visitors, have access to it?

· Does the page’s type have a template that can be used?

· If the listing is what an editor would consider navigation, should the page be shown in navigations?

The GetChildren method that we use in the RenderMainNavigation method filters out pages that aren’t published but it returns pages that doesn’t meet the other criteria. Also, there are other methods in the API, including an overload of the GetChildren method, that may not filter by publication status either. In other words, it’s usually a good idea to explicitly ensure that pages shown in listings and menus meet the above conditions. Luckily EPiServer’s API provides a number of classes that make filtering on several, or all, of the conditions fairly easy. Those classes are located in the EPiServer.Filters namespace. These classes include:

· FilterAccess – Used to exclude content for which the current user doesn’t have sufficient access rights. The required access rights can be specified using one of the class’ constructors. If the parameterless constructor is used it will require read access rights.

· FilterPublished – Used to exclude content that doesn’t have a required publication status specified in its constructor. Using its parameterless constructor pages need to be published in order not to be excluded.

· FilterTemplate – Used to exclude content that doesn’t have a page template, meaning a template that can render the content as a full-scale page (as opposed to as part of another page).

· FilterContentForVisitor – Used in order to conveniently apply all of the three filters above.

All of the above classes implement an interface named IContentFilter, meaning that it’s possible to write methods that filters content but leaves it up to the caller of the method to supply the specific filter implementation as an argument. While each of the methods above have their own additional methods they have the methods declared in the IContentFilter interface in common. Two of these methods sum up the two ways to use the filters nicely:

· void Filter(List<IContent> contents) – Modifies a list of IContent, excluding those items that don’t meet the required criteria.

· bool ShouldFilter(IContent content) – Returns true if a specific content should be excluded as it doesn’t meet the criteria of the filter.

All of the filters we’ve looked at so far have instance methods for filtering, meaning that we need to instantiate them in order to use them. In addition to those classes the EPiServer.Filters namespace also contains a class named FilterForVisitor that has static methods for filtering a collection of content the same way as the FilterContentForVisitor class. The most commonly used of those methods have the following syntax:

IEnumerable<IContent> Filter(IEnumerable<IContent> contentItems)

While the filter classes that require instantiation can be used for greater flexibility and testability the FilterForVisitor class is often very convenient. Which one you use is up to you and can differ from method to method. However, no matter which approach we use it’s important to remember to always filter out content that shouldn’t show up in listings. This may seem obvious but please trust me when I say that its easy to forget, and bugs related to lack of filtering tend to show up late in projects. Sometimes after release, when editors start entering realistic content, working with access rights and scheduling content for publication.

tip

Favor one call to FilterForVisitor.Filter(…) too many over one call to few

It bears repeating - always remember to filter content for public display in listings and menus! While there are several ways of filtering content and multiple ways of accomplishing the same goal, use the EPiServer.Filters.FilterForVisitor.Filter(...) when in doubt and you will at least never see embarrassing bug reports related to unpublished pages or content with strict access rights settings showing up in your listings.

As we’ve just seen, we can use the FilterForVisitor class to filter the start page’s children in our top menu in order to exclude pages that aren’t published, doesn’t have a template or for which the current user doesn’t have sufficient access rights. There’s however one more thing that we need to take care of. All pages have a property named VisibleInMenu inherited from the PageData class. This property corresponds to the “Display in Navigation” setting in forms editing mode. A page for which this property is false, meaning that the setting isn’t checked in edit mode, is visible on the site and should typically show up in listings (such as “latest news”) but not show up in navigation components such as our top menu.

There’s no filter for the VisibleInMenu property but it’s easy to exclude pages for which it’s false using LINQ’s Where method. Using that, along with the FilterForVisitor class we can modify the RenderMainNavigation method to only show pages that should actually be shown in the top menu like this:

1 //Assuming a using statement for EPiServer.Filters

2 //has been added to the class.

3 var topLevelPages = contentLoader

4 .GetChildren<PageData>(rootLink);

5 topLevelPages = FilterForVisitor

6 .Filter(topLevelPages)

7 .OfType<PageData>()

8 .Where(x => x.VisibleInMenu);

Note the call to LINQ’s OfType<T> method on line seven. As the FilterForVisitor.Filter method returns a collection of IContent, even though we give it a collection of PageData objects, we need to convert the collection back to a collection of pages.

This completes the RenderMainNavigation method. We now have a robust and fully functional top menu on our site! Below is the full NavigationHelpers class.

usingEPiServer;

usingEPiServer.Core;

usingEPiServer.Filters;

usingEPiServer.ServiceLocation;

usingEPiServer.Web.Mvc.Html;

usingEPiServer.Web.Routing;

usingSystem.Linq;

usingSystem.Web.Mvc;

namespaceFruitCorp.Web.Helpers

{

publicstaticclassNavigationHelpers

{

publicstaticvoid RenderMainNavigation(

this HtmlHelper html,

PageReference rootLink = null,

ContentReference contentLink = null,

bool includeRoot = true,

IContentLoader contentLoader = null)

{

contentLink = contentLink ??

html.ViewContext.RequestContext.GetContentLink();

rootLink = rootLink ??

ContentReference.StartPage;

var writer = html.ViewContext.Writer;

//Top level elements

writer.WriteLine("<nav class=\"navbar navbar-inverse\">");

writer.WriteLine("<ul class=\"nav navbar-nav\">");

if (includeRoot)

{

//Link to the root page

if (rootLink.CompareToIgnoreWorkID(contentLink))

{

writer.WriteLine("<li class=\"active\">");

}

else

{

writer.WriteLine("<li>");

}

writer.WriteLine(

html.PageLink(rootLink).ToHtmlString());

writer.WriteLine("</li>");

}

//Retrieve and filter the root pages children

contentLoader = contentLoader ??

ServiceLocator.Current.GetInstance<IContentLoader>();

var topLevelPages = contentLoader

.GetChildren<PageData>(rootLink);

topLevelPages = FilterForVisitor.Filter(topLevelPages)

.OfType<PageData>()

.Where(x => x.VisibleInMenu);

//Retrieve the "path" from the current page up to the

//root page in the content tree in order to check if

//a link should be highlighted.

var currentBranch = contentLoader.GetAncestors(contentLink)

.Select(x => x.ContentLink)

.ToList();

currentBranch.Add(contentLink);

//Link to the root pages children

foreach (var topLevelPage in topLevelPages)

{

if (currentBranch.Any(x =>

x.CompareToIgnoreWorkID(topLevelPage.ContentLink)))

{

writer.WriteLine("<li class=\"active\">");

}

else

{

writer.WriteLine("<li>");

}

writer.WriteLine(html.PageLink(topLevelPage).ToHtmlString());

writer.WriteLine("</li>");

}

//Close top level element

writer.WriteLine("</ul>");

writer.WriteLine("</nav>");

}

}

}

Web Forms note

The built in server controls for creating menus and listings, such as MenuList, takes care of filtering for us. Of course, if we’re writing code that accesses EPiServer’s API directly we need to apply access rights and publication status filtering ourselves no matter if we’re using MVC or Web Forms.

Building a sub navigation

Following the previous sections we now offer visitors to our site a way to navigate between the pages located directly below the start page. There is however no way for them to navigate further down the site’s structure. While there are several design/navigation patterns for solving this problem the dominating one on EPiServer sites is, or at least has been in the past, to use a vertical sidebar navigation.

This navigation component is typically found on all pages below the start page and lists the children of the current section. Meaning the children of the page directly below the start page in the page tree in the branch that the current page is in. It usually also lists the children for each page that is a part of the current page’s branch. This type of navigation ensures that users can always navigate downwards in the site’s structure, go upwards in the structure as well as see where the current page resides on the site.

Hardly surprising, building such a menu involves using EPiServer’s API in order to traverse the content tree, using the current page and the start page as input. Pseudo code for a method that renders a sub menu like as discussed here may look like this:

1. Create a collection with the pages between the current page and the start page, including the current page but not the start page.

2. Use the page closest to the start page as the “root” page.

3. Retrieve and filter the root page’s children.

4. For each child:
4.1 Write a link to the page.
4.2 If the page is the current page mark it as highlighted.
4.3 If the page is in the collection created in #1, recursively invoke #3 with the page as the root page.

Let’s implement a method that does this in our NavigationHelpers class. We’ll call it “RenderSubNavigation”. This time around we’ll begin by looking at the complete code for it, including a private method in the same class that it delegates to, and then look at the interesting parts in detail.

1 publicstaticvoid RenderSubNavigation(

2 this HtmlHelper html,

3 ContentReference contentLink = null,

4 IContentLoader contentLoader = null)

5 {

6 contentLink = contentLink ??

7 html.ViewContext.RequestContext.GetContentLink();

8 contentLoader = contentLoader ??

9 ServiceLocator.Current.GetInstance<IContentLoader>();

10

11 //Find all pages between the current and the

12 //start page, in top-down order.

13 var path = contentLoader.GetAncestors(contentLink)

14 .Reverse()

15 .SkipWhile(x =>

16 ContentReference.IsNullOrEmpty(x.ParentLink)

17 || !x.ParentLink.CompareToIgnoreWorkID(ContentReference.StartPage))

18 .OfType<PageData>()

19 .Select(x => x.PageLink)

20 .ToList();

21

22 //In theory the current content may not be a page.

23 //We check that and, if it is, add it to the end of

24 //the content tree path.

25 var currentPage = contentLoader

26 .Get<IContent>(contentLink) as PageData;

27 if (currentPage != null)

28 {

29 path.Add(currentPage.PageLink);

30 }

31

32 var root = path.FirstOrDefault();

33 if (root == null)

34 {

35 //We're not on a page below the start page,

36 //meaning that there's nothing to render.

37 return;

38 }

39

40 RenderSubNavigationLevel(

41 html,

42 root,

43 path,

44 contentLoader);

45 }

46

47 privatestaticvoid RenderSubNavigationLevel(

48 HtmlHelper helper,

49 ContentReference levelRootLink,

50 IEnumerable<ContentReference> path,

51 IContentLoader contentLoader)

52 {

53 //Retrieve and filter the pages on the current level

54 var children = contentLoader.GetChildren<PageData>(levelRootLink);

55 children = FilterForVisitor.Filter(children)

56 .OfType<PageData>()

57 .Where(x => x.VisibleInMenu);

58

59 if (!children.Any())

60 {

61 //There's nothing to render on this level so we abort

62 //in order not to write an empty ul element.

63 return;

64 }

65

66 var writer = helper.ViewContext.Writer;

67

68 //Open list element for the current level

69 writer.WriteLine("<ul class=\"nav\">");

70

71 //Project to an anonymous class in order to know

72 //the index of each page in the collection when

73 //iterating over it.

74 var indexedChildren = children

75 .Select((page, index) => new {index, page})

76 .ToList();

77

78 foreach (var levelItem in indexedChildren)

79 {

80 var page = levelItem.page;

81 var partOfCurrentBranch = path.Any(x =>

82 x.CompareToIgnoreWorkID(levelItem.page.ContentLink));

83

84 if (partOfCurrentBranch)

85 {

86 //We highlight pages that are part of the current branch,

87 //including the currently viewed page.

88 writer.WriteLine("<li class=\"active\">");

89 }

90 else

91 {

92 writer.WriteLine("<li>");

93 }

94 writer.WriteLine(helper.PageLink(page).ToHtmlString());

95

96 if (partOfCurrentBranch)

97 {

98 //The page is part of the current pages branch,

99 //so we render a level below it

100 RenderSubNavigationLevel(

101 helper,

102 page.ContentLink,

103 path,

104 contentLoader);

105 }

106 writer.WriteLine("</li>");

107 }

108

109 //Close list element

110 writer.WriteLine("</ul>");

111 }

discussion

Don’t be put off by the size of the code above. As with the top menu there are other ways to create menus that doesn’t rely on writing HTML directly to the writer and/or that builds up an object graph that makes the code easier to read. For instance, we could let our method render a partial view that in turn invokes the method again after writing the HTML code, or we could use use on or more Razor helpers as arguments to the method, delegating the actual rendering to them. The latter is what the previously discussed MenuList method in the MVC version of the Alloy demo site does.

However, the above code works just fine and while it’s long it’s fairly straight forward and easy to change as per the requirements for the site. It also has the benefit of showing all of the interactions with EPiServer’s API in one place.

The RenderSubNavigation method’s signature (line 1-4) is similar to the RenderMainNavigation methods; we use optional parameters for a reference to the current content and for an IContentLoader. We then assign default values to them (line 6-9) if they haven’t been supplied by the caller.

On lines 13-30 we figure out the path from the start page to the current page. The values in the collection correspond to the pages that we would have expanded in the page tree in edit mode in order to get to the current page, plus the current page.

On lines 32-38 we figure out the root page for the navigation, meaning the currently selected page in the top navigation. Thanks to the work we do on the previous lines we can easily do that by retrieving the first item from the page tree path that we’ve already calculated.

We’ve now figured out all of the context needed to recursively render the navigation. In order to do that we invoke our second method, RenderSubNavigationLevel. This method corresponds to #3 and #4 in the pseudo code. It receives a single root page, retrieves that page’s children, renders links to them and, if a page is part of the current pages branch, invokes it self with that page as the new root page. The links are rendered in an UL/LI list and when the method recursively invokes it self it does that before having closed the LI element for the current page in the iteration. The effect is that each level of links is nested in the parent levels list making it easy for us to visually show the hierarchy using CSS styling.

Version three of Bootstrap doesn’t contain such styling however, meaning that we’ll need to add a custom style sheet. Therefore we right click on the Content folder in Visual Studio’s Solution Explorer and select AddNew item…. Then we select to add a Style Sheet which we name “custom.css”. In order to use it we include it in the HEAD part of our layout file, Root.cshtml.

Adding the custom style sheet to _Root.cshtml.


...

<head>

<meta name="viewport" content="width=device-width" />

<link href="~/Content/bootstrap/bootstrap.min.css" rel="stylesheet">

<link href="~/Content/custom.css" rel="stylesheet">

<title>_Root</title>

</head>

...


Once we’ll use the RenderSubNavigation method in a view we’ll wrap it in an element with a CSS class named “sub-navigation” to identify it. So, in order to visually show the page hierarchy and where the visitor is we can add the following to our new style sheet:

Contents of custom.css.


.sub-navigation {

background-color: #F5F5F5;

border-radius: 4px;

}

.sub-navigationul {

padding: 10px 0;

}

.sub-navigationulli.active > a {

font-weight: bold;

}

.sub-navigationulul {

padding: 0 10px 0 10px;

}

.sub-navigationulula {

padding: 5px 25px;

font-size: 90%;

}


Creating a layout for pages with sub navigation

With the RenderSubNavigation method completed we could simply add it to the view for our standard page page type. However, we’ll be creating other page types later whose views will also need it. Therefore we create a new layout that extends our existing layout.

We begin by creating a new layout in the form of a view in the the /Views/Shared folder that we name _WithSubNavigation. As this layout will extend our existing one we make sure that the “Use a layout or master page” option is selected and enter the path to the existing layout, ~/Views/Shared/_Root.cshtml.

As a first step we clean it up a bit by removing some of the things that Visual Studio put in the layout for us, the H2 tag and assignment to ViewBag.Title. Next we copy all of the HTML markup from the standard page view, the Index view for the StandardPageController, and paste it into the new layout. Then we remove the code that is specific to the standard page, the rendering of the three properties, and instead insert a call to the RenderBody method. This leaves the new layout looking like this:

_WithSubNavigation.cshtml


@{

Layout = "~/Views/Shared/_Root.cshtml";

}

<div class="row">

<div class="col-md-3">

</div>

<div class="col-md-6">

@RenderBody()

</div>

<div class="col-md-3"></div>

</div>


With the markup for the grid moved to our new layout file we clean up the standard page view leaving only the rendering of the properties in terms of markup. Also, as we foresee that our new layout will be the most commonly used layout in the views on our site we also remove the assignment to the Layout property and, as we’ll soon see, accomplish that in another way. The standard page view now looks like this:

/Views/StandardPage/Index.cshtml after extracting the grid to the new layout.


@model FruitCorp.Web.Models.Pages.StandardPage

<h1>@Html.PropertyFor(x => x.PageName)</h1>

<p class="lead">@Html.PropertyFor(x => x.MainIntro)</p>

@Html.PropertyFor(x => x.MainBody)


In order to keep all views on a site from having to explicitly specify what layout they use the Razor view engine provides a mechanism with which common view code, such as specifying the layout, can be placed in a file that is executed at the start of each view. This file must be called “_ViewStart.cshtml” and be placed in the sites Views folder.

So, let’s create such a file. Right click on the views folder and add a new view. In the Add View dialog name it “_ViewStart” (the file extension is automatically added) and set the path to the layout to our new layout, ~/Views/Shared/_WithSubNavigation.cshtml. Again, Visual Studio adds some things that we don’t want or need. After cleaning it up the _ViewStart file looks like this:

Content of _ViewStart.cshtml


@{

Layout = "~/Views/Shared/_WithSubNavigation.cshtml";

}


Now the Index view for standard pages will use the _WithSubNavigation layout while the Index view for the start page will continue to use the _Root view as it explicitly sets its Layout property to that. Going back to the _WithSubNavigation view we can now use our sub navigation method. First we need to add a using statement for the namespace that the NavigationHelpers class is in. Then, inside the left column, the first DIV tag with the col-md-3 CSS class, we invoke our RenderSubNavigation method. In order to apply our custom CSS to it we wrap it in a DIV with a “sub-navigation” class.

Using the RenderSubNavigation method in _WithSubNavigation.cshtml.


@using FruitCorp.Web.Helpers

...

<div class="col-md-3">

<div class="sub-navigation">

@{ Html.RenderSubNavigation();}

</div>

</div>


Compile the project and take a look at the site. We now have a working, multi-level left navigation, which together with the top menu, allow visitors to browse to all pages on the site.

The sub navigation on different levels of the site.

The sub navigation on different levels of the site.

The full code for the _WithSubNavigation layout looks like this:

@using FruitCorp.Web.Helpers

@{

Layout = "~/Views/Shared/_Root.cshtml";

}

<div class="row">

<div class="col-md-3">

<div class="sub-navigation">

@{ Html.RenderSubNavigation();}

</div>

</div>

<div class="col-md-6">

@RenderBody()

</div>

<div class="col-md-3"></div>

</div>

Web Forms note

As with the top menu EPiServer provides a server control that can be used to create complex menus such as the one we’ve just created; the PageTree control located in the EPiServer.Web.WebControls namespace. The PageTree control is very powerful in the sense that it abstracts away all of the logic needed to create a hierarchical sub navigation (or a site map) and instead allows us to provide templates for different types of items that are rendered. However, this comes at a price. The control has a lot of different templates and building a sub navigation with it from scratch can be rather confusing unless one understands what each template does.

We won’t cover the various templates here as there are a couple of resources online that does that well, namely the Navigation Menus and Listings part of EPiServer’s developer guide and a blog post by Frederik Vig.

Using the PageTree control to create a sub navigation like the one we’ve just created for MVC looks like this:

<EPiServer:PageTree

PageLink="<%#MenuRoot %>"

Visible="<%# MenuRootHasChildren %>" runat="server">

<HeaderTemplate>

<div class="sub-navigation">

<ul class="nav">

</HeaderTemplate>

<FooterTemplate>

</ul>

</div>

</FooterTemplate>

<ItemTemplate>

<li>

<EPiServer:Property PropertyName="PageLink" runat="server" />

</ItemTemplate>

<SelectedItemTemplate>

<li class="active">

<EPiServer:Property PropertyName="PageLink" runat="server" />

</SelectedItemTemplate>

<ExpandedItemTemplate>

<li class="active">

<EPiServer:Property PropertyName="PageLink" runat="server" />

</ExpandedItemTemplate>

<ItemFooterTemplate>

</li>

</ItemFooterTemplate>

<IndentTemplate>

<ul class="nav">

</IndentTemplate>

<UnindentTemplate>

</ul>

</UnindentTemplate>

</EPiServer:PageTree>

Note the assignment to the PageLink and Visible properties in the first lines above. These assume that there is a property named MenuRoot in the code behind file that returns a reference to the current branch root, or section, as well as one named MenuRootHasChildren that checks whether there is anything to show in the menu.

Adding a breadcrumb

Using the two navigation components that we’ve already created users are able to navigate to all pages on the site. Thanks to the highlighting of links to pages in the current branch they are also able to figure out where they are on the site. However, to make that more obvious and to aid search engines in understanding the site’s hierarchy we can also add a breadcrumb to the templates used for pages below the site’s start page.

Given what we’ve seen and done with the code for the top menu and the sub navigation, creating a breadcrumb doesn’t seem very hard. In fact we’ve already done all of the heavy lifting needed in the RenderSubNavigation method. So, we can extract1 the code for figuring out the path between the start page and the current page from that to a new method in the NavigationHelpers class named NavigationPath.

The NavigationPath method after extracting it from RenderSubNavigation.


privatestatic IEnumerable<PageReference> NavigationPath(

ContentReference contentLink,

IContentLoader contentLoader)

{

//Find all pages between the current and the

//"from" page, in top-down order.

var path = contentLoader.GetAncestors(contentLink)

.Reverse()

.SkipWhile(x =>

ContentReference.IsNullOrEmpty(x.ParentLink)

|| !x.ParentLink.CompareToIgnoreWorkID(ContentReference.StartPage))

.OfType<PageData>()

.Select(x => x.PageLink)

.ToList();

//In theory the current content may not be a page.

//We check that and, if it is, add it to the end of

//the content tree path.

var currentPage = contentLoader

.Get<IContent>(contentLink) as PageData;

if (currentPage != null)

{

path.Add(currentPage.PageLink);

}

return path;

}


The assignment to the path variable in RenderSubNavigaton method after extracting NavigationPath.


...

var path = NavigationPath(

contentLink,

contentLoader);

...


Using only the extracted code from RenderSubNavigation the new method is almost exactly what we need in order to get the pages that we’ll link to in the breadcrumb, save for two things. First of all, as we’ll be rendering links to the pages returned by it we’ll need to filter them for public display, which isn’t needed when we only use the method’s return value to check if a page is in the current branch in RenderSubNavigation. Second, the returned collection doesn’t include the start page which we’ll want to link to in the breadcrumb. So, in order to be able to use the same code for both RenderSubNavigation and the method that we’ll create for the breadcrumb we make the extracted method return a collection of PageData objects (by removing the projection to PageReference). We also make it a bit more flexible by adding a parameter to it, with which we can specify from below where the returned list of pages should start.

The NavigationPath method after making it’s return type IEnumerable<PageData> and adding a fromLink parameter.


privatestatic IEnumerable<PageData> NavigationPath(

ContentReference contentLink,

IContentLoader contentLoader,

ContentReference fromLink = null)

{

fromLink = fromLink ?? ContentReference.RootPage;

//Find all pages between the current and the

//"from" page, in top-down order.

var path = contentLoader.GetAncestors(contentLink)

.Reverse()

.SkipWhile(x =>

ContentReference.IsNullOrEmpty(x.ParentLink)

|| !x.ParentLink.CompareToIgnoreWorkID(fromLink))

.OfType<PageData>()

.ToList();

//In theory the current content may not be a page.

//We check that and, if it is, add it to the end of

//the content tree path.

var currentPage = contentLoader

.Get<IContent>(contentLink) as PageData;

if (currentPage != null)

{

path.Add(currentPage);

}

return path;

}


After these modifications we need to modify the RenderSubNavigation method to accommodate for the fact that the extracted method defaults to returning the start page as the first page in the return value (which will suit the breadcrumb well but not the sub navigation). We must also adjust it by adding a projection from a collection of PageData objects to a collection of PageReference objects.

Modification to the RenderSubNavigaton method, passing a reference to the start page.


...

var path = NavigationPath(

contentLink,

contentLoader,

ContentReference.StartPage)

.Select(x => x.PageLink);

...


With this refactoring done we can create ourselves a method that renders a breadcrumb, almost exclusively focused on rendering links to the pages returned by the NavigationPath method. We add the new method, named RenderBreadcrumb, to the NavigationHelpers class, with a familiar list of arguments and default values:

publicstaticvoid RenderBreadcrumb(

this HtmlHelper html,

ContentReference contentLink = null,

IContentLoader contentLoader = null)

{

contentLink = contentLink ??

html.ViewContext.RequestContext.GetContentLink();

contentLoader = contentLoader ??

ServiceLocator.Current.GetInstance<IContentLoader>();

}

Next, we implement the interesting part of the method. First we fetch the pages that should be linked to using the NavigationPath method and filter them to ensure that we’re not rendering a link to a page the visitor won’t be able to see. Then we write a link to each page as part of a list, using CSS classes for breadcrumbs from Bootstrap:

...

var pagePath = NavigationPath(contentLink, contentLoader);

var path = FilterForVisitor.Filter(pagePath)

.OfType<PageData>()

.Select(x => x.PageLink);

if (!path.Any())

{

//Nothing to render, no need to output an empty list.

return;

}

var writer = html.ViewContext.Writer;

writer.WriteLine("<ol class=\"breadcrumb\">");

foreach (var part in path)

{

if (part.CompareToIgnoreWorkID(contentLink))

{

writer.WriteLine("<li class=\"active\">");

//For the current page there's no point in outputting a link.

//Instead output just the (encoded) page name.

var currentPage = contentLoader.Get<PageData>(contentLink);

writer.WriteLine(html.Encode(currentPage.PageName));

}

else

{

writer.WriteLine("<li>");

writer.WriteLine(html.PageLink(part));

}

writer.WriteLine("</li>");

}

writer.WriteLine("</ol>");

...

As we’re already familiar with the NavigationPath method, which we use on the first line, the above code should be fairly straight forward. Looking at the code there is one interesting thing to think about though. We’re not filtering out pages that don’t have a template. So, what happens if a page is of a type for which there is no renderer?

Of course, given the content that we’ve created so far that won’t be the case, but in a more general scenario that may happen. Well, what will happen then is that the page will appear in the breadcrumb but not as a link. Instead it will be displayed with its name as plain text. That’s because thePageLink method checks whether there the referenced page (passed to it as an argument) is of a type that has a template. If it does it returns a link to it, otherwise it returns the page’s name wrapped in a SPAN tag.

Anyhow, with our RenderBreadcrumb method implemented we’re ready to put it to use. We add a call to it in our layout file for pages that have a sub navigation, _WithSubNavigation.cshtml, just before we invoke the RenderBody method.

Using RenderBreadCrumb in _WithSubNavigation.cshtml


...

<div class="col-md-6">

@{ Html.RenderBreadcrumb(); }

@RenderBody()

</div>

...


After compiling the project we can see that the breadcrumb appears on all pages except the start page, and indeed works as it should.

tip

Breadcrumbs and rich snippets

While off topic in this book the HTML markup for breadcrumbs can be enhanced with attributes using the microdata format. The additional markup tells search engines that the elements used to display the breadcrumb represents a breadcrumb. That information can be used by search engines to show better snippets (the lines of text that appear below the link to the page in a search results listing) for the page.

For more information about microdata see the schema.org getting started guide and Google’s explanation of rich snippets. Specifically for breadcrumbs, see Google’s Webmaster Tools documentation about that.

Web Forms note

The easiest way to build a breadcrumb when using Web Forms is to use the PageTree control and create templates for ExpandedItemTemplate and SelectedItemTemplate but not for ItemTemplate, meaning that only pages that are in the current branch of the page tree will have templates. Below is an example of how we could use the PageTree control to create a breadcrumb like the one we just built for MVC.

<%@ Import Namespace="EPiServer.Core" %>

<EPiServer:PageTree PageLink="<%# ContentReference.StartPage %>"

ShowRootPage="True" runat="server">

<HeaderTemplate>

<ol class="breadcrumb">

</HeaderTemplate>

<ExpandedItemTemplate>

<li>

<EPiServer:Property PropertyName="PageLink" runat="server" />

</li>

</ExpandedItemTemplate>

<SelectedItemTemplate>

<li class="active">

<EPiServer:Property PropertyName="PageName" runat="server" />

</li>

</SelectedItemTemplate>

<FooterTemplate>

</ol>

</FooterTemplate>

</EPiServer:PageTree>

Adding the Quick Navigator

With a couple of page types, templates and navigation components we’re well under way developing our site. Now, or perhaps even earlier, would be a good time to add a small but important detail; EPiServer’s Quick Navigator. The Quick Navigator is a component that show up when viewing the site in view mode (as in outside of edit mode) while logged in as an editor.

The component is absolute positioned on top of each page, near the top right corner, and allow editors (as well as us developers) to quickly navigate to edit mode for the viewed page. It also features a drop down menu that can be used to navigate to edit mode in general, without the specific page opened, as well as to Online Center’s dashboard.

As the Quick Navigator is shown on the public part of our site, meaning the part not controlled by EPiServer but by our templates, it’s up to us to add it. In order to do so we use a HTML helper extension from EPiServer called RenderEPiServerQuickNavigator. As a side note, the method’s name isn’t optimal. The method returns a MvcHtmlString instead of writing to the response, meaning that its name shouldn’t start with “Render…”.

Anyhow, we can use the method pretty much anywhere inside of either the HEAD or BODY tags in our views, although I would suggest in the HEAD tag as it outputs a style sheet link and some JavaScript. As it should be used on all our views we add a call to it in our “base” layout, _Root.cshtml.

Modification to _Root.cshtml


...

<head>

...

@Html.RenderEPiServerQuickNavigator()

</head>

...


With that done the rendered markup sent to the users browser for each of our pages on our site will contain an additional style sheet link and a couple of script tags that adds the Quick Navigator, if the user has access to edit mode but is not viewing the page inside of edit mode. For public visitors the method won’t output anything. After compiling the project and ensuring that we’re logged in as an editor we can verify that the Quick Navigator is indeed rendered by browsing a page in view mode.

Web Forms note

When using Web Forms the Quick Navigator is automatically added as long as our templates inherit from the TemplatePage base class.

Summary

This has been a long chapter and we’ve covered a lot of ground. We now have a simple but fully functional site. It doesn’t quite live up to good craftsmanship though, as we’ll need to enhance the user experience in edit mode and fix the rendering of the preamble on standard pages in edit mode so that it offers a realistic preview. Still, we’ve learned the gist of EPiServer site development:

· Defining content types

· Defining properties in content types

· Creating templates that renders content types

An important part of creating templates is building navigations and we’ve covered that, including the important topic of filtering content, in great detail. However, we haven’t spent much time on the details when it comes to content types and properties. Also, we’ve so far only seen the simplest way of rendering properties, using the PropertyFor method with a single argument. In the coming chapters we’ll explore those topics further.