Creating a Link to a Page in your EPiServer 7 MVC View
When developing an EPiServer 7 site (or really any site, for that matter), making a link to a page is one of the most common things you'll do. So, I thought I'd do something a little more basic and explore how to handle this task in your front-end page and block templates, specifically using MVC Razor views. In this post, we'll look at what you can currently use to create a link to a page, then I'll introduce some extension methods that you can use to give yourself a little more flexibility in your MVC view.
Current Options and Limitations
First of all, resist the temptation to solely rely on the LinkURL
property in the PageData
object. This will return an internal link to the page that isn't very user friendly. Sure, when you click the link it takes you to the correct page, but we don't want to be showing these links to the outside world. Instead, rely on the helpers and extensions that EPiServer 7 provides.
Html.PageLink
The simplest and most common way to create a link to page is to use EPiServer's Html.PageLink
helper. Using this helper, you can pass in a PageData
object, a PageReference
, or even a LinkItem
, and it will create an HTML anchor, using the clean, friendly URL to the page. For the text of the link, if you do not supply it with a text string, it will use the PageName
property for the page (or the Text
property for a LinkItem
object).
@Html.PageLink(Model.SomePageReference)
@Html.PageLink(Model.SomePageData)
@Html.PageLink("A link to a page", Model.SomeLinkItem)
You can also pass in route data information that can change the URL (helpful when working with language branches) or additional HTML attributes to add to the anchor tag.
@Html.PageLink("A link to a Swedish page", Model.SomePageReference, new { language = "sv-SE" }, null)
@Html.PageLink("A link with a class", Model.SomePageReference, null, new { @class = "link-class" })
Be sure to check the SDK for this helper, because there are a lot more options you could use that might be a better fit for your needs.
In most cases, this HTML helper is what you'll use to create a link to a page. There are some situations, however, where you'll need a little more flexibility. A common example is when you want to wrap an image tag with an anchor tag, or when you want to wrap more elements. To solve that situation, you could build an image link HTML helper, or you could just retrieve the actual URL to the page using Url.PageUrl
.
Url.PageUrl
Another way is to use EPiServer's Url.PageUrl
helper. This helper only takes one parameter: the internal URL for the page. It will then use EPiServer's PermanentLinkUtility
to find the friendly URL to the page, and return only the URL as a string. You could then take that string and use it directly in an HTML anchor tag.
<a href="@Url.PageUrl(Model.SomePageData.LinkURL)">
A link to a page
</a>
This covers a lot of the other cases when you need better flexibility when creating a link to a page. The only thing I don't like about this, though, is that you need to have the PageData
of the page to use it. So if you have a PageReference
property on your page or block type, you'll need to use IContentLoader
or IContentRepository
to get the PageData
from the PageReference
. Wouldn't it be easier to just pass in a PageData
or a PageReference
object instead?
Another Option: Extending the UrlHelper
The solution I put together, which I think provides the best of both worlds, is heavily based on the code for EPiServer's Html.PageLink
helper. It only returns the friendly URL to a page like Url.PageUrl
, but still gives you the flexibility to pass in a PageData
or a PageReference
object like Html.PageLink
. Rather than extending HtmlHelper
, it extends UrlHelper
and works alongside of the Url.PageUrl
helper.
UrlExtensions.cs
public static class UrlExtensions
{
public static string PageUrl(this UrlHelper urlHelper, PageReference pageLink)
{
return UrlExtensions.PageUrl(urlHelper, pageLink, (object)null, (IContentRepository)null);
}
public static string PageUrl(this UrlHelper urlHelper, PageReference pageLink, object routeValues)
{
return UrlExtensions.PageUrl(urlHelper, pageLink, routeValues, (IContentRepository)null);
}
public static string PageUrl(this UrlHelper urlHelper, PageData page)
{
return UrlExtensions.PageUrl(urlHelper, page, (object)null);
}
public static string PageUrl(this UrlHelper urlHelper, PageData page, object routeValues)
{
if (!PageDataExtensions.HasTemplate(page))
return string.Empty;
switch (page.LinkType)
{
case PageShortcutType.Normal:
case PageShortcutType.Shortcut:
case PageShortcutType.FetchData:
return UrlExtensions.PageUrl(urlHelper, page.PageLink, routeValues, (IContentLoader)null, (IPermanentLinkMapper)null, (LanguageSelectorFactory)null);
case PageShortcutType.External:
return page.LinkURL;
case PageShortcutType.Inactive:
return string.Empty;
default:
return string.Empty;
}
}
private static string PageUrl(this UrlHelper urlHelper, PageReference pageLink, object routeValues, IContentRepository contentRepository)
{
if (contentRepository == null)
contentRepository = ServiceLocator.Current.GetInstance<IContentRepository>();
if (PageReference.IsNullOrEmpty(pageLink))
return string.Empty;
PageData page = contentRepository.Get<PageData>((ContentReference)pageLink);
return UrlExtensions.PageUrl(urlHelper, page, routeValues);
}
private static string PageUrl(this UrlHelper urlHelper, PageReference pageLink, object routeValues, IContentLoader contentQueryable, IPermanentLinkMapper permanentLinkMapper, LanguageSelectorFactory languageSelectorFactory)
{
RouteValueDictionary routeValueDictionary = new RouteValueDictionary(routeValues);
if (!routeValueDictionary.ContainsKey(RoutingConstants.LanguageKey))
routeValueDictionary[RoutingConstants.LanguageKey] = (object)ContentLanguage.PreferredCulture.Name;
if (!routeValueDictionary.ContainsKey(RoutingConstants.ActionKey))
routeValueDictionary[RoutingConstants.ActionKey] = (object)"index";
routeValueDictionary[RoutingConstants.NodeKey] = (object)pageLink;
UrlExtensions.SetAdditionalContextValuesForContent(urlHelper, pageLink, routeValueDictionary, contentQueryable, permanentLinkMapper, languageSelectorFactory);
return urlHelper.Action((string)null, routeValueDictionary);
}
private static void SetAdditionalContextValuesForContent(this UrlHelper urlHelper, PageReference pageLink, RouteValueDictionary values, IContentLoader contentQueryable, IPermanentLinkMapper permanentLinkMapper, LanguageSelectorFactory languageSelectorFactory)
{
bool IdKeep = HttpContext.Current.Request.QueryString["idkeep"] != null;
contentQueryable = contentQueryable ?? ServiceLocator.Current.GetInstance<IContentLoader>();
permanentLinkMapper = permanentLinkMapper ?? ServiceLocator.Current.GetInstance<IPermanentLinkMapper>();
languageSelectorFactory = languageSelectorFactory ?? ServiceLocator.Current.GetInstance<LanguageSelectorFactory>();
IContent content = contentQueryable.Get<IContent>(pageLink, languageSelectorFactory.Fallback(values[RoutingConstants.LanguageKey] as string ?? ContentLanguage.PreferredCulture.Name, true));
if (content == null)
return;
if (IdKeep)
values["id"] = (object)content.ContentLink.ToString();
UrlExtensions.SetAdditionalContextValuesForPage(values, IdKeep, content);
}
private static void SetAdditionalContextValuesForPage(RouteValueDictionary values, bool IdKeep, IContent content)
{
PageData pageData = content as PageData;
if (pageData == null)
return;
if (pageData.LinkType == PageShortcutType.Shortcut)
{
PropertyPageReference propertyPageReference = pageData.Property["PageShortcutLink"] as PropertyPageReference;
if (propertyPageReference != null && !PageReference.IsNullOrEmpty(propertyPageReference.PageLink))
{
values[RoutingConstants.NodeKey] = (object)propertyPageReference.PageLink;
if (IdKeep)
values["id"] = (object)((object)propertyPageReference).ToString();
}
}
}
}
At this point, I'm just supporting passing in PageData
and PageReference
objects, but it could easily be extended to support the other objects that Html.PageLink
supports. I also made it support additional route data information, which will help when creating links to pages in another language branch.
<a href="@Url.PageUrl(Model.SomePageReference, new { language= "sv-SE" })">
<img src="swedish_flag.png" />
</a>
I hope you find this useful, and that it gives you the most flexibility you need when creating a link to a page in your EPiServer site!