Interface | Member | Type |
---|---|---|
IContentData | Property | PropertyDataCollection |
IInitializableContent | SetDefaultValues(ContentType) | |
IModifiedTrackable | ResetModified() | |
IModifiedTrackable | IsModified | bool |
IReadOnly | CreateWritableClone() | object |
IReadOnly | MakeReadOnly() | |
IReadOnly | IsReadOnly | bool |
IReadOnly<T> | CreateWritableClone<T> | T |
IContentSecurable | GetContentSecurityDescriptor() | IContentSecurityDescriptor |
ISecurable | GetSecurityDescriptor() | ISecurityDescriptor |
IContent | ContentGuid | Guid |
IContent | ContentLink | ContentReference |
IContent | ContentTypeID | int |
IContent | IsDeleted | bool |
IContent | Name | string |
IContent | ParentLink | ContentReference |
ILocalizable | ExistingLanguages | IEnumerable<CultureInfo> |
ILocalizable | Language | CultureInfo |
ILocalizable | MasterLanguage | CultureInfo |
IVersionable | IsPendingPublish | bool |
IVersionable | StartPublish | DateTime? |
IVersionable | Status | VersionStatus |
IVersionable | StopPublish | DateTime? |
IResourceable | ContentFolderID | long |
IChangeTrackable | Changed | DateTime |
IChangeTrackable | ChangedBy | string |
IChangeTrackable | Created | DateTime |
IChangeTrackable | CreatedBy | string |
IChangeTrackable | Deleted | DateTime? |
IChangeTrackable | DeletedBy | string |
IChangeTrackable | Saved | DateTime |
IChangeTrackable | SetChangedOnPublish | bool |
IRoutable | RouteSegment | string |
ICategorizable | Category | CategoryList |
IExportable | ShouldBeImplicitlyExported | bool |
List of EPiServer content interfaces and properties
Specifying drag-n-drop support in custom EPiServer editor descriptor
Editor descriptors can be used to change how a property, or properties of a given type, is edited. One such example is described in my article about how to let editors populate content reference properties using dropdowns.
When using an editor that doesn't support drag-n-drop the drag-n-drop functionality can be restored by specifying a drop target type in the editor descriptor. Example:
public override void ModifyMetadata(
ExtendedMetadata metadata,
IEnumerable<System.Attribute> attributes)
{
base.ModifyMetadata(metadata, attributes);
metadata.AdditionalValues["DropTargetType"] = new[] { "epi.cms.pagereference" };
}
Similarly for files the drop target type can be set to "fileurl":
metadata.AdditionalValues["DropTargetType"] = new [] { "fileurl" };
To restrict the type of content that can be dropped to pages the editor descriptors EditorConfiguration property can be used:
EditorConfiguration["typeIdentifiers"] = "epi.cms.page";
If the editor descriptor is for a block type used as a property what property in the block to map the dropped item to can be specified using:
metadata.AdditionalValues["DropTargetChildProperty"] = "LinkedPage";
Localization in widgets used as EPiServer gadgets or property editors
Given a language resource file like this:
<?xml version="1.0" encoding="utf-8" ?>
<languages>
<language name="English" id="en">
<mystuff>
<substuff>
<key>Value</key>
</substuff>
</mystuff>
</language>
</languages>
The node "mystuff" can be accessed in a JavaScript UI-component such as a gadget or custom editor using require and the i18n module, like this:
define([
"epi/i18n!epi/cms/nls/mystuff"
], function (
resources
) {
return declare("", [], {
resources: resources,
});
});
The resources variable will be an object like this:
{
substuff: {
key: "value"
}
}
To instead retrieve only the "substuff" element the required module can be modified to epi/i18n!epi/cms/nls/mystuff.substuff.
To output a localized resource like "key" above in a template the following can be used:
${resources.substuff.key}
Slice your EPiServer content with PowerSlice!
Want to get slicing right away or prefer a short introduction video? Check out PowerSlice's site.
Content manangement using EPiServer CMS is largely based on a hierarchical structure - the content tree. Pages is organized in the page tree and blocks in folders.
This hierarchy is great in many ways. By using the hierarchy when creating navigation and listing components when developing templates editors are free to organize the site's content in a flexible way while not having to call on developers to modify functionality for navigation components and the like.
However, not all content fits naturally into a hierarchical structure. On a medium sized corporate websites most of the content is naturally fitted into a hierarchical structure - but then there's the news room and the corporate blog.
Such content may share a natural first level parent but other than that there may not be anything specific that groups the content into a hierarchy. At least not naturally.
On other types of sites, such as media sites and blogs, almost none of the content can be fitted into a deep hierarchical structure. Instead sections, categories and keywords/tags is used to create the site's taxonomy and navigation.
Freeing content from the content tree using Find
EPiServer CMS doesn't handle such content very well out of the box. Luckily EPiServer Find provides the perfect compliment. Using Find we as developers can easily list all content in a specific category no matter of where it's located in the content tree. Or all content tagged with a given combination of tags.
By using Find we can easily build templates without a dependency on the page tree. However, in order to easily work with content that isn't easily grouped using a tree structure editors need some help. Find can come to the rescue here as well, but we as developers will need to extend EPiServer's user interface.
Freeing editors from the content tree using PowerSlice
I've worked quite a lot with media sites using EPiServer. I've also built this site, which features articles classified by categories and tags rather than a hierarchy, on EPiServer.
Therefor, to make it easier to handle non-hierarchical content and illustrate how media sites, news room functionality or blogs can be built using EPiServer CMS and Find I've created an extension module that makes it easy to list content matching a defined set of criteria. Each such listing is called a "slice". Why? It's a slice of the site's content tree.
A slice may be all pages or blocks of a given type. Or it may be all content created by the user. Or all content who's status is "ready to publish". Or...
All content in a slice is listed using "infinite" scrolling. Editors can filter it using free text search and sort it, by default either by name or publish date.
When creating a slice the developer can also add one or several ways to create new content, typically within the slice. For instance if a slice lists all content of a number of page types used for articles the editor can use it to select one of those page types, enter a name and hit the Create button and a new page will be created at a predefined location in the page tree.
Besides enabling new and improved workflows for editors (and developers) PowerSlice can also be used for exposing custom content types. That is, instances of custom types implementing IContent which won't be displayed in the page tree or blocks gadget.
Download and documentation
The source code for PowerSlice, which is a number of C# classes, JavaScript files and HTML-templates used by the JavaScript components is located on GitHub. The module is also packaged as a NuGet package available on EPiServer's NuGet feed.
Creating a slice is easy. In it's simplest form all we need to do is create a class with a single string property returning the slice's name. Meanwhile, should we want to we can customize the slice to apply filtering, specify the default sort option, add additional sort options, add ways to create content etc.
Documentation as well as a video introducing what PowerSlice is can be found here.
Happy slicing!
Custom routing for EPiServer content
EPiServer 7 CMS uses the built in routing functionality in ASP.NET for URL handling. The default routing when using EPiServer 7 mimics the friendly URL functionality in older versions of the CMS in which the URL for a page is built up of it's URLSegment property prefixed by it's ancestors URL segments.
There are many nice aspects of the default behavior. For instance, URL's for pages match their places in the page tree and in navigation elements making them seem logical and predictable. However, it also has the drawback of tying the URL for a page to it's location in the page tree making it risky to move content.
Also, in some situations, such as when you have a site with a lot of content, it may not be appropriate to place content in a specific place in the content tree even though it belongs there.
It may also be that a page is only used as a data bearer without a template and links to it should lead to some other page, optionally with some parameters in the URL.
In such cases we need to extend or modify EPiServer's default routing. In this article I'll show two real-world examples of doing just that.
Custom routing using partial routing
The first example is drawn from how articles are routed on this site. The article you're currently reading is tied to the EPiServer CMS category (a page) on the site which is reflected in navigation elements.
It does not however reside under the CMS category's page. Instead it's placed in a hierarchical structure based on the date it was created.
Why articles is stored this way is beyond the scope of this article but, as you can probably imagine, storing them this way brings a few problems.
Making the article belong to the CMS category is straight forward. All that's needed is a property of type ContentReference or PageReference on the page type used for articles. That can then be used when building menus and breadcrumbs.
Listing content based on categories is a bit trickier to achieve performance wise with the CMS' API but in my case I have Find to help me with that.
Finally, the URL for an article will be /<year>/<month>/<URLSegment>/. While that's not terrible it's not what I want. Instead I wanted the URL's for articles to be simply the host name followed by their URL segments.
Partial routing
EPiServer 7 features a concept called partial routing. In the developer guide it's described as
Partial routing makes it possible to extend routing beyond pages. You can use partial routing either to route to data outside EPiServer CMS or to route to other content types than pages.
We can however also use it to route ordinary pages, like articles in this case.
To utilize partial routing we create a class implementing IPartialRouter<TContent, TRoutedData> found in the EPiServer.Web.Routing namespace. The first type parameter specifies the type of content we're interested in routing children for. The second the type of content, or other type of object that we'll be routing.
Did I lose you there? Perhaps a fruity example can clarify things.
A class that implements IPartialRouter<Banana, Apple> will be able to route outgoing URLs whenever the object to route is an apple. Whenever a part of the URL for an incoming request has been routed to a content object of type Banana and there are still parts of the URL left to route it will be handed the banana and information about the remaining URL and asked to return an apple.
Crystal clear, right? Perhaps not. Let's look at how we can use it to route articles.
Implementing IPartialRouter
Given that we want articles to have relative URLs that are simply their URL segments the first type parameter when implementing IPartialRouter is the page type class that is used for the start page. The second type parameter is the page type used for articles.
Let's create a class that implements IPartialRouter with those type parameters and have Visual Studio generate the required methods for us.
using System.Web.Routing;
using EPiServer.Web.Routing;
using EPiServer.Web.Routing.Segments;
using MySite.Models.Pages;
namespace MySite.Routing
{
public class ArticleRouter : IPartialRouter<StartPage, ArticlePage>
{
public object RoutePartial(StartPage content, SegmentContext segmentContext)
{
throw new System.NotImplementedException();
}
public PartialRouteData GetPartialVirtualPath(
ArticlePage content,
string language,
RouteValueDictionary routeValues,
RequestContext requestContext)
{
throw new System.NotImplementedException();
}
}
}
As you can see, there are two methods that we'll need to implement. RoutePartial is for incoming requests while GetPartialVirtualPath is for outgoing.
We'll start by implementing the RoutePartial method.
RoutePartial
The RoutePartial method will be invoked whenever a the first parts of the URL points to a page of the TContent type parameter and there are remaining parts of the URL left to route. In this example, as we're implementing the IPartialRouter interface with the start page's type as the TContent parameter that will pretty much always be the case.
The method is invoked with two arguments. The first is the content of type TContent that the first parts of the URL route to, the start page in our case. The second is an object of type SegmentContext which contains information about the next segment in the URL to route.
Using the two arguments it's our job to figure out what article, if any, the request should be routed to.
public object RoutePartial(StartPage content, SegmentContext segmentContext)
{
if (!content.ContentLink.CompareToIgnoreWorkID(ContentReference.StartPage))
{
return null;
}
var nextSegment = segmentContext.GetNextValue(segmentContext.RemainingPath);
var urlSegment = nextSegment.Next;
if (string.IsNullOrEmpty(urlSegment))
{
return null;
}
ArticlePage article = null;
//TODO: Figure out which article it is we're routing based on the urlSegment variable, if any, and populate the article variable with that.
if (article != null)
{
segmentContext.RemainingPath = nextSegment.Remaining;
segmentContext.RoutedContentLink = article.ContentLink;
}
return article;
}
There's quite a few things going on in the above code.
First of all it verifies that the root content is indeed an object for which we want to route remaning parts of the URL. In other words, if we for some reason are dealing with a page of the StartPage type that is not the start page. Not likely when dealing with start pages perhaps, but stranger things have happened and this check may be vital in other scenarios.
Next we proceed to retrieve the next part of the URL which we assign to the urlSegment variable. For an URL such as /hello-world/ the urlSegment variable's value will be "hello-world". For an URL such as /hello/world/ the urlSegment variable's value will be "hello".
Given that there is indeed at least one more segment in the URL we proceed to locate an article whose URL segment matches that. As that's code specific to the site I've omitted my implementation.
How one would do this can vary greatly from site to site. On a small site we may simply retrieve all articles and use LINQ's Where method to find an article with a matching URLSegment property. On a larger site we may use EPiServer Find or we may make the page's ID a part of the URL when implementing outgoing routing which we'll get to shortly.
However we choose to locate articles, or whatever it it we're routing, we need to make sure to do it in a good way performance wise. This code will be called a lot!
Given that we've found an article that we wish to route to we remove the URL segment that we've taken care of from the SegmentContext by assigning the remaning part of the URL to its RemainingPath property. That is, given an article with a URL such as /hello-world/ where we have extracted "hello-world" from the segment context we update it to set its remaining path to nothing.
Also, given that we've found an article, we modify the segment context so that it knows that we've found content that we want to route to.
Finally we return the article, or null, if we didn't find one. If we return null nothing in particular will happen. The routing will proceed as usual routing to a page of some other type or resulting in a 404.
GetPartialVirtualPath
The GetPartialVirtualPath method will be invoked when an article is being outgoing routed. That is, when we for instance create a link to an article with the PageLink HTML helper method.
It's invoked with four arguments which we can use to determine the context in which the article is being routed. Based on those we need to return an object of type PartialRouteData.
The PartialRouteData class has two properties - BasePathRoot and PartialVirtualPath. The generated relative URL will be a combination of the relative URL of the content referenced by BasePathRoot and whatever we set PartialVirtualPath to.
public PartialRouteData GetPartialVirtualPath(
ArticlePage content,
string language,
RouteValueDictionary routeValues,
RequestContext requestContext)
{
var contentLink = ContentRoute.GetValue("node", requestContext, routeValues)
as ContentReference;
if (!content.ContentLink.CompareToIgnoreWorkID(contentLink))
{
return null;
}
return new PartialRouteData
{
BasePathRoot = ContentReference.StartPage,
PartialVirtualPath = content.URLSegment
};
}
In the implementation above we begin by extracting a reference to the content that is being routed using the ContentRoute.GetValue method. Typically this will be a reference to the same article that is being passed to the method as the "content" parameter. However, I've found at least one situation when that wasn't the case.
If the method is invoked in context of routing something else than the article passed in as the "content" variable we're not interested in modifying the routing so we simply return null.
If however we are dealing with an article that we want to modify the routing for we create a new PartialRouteData object, populate its properties and return it.
Since in this example we want articles to simply have their own URL segments as URLs we set the BasePathRoot to a reference to the start page and the PartialVirtualPath to the article's URLSegment property.
Using the partial router
Our partial router class is done. In order for it to be used we need to add it to the site's route table during start-up though. That's easily done using an initialization module and the RegisterPartialRouter extension method that EPiServer provides (in the EPiServer.Web.Routing namespace).
using System.Web.Routing;
using EPiServer.Framework;
using EPiServer.Framework.Initialization;
using EPiServer.Web.Routing;
namespace MySite.Routing
{
[ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
public class RouteInitialization : IInitializableModule
{
public void Initialize(InitializationEngine context)
{
var articleRouter = new ArticleRouter();
RouteTable.Routes.RegisterPartialRouter(articleRouter);
}
public void Uninitialize(InitializationEngine context)
{
}
public void Preload(string[] parameters)
{
}
}
}
Restoring tree-like URLs
In the above example we modify EPiServer's default routing to make pages of a specific type, articles, have URLs that are simply their URL segments. That's what I wanted for this site as it decouples article's URLs from both their place in the tree and their categories.
However, we're not limited to that specific use case. For instance we may instead want articles in the example scenario we've looked at here to have a URL based on their categories instead. As if they were actually placed under their categories in the page tree.
To accomplish that we would need to make a few modifications to our partial router class.
Since the "URL homes", the base path, for articles won't be the start page but instead a categories the first step is to modify the TContent type parameter when implementing IPartialRouter. Instead of the start page's type we set it to the type under which articles will reside URL-wise. In the case of this site that would be the class CategoryPage.
public class ArticleRouter : IPartialRouter<CategoryPage, ArticlePage>
Changing the type parameter of the implemented interface we need to change the type of the RoutePartial method's first parameter or the compiler will complain.
public object RoutePartial(CategoryPage content, SegmentContext segmentContext)
Now the compiler is happy but we'll need to make some changes to the code as well. Let's start with the GetPartialVirtualPath method as that's easiest.
All we need to do there is change what we set the BasePathRoot property on the object we return to. Instead of setting it to a reference to the start page we set it to a reference to some other page that will provide the first part of the article's URL. For our example scenario that would be the property that points to the article's main category.
return new PartialRouteData
{
BasePathRoot = content.MainCategory,
PartialVirtualPath = content.URLSegment
};
The RoutePartial method will need to undergo larger changes.
We can skip the initial check that the root content which is passed in in as the "content" variable is indeed the root page we care about as we now have multiple possible roots.
Instead we'll need to verify that the root, the category, should be used as the routing parent for the article that we're routing.
ArticlePage article = null;
//TODO: Figure out which article it is we're routing based on the urlSegment variable, if any, and populate the article variable with that.
if (article == null
|| content.ContentLink.CompareToIgnoreWorkID(article.MainCategory))
{
return null;
}
segmentContext.RemainingPath = nextSegment.Remaining;
segmentContext.RoutedContentLink = article.ContentLink;
return article;
Custom routing using a custom segment
We've now seen an example of how we can use the partial routing concept in EPiServer 7 to implement custom routing. In the next example we'll look at another way of customizing routing - by using a custom implementation of the ISegment interface.
On this site I use pages to define tags. That is, each tag is a page somewhere in the page tree and articles have a content area to which I can add one or more tag pages. This allows me to change the name of a tag without having to update all pages tagged with it. It also gives me the possibility to add unique content to a tag beyond its name.
When I tag a page with one or more existing tags I simply drag them to the content area. When I render a list of tags I simply output a link to each tag using the PageLink HTML helper.
As I have a separate template for tag pages this works great. However, when the site first went live I didn't have that template. Instead I wanted a tag-link to point to a search for the tag using the site's search page.
One way to solve that would of course have been to find all places where links to tags were rendered and modify the code there to link to the search page. That wouldn't have been a very flexible solution though. By instead modifying the outgoing URLs for tag pages by modifying routing for them I didn't have to touch any other code.
At first glance it may seem like we could use partial routing for this scenario as well but a case like this, where we want to route to a specific page with one or more query string parameters isn't really what partial routing is for.
Instead we can handle cases like this by creating a custom implementation of the ISegment interface and register a route that uses the segment.
Creating a segment
In order to create a custom segment we create a class that implements ISegment (in the EPiServer.Web.Routing.Segments namespace). Alternatively we can let our class inherit SegmentBase which I'll do in this example.
using System;
using System.Web.Routing;
using EPiServer.Web.Routing.Segments;
namespace MySite.Routing
{
public class TagSearchSegment : SegmentBase
{
public TagSearchSegment(string name) : base(name)
{
}
public override bool RouteDataMatch(SegmentContext context)
{
throw new NotImplementedException();
}
public override string GetVirtualPathSegment(
RequestContext requestContext,
RouteValueDictionary values)
{
throw new NotImplementedException();
}
}
}
As you can see from the code above, where I've had Visual Studio autogenerate required methods for me, SegmentBase has two abstract methods that we must implement - RouteDataMatch and GetVirtualPathSegment.
RouteDataMatch is invoked during in-bound routing. We get passed information about the current context and can use that to determine if we want to modify the routing. If we do we modify the context in some way and return true. However, as we only care about out-bound routing in this example we can simply implement it to return false.
public override bool RouteDataMatch(SegmentContext context)
{
return false;
}
GetVirtualPathSegment
The GetVirtualPathSegment method is invoked during outgoing routing and in order to modify the URL we'll need to return a string with the URL, or part of the URL, that we want to route to. We'll also need to remove values that we feel we've taken care of from the RouteValueDictionary that is passed to the method.
public override string GetVirtualPathSegment(
RequestContext requestContext,
RouteValueDictionary values)
{
if (GetContextMode(requestContext.HttpContext, requestContext.RouteData)
!= ContextMode.Default)
{
return null;
}
var contentLink = ContentRoute.GetValue("node", requestContext, values)
as ContentReference;
if (ContentReference.IsNullOrEmpty(contentLink))
{
return null;
}
var contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>();
var tagToRoute = contentLoader.Get<IContent>(contentLink) as TagPage;
if (tagToRoute == null)
{
return null;
}
string tagSearchUrl = null;
//TODO: Figure out the url we want to route to, if any, and set the tagSearchUrl variable to that
if (tagSearchUrl == null)
{
return null;
}
values.Remove("node");
values.Remove("controller");
values.Remove("action");
values.Remove("routedData");
return tagSearchUrl;
}
That's quite a mouthful! Let's go through it step by step.
First we check that we're not in edit mode using a helper method. This is necessary as we will otherwise get a 404, or worse, when viewing tag pages in edit mode as our code will route to the search page while EPiServer will add the tag's ID to the URL, meaning that we'll end up viewing invoking the search page's template with the tag page as the current page.
The GetContextMode method looks like this:
private static ContextMode GetContextMode(
HttpContextBase httpContext,
RouteData routeData)
{
var contextModeKey = "contextmode";
if (routeData.DataTokens.ContainsKey(contextModeKey))
{
return (ContextMode)routeData.DataTokens[contextModeKey];
}
if ((httpContext == null) || (httpContext.Request == null))
{
return ContextMode.Default;
}
if (!PageEditing.GetPageIsInEditMode(httpContext))
{
return ContextMode.Default;
}
return ContextMode.Edit;
}
Next we proceed to extract a reference to the content that is being routed. If we can't find such a reference we're probably routing a file or something else that we don't care about so we return null.
Given that we have a content reference we proceed to fetch the content that we're routing. If it isn't a tag we don't care about it so we return null.
If however the content that we're routing is a tag we build up the URL we want to route to. I've omitted that code as it may be specific to each site and use case.
Given that we have a URL that we want to route to we return it, but before doing so we remove route values that we don't want anyone else who cares about routing to see as we've already taken care of them.
Using the segment
With the custom segment done we need to register a route that uses it for it to matter. As this should be done during start-up of the site we add an initialization module.
using System.Collections.Generic;
using System.Web.Routing;
using EPiServer.Framework;
using EPiServer.Framework.Initialization;
using EPiServer.Web.Routing;
using EPiServer.Web.Routing.Segments;
namespace MySite.Routing
{
[ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
public class RouteInitialization : IInitializableModule
{
public void Initialize(InitializationEngine context)
{
var segment = new TagSearchSegment("tag");
var routingParameters = new MapContentRouteParameters()
{
SegmentMappings = new Dictionary<string, ISegment>()
};
routingParameters.SegmentMappings.Add("tag", segment);
RouteTable.Routes.MapContentRoute(
name: "tags",
url: "{language}/{tag}",
defaults: new { action = "index" },
parameters: routingParameters);
}
public void Uninitialize(InitializationEngine context)
{
}
public void Preload(string[] parameters)
{
}
}
}
Using the code in the Initialize method above we register a route for "{language}/{tag}". We map the {tag} part to our custom segment meaning that it will be replaced with whatever we return from the segment's GetVirtualPathSegment method.
Basic routing
We've just seen two fairly advanced examples of how the routing for content can be customized when using EPiServer 7. In both cases we're more or less in complete control and can execute custom logic on a per-request basis.
That's nice, but there may be cases where we want to do something simpler. Were we don't need all that power and flexibility. For instance, let's say we for some reason wanted to support outputting only the first n characters of the name of a page given that the request is for a page followed by /name/<n>/.
Not the most realistic scenario perhaps, but it does make for a simple example. Anyhow, To handle such a case we wouldn't have to create a partial router or a custom segment. All we need to do is register a route using the MapContentRoute method during start-up.
RouteTable.Routes.MapContentRoute(
"myRoute", "{language}/{node}/{action}/{charcount}",
new {action = "name"});
Now, given that we're using MVC, we can add an action to controllers for pages named Name which will be used for matching requests.
public ActionResult Name(CategoryPage currentPage, int charCount)
{
if (currentPage.PageName.Length < charCount)
{
charCount = currentPage.PageName.Length;
}
return new ContentResult()
{
Content = currentPage.PageName.Substring(0, charCount)
};
}
For another example of this type of custom routing check the routing section in EPiServer's developers guide.
Conclusion
We've seen a couple of examples of how we can take charge of the routing for specific types of content. With this in our toolbox we can free the URLs for pages from the content tree, change where pages links to as well as other interesting stuff.
A couple of words of warning is in place though.
First of all, when modifying both the outbound and inbound routing as in the first example, the one with articles, we're bypassing EPiServer's functionality for ensuring that no two pages can have the same URL. That is, in the example with the articles we'll need to ensure that no two articles have the same URL segment.
Second, do think about that the methods for routing, such as the ones we've looked at in this article may be invoked a lot, so be sure to test the performance with realistic data before deploying to production.
With that said, I must say that it's nice that we can control the routing in just about any way we'd like. That brings many interesting possibilities.
Happy routing!
Hiding EPiServer properties from editors
When managing a property in EPiServer's admin mode there is a setting named "Display in Edit Mode". To control this setting from code in EPiServer 7 use the ScaffoldColumn attribute.
When set to false it will hide the property from editors, meaning that while they may seem the rendered value in preview mode they won't see a blue box around it and they won't be able to click on the property to edit it. The property won't be visible in forms editing mode either.
There is also the Editable attribute. When this is set to false the property will still be visible to editors in both preview mode, with a blue box around it, and in forms editing mode. The editor for it will however be disabled, meaning that while editors can inspect the property's value they can't modify it.
Pattern for EPiServer block preview MVC controller
using System.Web.Mvc; using EPiServer.Core; using EPiServer.Framework.DataAnnotations; using EPiServer.Framework.Web; using EPiServer.Web; namespace MySite { [TemplateDescriptor( Inherited = true, TemplateTypeCategory = TemplateTypeCategories.MvcController, Tags = new [] { RenderingTags.Preview }, AvailableWithoutTag = false)] public class PreviewController : Controller, IRenderTemplate<BlockData> { public ActionResult Index(IContent currentContent) { //TODO: Create model suitable for the view and return ViewResult return View(); } } }
Explained:
using System.Web.Mvc; using EPiServer.Core; using EPiServer.Framework.DataAnnotations; using EPiServer.Framework.Web; using EPiServer.Web; namespace MySite { [TemplateDescriptor( //Support everything inheriting from BlockData. Inherited = true, //By default controllers implementing IRenderTemplate<T> where T is BlockData //are registered as partial renderers. As this will be a "full page" renderer //we need to change that. TemplateTypeCategory = TemplateTypeCategories.MvcController, //Should only be used for preview Tags = new [] { RenderingTags.Preview }, AvailableWithoutTag = false)] public class PreviewController : Controller, //Register as template for BlockData. To only support a specific type //change the type parameter from BlockData to that type and optionally //set Inherited = false in the the TemplateDescriptor attribute above. IRenderTemplate<BlockData> { public ActionResult Index( //While we implement IRenderTemplate for BlockData model binding //can only deal with IContent, ie shared blocks in this case. IContent currentContent ) { //TODO: Create model suitable for the view and return ViewResult return View(); } } }
EPiServer Find 101
If you're new to EPiServer Find and going to use it in a project, or just want to try it out, there's plenty of documentation on Find's site. There's also training offered by EPiServer. Not to mention the forum on EPiServer World.
Sometimes you need a little quick start though. Here's the most essential things to know about Find's .NET API and CMS integration.
Setting up
You can grab yourself a free test index at find.episerver.com. Once you have created your index you can copy and paste the necessary configuration into your app.config or web.config.
Finally you need to download and reference Find's .NET API. That's easily done through NuGet given that you have EPiServer's NuGet feed added as a package source. There's three packages to choose from. For an EPiServer CMS site install EPiServer Find CMS Integration. For any other type of .NET application install EPiServer Find Client API.
Communicating with the REST API
You can use the .NET API from any .NET application. When doing so, all communication with Find's REST API goes through an instance of the IClient interface. To obtain such an instance when not using the CMS integration you can use the Client.CreateFromConfig() method to create it based on settings in app.config/web.config.
If you're using the CMS integration or any other integration for other EPiServer products always use the singleton SearchClient.Instance. This is important. The singleton is preconfigured with conventions set up for CMS content.
"If you're using the CMS integration always use the singleton SearchClient.Instance"
Finding things
Once you have a client search for indexed objects (CMS objects are automatically indexed upon save) using the Search<T>() method where T is the type of objects you want to search for. The method supports inheritance and searching for objects that implement an interface.
The Search method returns an object which you can add queries and filters to. Use the For method for free text search. Use the Filter method to apply criterias.
The Filter method is very similar to LINQ's Where method but has a slightly different syntax. You can filter on any property of a "simple" type, such as int, string, DateTime, double, GUID etc. Check out the documentation for more information or use Intellisense.
You can use methods with familiar names from LINQ to apply sorting, projections and paging - OrderBy, Select, Skip, Take etc.
Once you're done building your search request execute it against the REST API and get the result using the GetResult() method. The returned object will be an object the implements IEnumerable<T> so you can start iterating over the results straight away.
Note that if you're searching for CMS objects such as PageData and want to fetch the full objects you shouldn't use GetResult but instead use the GetContentResult() method. When using the integration for CMS 6, that's called GetPagesResult.
"if you're searching for CMS objects such as PageData and want to fetch the full objects ... use the GetContentResult() method"
Unified Search
One of the things that makes Find powerful and easy to use at the same time is the fact that it let's us index our own .NET object and search for them in a strongly typed way. Sometimes though, such as building a search page, we want to search over many different types. Find makes that easy to using a concept called Unified Search.
In general - use the regular Search method for building listings, navigations and specialized search page with ease and use Unified Search when building free text search pages where users should search in *everything*.
Where to go from here
That's it. A few key concepts to get you started and a few important things to remember to avoid being faced by a YSOD.
Once you've familiarized yourself with Find's .NET API you can move on to indexing custom objects or why not check out some of the more specialized and interesting methods such as MoreLike and BoostMatching. Also, don't miss out of the many types of facets which you can use to build many interesting things.
Want to build a search page for an EPiServer site quickly? Here's one complete with annotated source code for you.
Happy Finding!
Specify z-index for a property's overlay in EPiServer's on page edit mode
Sometimes two property values overlap when rendered in a template. It may for instance be a string property whose value should be rendered on top of an image which is also a property. Or it may be a link/URL property that should be rendered at the of a string property.
By default both properties will have an overlay, the thin blue border that is positioned on top of the element in which the property value is rendered. However, only one of them will be clickable.
To handle that we can specify the z-index for the overlay for the property that isn't clickable. To do so add a data-epi-overlay-z-index attribute to the element that contains the property. For instance, when using ASP.NET MVC and Razor:
<h1 data-epi-overlay-z-index="123" @Html.EditAttributes(x => x.Heading)> @Model.Heading</h1>
Add UIHint to an EPiServer property without affecting its editor
We can use UI hints to make the PropertyFor and DisplayFor methods use a specific display template when rendering properties with ASP.NET MVC.
For instance, when rendering a property like this...
[UIHint("Banana")]
public virtual string Heading { get; set; }
...using Html.PropertyFor() it will be "sent" to a display template named Banana, given such a template exists.
There's just one problem. When adding a UI hint that no editor descriptor cares about we loose the original editing functionality for the property and it will be edited using the "legacy" editing functionality. That is, instead of a textbox for a string property editors will see a button saying "Click the button to edit".
To fix that, use an overload for the UIHint attribute and specify PresentationLayer.Website, like this:
[UIHint("Banana", PresentationLayer.Website)]
public virtual string Heading { get; set; }
One caveat, while this works great with PropertyFor it won't work with DisplayFor which won't recognize the UI hint any longer.
How EPiServer's HTML helper PropertyFor works
When using Web Forms the standard/default/recommended way to render an EPiServer property is to use the EPiServer:Property control. When using ASP.NET MVC its counterpart is the HTML helper PropertyFor.
That is, PropertyFor is the counterpart the Property control in MVC in terms of being the recommended way to render a property. It is however not the equivalent of the Property control in terms of functionality.
Wrapping elements
As you may know, the Property control always wraps the property value in a HTML element. The element differs depending on the property type and can be customized using the CustomTag attribute on the control. This isn't the case with PropertyFor.
To examplify, let's look at the code below where we use the PropertyFor method to render a string property.
<div> @Html.PropertyFor(x => x.Heading)</div>
Given the Heading property has the value "Banana", the above will output the following when the page is rendered in view mode.
<div> Banana</div>
When the page is rendered in edit mode the output will instead be:
<div><div class="epi-editContainer" data-epi-property-name="Heading" data-epi-use-mvc="True">Banana</div></div>
As you can see, the property's value has been wrapped in a div with a CSS class and a couple of data-attributes.
Conclusion: PropertyFor never wraps the property's value in an element when rendering it in view mode. It always wraps the property's value in an element when rendering it in edit mode.
CustomTag and CssClass
As I've previously written about, it's possible to pass settings such as CustomTag and CssClass to PropertyFor. With the Property control the CustomTag setting controls the type of element that the property is wrapped in and the CssClass setting adds one or more CSS classes to the wrapping element.
Let's modify the previous example to see what PropertyFor does with them.
<div> @Html.PropertyFor(x => x.Heading, new { CustomTag = "h1", CssClass ="muted" })</div>
In view mode this changes the output to:
<div> Banana</div>
That's right, it doesn't change the output at all.
What about when rendered in edit mode?
<div><h1 class="epi-editContainer" data-epi-property-name="Heading" data-epi-use-mvc="True" data-epi-property-rendersettings="{"customTag":"h1","cssClass":"muted"}" class="heading">Banana</h1></div>
Starting with CustomTag we can see that setting it to h1 changed the type of the wrapping element that PropertyFor added in edit mode.
Conclusion: CustomTag changes the type of element that wrapes the rendered property when rendered in edit mode.
As for CssClass that added a class attribute with the specified value to the element. However, there already was another class attribute on the element and our custom CSS class wasn't added to that. It was added as a separate class attribute, meaning that the browser will ignore it. Here's how FireBug sees the rendered markup:
So, it appears that while CustomTag changes the type of wrapping element that PropertyFor renderes the property in in edit mode CssClass doesn't, in practice, have any effect on the class attribute on the wrapping element.
However, there's another setting we can pass to PropertyFor, EditContainerClass. Let's see what happens if we use that instead of CssClass, like this:
<div> @Html.PropertyFor(x => x.Heading, new { CustomTag = "h1", EditContainerClass ="muted" })</div>
That produces the following when rendered in edit mode:
<div><h1 class="muted" data-epi-property-name="Heading" data-epi-use-mvc="True" data-epi-property-rendersettings="{"customTag":"h1","editContainerClass":"muted"}">Banana</h1></div>
Look at that. No second class attribute has been added and the first has changed from "epi-editContainer" to the value we specified in EditContainerClass.
Conclusion: CssClass does not change the CSS class for the wrapping element when rendered in edit mode. EditContainerClass does.
Us specifying CustomTag, CssClass and, later, EditContainerClass also brought another change to the rendered markup in edit mode - a data-epi-property-rendersettings attribute was added to the wrapping element. The attributes value looks to be the serialized version of the anonymous object we used to pass the settings to PropertyFor with.
How PropertyFor renders property values
So far we've looked at how and when PropertyFor wraps the output for a property. But, beyond rendering a wrapping element in edit mode, how does it actually render a property?
In Web Forms the Property control uses the ClassFactory to resolve a control to which it delegates the actual rendering of the property. This way different controls are used for different types of properties. For instance, a string is rendered as is (wrapped in a p tag) while a LinkCollection is rendered as an ul-li list.
The PropertyFor method works quite differently but in a way that achieves similar results - it delegates the actual rendering to ASP.NET MVC's DisplayFor method.
Conclusion: In view mode PropertyFor isn't much more than an alias for DisplayFor. In edit mode it adds a wrapping element to whatever is rendered by DisplayFor. The wrapping element contains attributes that the CMS uses to identify editable properties.
DisplayFor and display templates
The DisplayFor method uses a number of rules to determine what to render for a given object. These, in an abbreviated form based on the official documentation on MSDN, are:
- If the property is typed as a primitive type (integer, string, and so on), the method renders a string that represents the property value.
- If the property type is Boolean, the method renders an HTML input element for a check box.
- If a property is annotated with a data type attribute, the attribute specifies the markup that is generated for the property. For example, if the property is marked with the EmailAddress attribute, the method generates markup that contains an HTML anchor that is configured with the mailto protocol.
- If the object contains multiple properties, for each property the method generates a string that consists of markup for the property name and markup for the property value.
What the documentation doesn't say clearly though is that the rendering is based on templates which can be overridden. That can be done by placing a partial view whose name matches the type of object that is to be displayed or whose name matches either a data type name or UI hint.
EPiServer adds a number of such templates to our site using a virtual path provider. These are the ones that handle the acutal rendering of EPiServer specific types such as LinkCollection, ContentArea etc.
Should we want to we can customize the rendering of such types by creating our own display templates. We can also specify that a specific template should be used for a specific property, no matter its type, by adding a UI hint to it.
PropertyFor and content areas
So far we've concluded that PropertyFor acts as a wrapper for DisplayFor and wraps the output from DisplayFor in an element, but only when invoked in edit mode context. There's seemingly an exception to that rule.
Let's look at what happens when we render a property of type ContentArea. The code may look like this:
<div> @Html.PropertyFor(x => x.Contents)</div>
Given that the content area is empty this results in the following output in edit mode:
<div><div class="epi-editContainer" data-epi-property-name="Contents" data-epi-use-mvc="True"></div></div>
Nothing surprising there. The call to PropertyFor results in a wrapping element, the div. Of course, since we're in edit mode that's expected.
Now, let's look at the output in view mode:
<div></div>
Nothing surprising there either. The wrapping element is gone. As we've already concluded that it should be in view mode.
Let's see what happens when we add something to the content area. In this case I've added a page for which there is a very simple partial renderer which simply outputs "Partial renderer output".
Here's the output in edit mode:
<div><div class="epi-editContainer" data-epi-property-name="Contents" data-epi-use-mvc="True"><div data-epi-block-id="4" data-epi-content-name="Home"> Partial renderer output</div></div></div>
Again, no surprises. The wrapping element is there. The single content in the area is also wrapped in a div which is used to communicate information about item in the area to the CMS.
What about view mode then?
<div><div><div> Partial renderer output</div></div></div>
That's quite a few div tags! Compare them to the output we saw in edit mode and you find that the wrapping element for the property (the second div) is there, even though we're in view mode.
It appears as though there's something treating content areas in a special way in PropertyFor, always outputting the wrapping element if the area isn't empty no matter in what context the area is being rendered.
That's not the case though. Instead it's the default display template for content areas that works in a way that gives that effect.
That display template delegates the rendering of the area to a method named RenderContentArea located in the class EPiServer.Web.Mvc.Html.ContentAreaExtensions. That method checks if the context is view mode and if it is outputs the wrapping element.
Why? Well, imagine you want to render the content area as an ul-li list. In that case you'd want to invoke PropertyFor with ChildrenCustomTagName set to "li" in order to make each item in the area wrapped with an li instead of a div.
Then you'd want to have the li tags wrapped in an ul. If you were to wrap the call to PropertyFor in an ul that would work great in view mode but you'd end up with <ul><div><li> in edit mode as PropertyFor always wraps the output in a div, or some other tag specified using CustomTag.
On the other hand, if you didn't wrap the call to PropertyFor in an ul tag and set CustomTag to "ul" you'd end up with an ul-li list in edit mode but a lone set of li tags in view mode. To prevent that, the default display template for content areas, by means of the RenderContentArea method, outputs the wrapping element in view mode.
Conclusion: The default display template for content areas is implemented in such a way that the wrapping element is always outputted no matter if the property is rendered in view mode or edit mode, but only if the content area isn't empty.
Fix for weird looking Google Maps controls and overlays
Recently I have on two different sites run an into issue when using Google Maps API v3 - the controls for zooming have looked partially hidden and skewed.
When using overlays such as InfoWindow there has been similar issues with odd looking shadows and lines in and around them resembling the type of fractals that can be seen when the graphics card is struggling during 3D intensive games.
On both the issues were caused by image tags having max-width set to 100% in the CSS. Setting that to "none" instead for images withing the element containing the map, like the CSS example below, fixed it.
.map_canvas img { max-width: none; }
Building large scale EPiServer sites
Last week I received an e-mail with the subject ”Huge number of pages (>500 000) in EPiServer?” As you can imagine the e-mail contained questions related to whether EPiServer CMS can handle sites with A LOT of content.
It has been proven on a number of occasions that it indeed can. For instance, several of the biggest newspapers in Sweden, with millions of pages, run EPiServer. There are also government sites with several hundreds of thousands of pages.
That doesn’t mean that we’re not faced with challenges when building large EPiServer sites though.
In my experience, what those challenges are differs depending on the type of site, or rather, depending on how the content is structured on the site. While an individual site may be a mix of the two, there are two categories of large scale sites in terms of how they structure their content.
Content that fit naturally in a deep hierarchy
This is common for government sites and the like. Such sites may publish a lot of content that is organized and exposed to visitors in a hierarchy based on topics, subtopic and so on.
Alternatively the content may fit naturally in a date-based hierarchy, such as an archive with publications.
Content that isn’t hierarchical or the hierarchy is shallow
This is very common on media sites such as newspapers. Those sites typically have a shallow hierarchy made up of sections. There may for instance be a first level section called "Sport" and a subsection to that called "Football".
An article about one of the Champions League semifinals 2013 is displayed in the context of Sport/Football but beyond that it has no natural place in a tree like structure. On a site with a lot of content this in turn means that an article in context of its place in the hierarchy may have thousands, or even millions, of siblings.
Finding content based on non-hierarchical criteria
Given that we’re dealing with a site that has a lot of content that fit nicely into a tree based structure EPiServer CMS works great out-of-the-box for editors. The CMS stores content in a tree, the content tree, and expose that to editors using UI components such as the Page tree and the Block gadget that also lets editors work with content in a tree.
While there is a lot of content, meaning that the content tree has a lot of branches and leafs editors can easily find the right place to publish new content. They can also find old content simply by navigating the content tree, or the site, the same way a public visitor would.
In cases where the standard navigation doesn’t suffice, such as when an editor or visitors needs to find content that, in their view, isn’t placed where it should be on the site, basic free text search functionality typically can handle that.
As for developers building navigation components is typically easy as all they have to do is utilize the page tree. EPiServer's methods for doing that, such as Get, GetChildren and GetAncestors are highly optimized and aggresively cached with clever dependencies for releasing their caches when needed and only then.
However, no matter how natural the content hierarchy is there are usually a number of requirements for components that lists content in a way that isn’t based on the hierarchy. Examples of such components could for instance be the most recently published pages of a certain type, all articles published be a certain author or department and all publications categorized with a certain keyword.
For such requirements EPiServer CMS only has the method FindPagesWithCriteria to offer. Besides obvious usability issues for developers FPWC has some serious performance issues, especially on a site with a large volume of content.
In other words, on a large scale EPiServer site with content fitting naturally into a deep hierarchy we’re faced with the challenge of finding content based on non-hierarchical criteria.
There are two common solutions to this problem. One is to somehow store the answer to such questions at a time where we know that the answer is changing. For instance, this may mean storing a list of the ten last published pages of a given type serialized into a property somewhere or in the Dynamic Data Store, updating each time a page is published. This requires quite a lot of development time and, worse, it requires us to know what questions will need answering beforehand. It’s also rather error prone.
The second, and much better, common solution to this is to use a search engine. This has been done on a number of large EPiServer sites using different search engines. Today though the obvious solution is to use EPiServer Find, the search and content retrieval product that EPiServer offers. Find was in many ways built exactly to address this problem in a way that offers great usability for developers and short development time.
Solution: Use a EPiServer Find to create navigations and listings of content that are not based on the contents place in the content tree.
Non-hierarchical content
When the content can’t naturally be fitted into a deep hierarchy additional challenges arise. First, EPiServer’s API and editorial interface is designed for sites organizing content in a tree. If the content can’t be organized into a deep hierarchy performance will suffer. Here’s how I put it in my reply to the e-mail:
“The content tree can handle millions of items BUT if those items aren't stored in a deep hierarchy there will be performance problems. That is, if you have a page with ten thousand children you have a problem. If you have a page with a hundred children and each of those have a hundred children you won't have a problem.”
While I knew this from experience, after sending that reply, I decided to conduct a few experiments to prove it.
In the context of a scheduled job I wrote code that created ten thousand pages below the same parent. It also created a hundred pages below another common parent and then a hundred pages below each of those pages. Everything was done in batches of a hundred pages and the mean time for creating a page during each batch was logged.
Let’s look at the results for creating 100x100 pages in a hierarchy first.
Batch | Avg. time per published page (ms) |
1-100 | 22 |
201-300 | 22 |
501-600 | 22 |
901-1000 | 24 |
1901-2000 | 24 |
4901-5000 | 33 |
9901-10000 | 30 |
There are of course some variances which would likely even themselves out with a larger sample (I ran the test only four times), but it's pretty clear that it takes almost exactly the same amount of time to create page number ten thousand as page number one when storing pages in a hierarchy where each page has ninetynine siblings.
Now, let’s compare that to creating 10000 pages below the same parent.
Batch | Avg. time per published page (ms) |
1-100 | 22 |
201-300 | 26 |
501-600 | 34 |
901-1000 | 50 |
1901-2000 | 83 |
4901-5000 | 180 |
9901-10000 | 414 |
As we can see the time required to publish a page grows based on the number of existing pages below the page’s parent page. Plotted into a diagram we can see that this growth is linear.
Beyond API performance issues, expanding the tree node for a page revealing it’s ten thousand pages in edit mode takes time. Below is what FireBug reported for me when I tried to expand a none with ten thousand children.
After receiving the response from the server Firefox reported an unresponsive JavaScript on the page and it took several minutes before I actually got to see the pages in the page tree.
Of course, even if it the page tree wouldn't have any issues with displaying thousands of children for a node such a list would hardly be useful for editors.
Conclusion: EPiServer is built and optimized for sites that stores content in a hierarchy in which each node has hundreds and not thousands of child nodes.
Clearly, EPiServer's page tree doesn't work well for large scale sites with huge volumes of non-hierarchical content. Not in terms of performance and not in terms of editor usability.
Luckily there's a fairly easy solution that has proven to work very well. In fact, I've seen it done so many times that I'd call it a pattern. What is is? Faking it!
Structuring bulk content in arbitrary hierarchies
We know that EPiServer needs, or prefers, organizing pages in such a way that each node in its content tree doesn't have more than hundreds of immediate children. EPiServer does not however care about why a certain page belongs in a certain place in the tree. Therefor we can automatically place pages in a hierarchy based on some arbitrary criteria.
For articles on a media site this is commonly done by placing them in a structure based on publish date.
There are a number of ways to implement such functionality but it typically involves:
- Defining a root node for a certain type of content.
- Hooking up to events from EPiServer's API listening to when content is created.
- When a page of a matching type is created ensure that there is a place for it in the date structure below the root, otherwise create it.
- Move the newly created content to its parent in the date structure.
Of course, when content resides in a structure whose only purpose is to work well with EPiServer performance wise we can't utilize the content hierarchy when building navigations. The solution to that typically involves four things:
- Defining one or several properties on the content types that will be used for "bulk content". These properties are typically of type PageReference, ContentReference or ContentArea. For instance, an article may have a property named Section of type PageReference which points to the Football section.
- Utilizing the above mentioned property/properties when rendering pages to determine in what context they should be shown. For instance, an article about a Champions League game may have a Section property pointing to a page named Football which in turn is a child of a page named Sport. Based on that the article's content is displayed framed by a header, navigation elements and right column from Football or Sport.
- Using a search engine to create listings. Essentially I'm talking about the same problem as we looked at before here, finding content based on non-hierarchical criteria. The only difference is that we now need to apply the same solution in more places as the majority of the content of the site's content is organized in such a way in the content tree that it can't be used to build navigations and listings.
With that said, we can still utilize EPiServer's standard API methods for components such as the main navigation as those pages aren't the "bulk content" and therefor works well with the page tree. Again while we can use pretty much any search engine that offers good performance and scalability, EPiServer Find is the best option as it was born out of these specific needs. - Rewriting URLs. By default URLs on an EPiServer site is built up using the page's name prefixed by the name of each of its ancestors in the page tree. When storing articles or other content in a structure that has nothing to do with how visitors sees the page on the site URLs won't seem very logical. For instance an URL like /2013/04/23/champions-league.. is hardly helpful and doesn't look very good. With older versions of EPiServer CMS we typically handled that using a custom URL rewriter. Nowadays with EPiServer 7 we do it using custom routing.
Solution: Automatically organize non-hierarchical content in arbitrary hierarchies based on creation date, first letter in their names or some other criteria. Use properties on the content to tie it to pages in the page tree which provide the context in which the content should be rendered. Use EPiServer Find to build listings.
Using the above approach we solve the performance issues when dealing with non-hierarchical content of the type that is often found on media sites, blogs and the like. While that works great we have one more problem to solve - how editors create and find the content for which the page tree is more or less useless.
Custom components for editorial workflows
We could tell editors "To create a new article click on the Articles node in the page tree. We have some code that will automatically move it a few levels down in a date based structure. Oh, and don't forget to set the Section property." However, odds are we wouldn't find much jobs afterwards and we'd see buch of angry comments about EPiServer on Twitter.
Clearly, if we're dealing with the type of site that the page tree doesn't work well with we can't just be content with solving performance issues. We'll also need to extend EPiServer's edit mode to provide good workflows for editors.
Exactly how such components should work and be implemented differs from site to site but it typically involves functionality to:
- Create "bulk content" without having to use the page tree gadget.
- Either automatically populating "infrastructure properties" such as Section or making it very easy for editors to do so.
- List the most recent content. Especially on media sites it's a very common requirement to have a list that displays all articles that have either been published today, not yet been published or is scheduled to be published.
- Find content based on criteria such as author, publish date and section.
I've built such functionality both in EPiServer 6 and EPiServer 7 and based on those experiences I've created the PowerSlice project. PowerSlice is one way of addressing several of the above requirements and may solve all needs for some sites. For other sites it may be used for inspiration.
Either way, it's very much possible to build the components needed by editors. With EPiServer 7 it typically involves creating custom Dojo/Dijit widgets and utilizing EPiServer Find.
Solution: Extend EPiServer's edit mode with custom components tailor made for the needs of the editors. PowerSlice may be an option and/or used for inspiration.
Are you still with me? Perhaps it's about time we looked at an example.
An example - this site
This site doesn't exactly fit the "large scale" description. However, as it's primarily a blog it, along with certain parts of many other "small" sites, does have non-hierarchical content. Therefor I applied the above mentioned techniques to it, meaning that we can look at it as an example of how a large scale site, such as a media site, can be built.
Organizing pages
In terms of hiearchy there are two types of pages on the site. Articles and tags are automatically organized in two separate structures. Sections and standard pages are not.
All articles resider under a node below the start page. Under that node they are grouped first by year and then by month.
Articles have a property named MainCategory edited using a drop down from which it's possible to select one of the categories (sections) on the site.
This property is used for breadcrumbs and context specific things when rendering an article.
Articles also have a content area property to which other categories can be added.
MainCategory and any categories added to AdditionalCategories are combined by a code only property on articles named AllCategories.
New articles can be created using a UI component that's created using PowerSlice.
When an article is created it's initial parent is the root node for articles. Using a modified version of my old open source project PageStructureBuilder the article as automatically moved to a year/month structure.
Listings and routing
Categories/sections list articles "belonging" to them based ordered by publication date. This is done using a fairly simple search query using EPiServer Find that filters on article's AllCategories property.
_searchClient.Search() .Filter(page => page.Categories.Match(currentPage.ContentLink)) .CurrentlyPublished() .FilterOnReadAccess() .OrderByDescending(x => x.StartPublish);
As for URLs I use a custom partial router, which I've previously described in great detail.
Dealing with traffic
So far this article has been about how to build sites with large volumes of content on EPiServer CMS. There's of course another way to interpret "large scale sites" - sites with a lot of traffic.
Again, it's already been proven by a number of existing EPiServer customers that EPiServer can handle huge volumes of traffic. With that said, it's of course also very much possible to build a site on EPiServer that crumbles once it's hit with more than a couple of concurrent requests.
A robust EPiServer site that can handle a lot of traffic and let editors work efficiently at the same time requires a good implementation. And a good implementation requires skilled and experienced developers who know what they are doing.
In general EPiServer CMS's API is highly optimized and the most significant methods for getting content based on the hierarchy are cached. As for EPiServer Find it's fast, highly scalable and also has mechanisms for caching that can be used when needed. So, as first step in hardening a site for production it's absolutely vital to use the API methods visely. Having done so the site will hold well on its own.
Sometimes though, we're dealing with a site that has so much traffic that it's not just enough use caching to prevent database calls. The actual rendered HTML output needs to be cached as well. For that EPiServer offers a fairly basic output cache that can be used.
While that may be an option, for site with really, really, really much traffic we may need even more efficient output caching. In those cases we can either construct a custom output cache to use on the webservers or, which I prefer, use a web content accelerator/caching reverse proxy such as Varnish. We may also want to look in to using a CDN instead or as a compliment.
One thing to beware of though is that any form of output caching, with the exception of partial caching, will limit editors ability to use some of the functionality built into the CMS such as personalization. If that's a problem we can often work around that by loading parts of pages using JavaScript and not caching such requests. Of course that means more requests to the site though.
In general, my philosophy is to build the site as robust and performant as possible without so that it can handle the traffic without any other form of caching. After that, if it's needed or economically motivated some sort of cache can be put in front of the site.
This approach has two benefits. First of all we can choose to use output caching for the right reasons. Second, using output caching tends to hide performance problems in the application and while those may not be a problem at first they may be whenever the cache is released or if there's an issue with the output cache. Then it's very valuable if the web applciation can hold on its own.
Summary
- EPiServer CMS can handle sites with millions of pages.
- For large scale sites FindPagesWithCriteria doesn't work for non-hierarchical queries. Use EPiServer Find for that.
- EPiServer relies on content being split up into a hierarchy. If the content doesn't fit naturally into such a hierarchy, make one up and use a combination of properties, Find and edit mode extensions for creating new content and building listings.
RenderContentData with support for rendering tag
EPiServer 7 CMS' API features two HTML helper extensions for rendering partial content with MVC, both named RenderContentData. None of them offer a way to render content using a rendering tag.
The below extension adds such a method:
using System.Web.Mvc; using EPiServer; using EPiServer.Core; using EPiServer.Framework.Web; using EPiServer.ServiceLocation; using EPiServer.Web; using EPiServer.Web.Mvc; using EPiServer.Web.Mvc.Html; namespace MySite { public static class HtmlHelpers { public static void RenderContentData( this HtmlHelper html, IContentData content, string tag, isInContentArea = false) { var templateResolver = ServiceLocator.Current.GetInstance<TemplateResolver>(); var templateModel = templateResolver.Resolve( html.ViewContext.HttpContext, content.GetOriginalType(), content, TemplateTypeCategories.MvcPartial, tag); var contentRenderer = ServiceLocator.Current.GetInstance<IContentRenderer>(); html.RenderContentData( content, isInContentArea, templateModel, contentRenderer); } } }
Related content with EPiServer Find
EPiServer Find has a number of methods that can be used for finding related or similar content. Combine them and they can offer a powerful way to help visitors find interesting content and improve search engine rankings through internal links.
Find similar pages using MoreLike
Built into Find's .NET API is a method named MoreLike. It requires a string as argument and adds a query to a search request that will match indexed objects with similar text.
SearchClient.Instance.Search<PageData>().MoreLike("banana")
When using MoreLike the search results are ranked by how similar their texts are to the one we pass as argument to it. The similarity is essentially based on common words and there are a number of adjustments that can be made to the algorithm to optimise it to suit the specific nature of the content on a specific site.
Add business logic using BoostMatching
On its own the result of the MoreLike method is highly dependent on the textual content on the site, producing varying results in terms of quality depending on the nature of the content on the site. It produces results based on similarity between texts but doesn't guarantee that there is any relation between the content other than shared usages of words in the text.
Find's .NET API also features a method named BoostMatching which can be used to apply boost factors using whatever criteria that can be described using a filter. In other words, pretty much anything. Using that we can upgrade a MoreLike query from producing similar content to related content.
SearchClient.Instance.Search() .MoreLike("banana").BoostMatching(x => x.InCategory(42), 3)
What conditions we use for boosting is, naturally, dependent on the nature of the site and the type of content for which we want to find related content. If it's a blog post we may boost blog posts that share the same categories and tags. If it's a recipe site we may boost recipes that share the same ingredients. If it's a product we can boost based on categories, price and stock balance.
In other words, BoostMatching allows us to apply custom business logic tailored for our specific site and needs of the business.
An example
On this site I have functionality to relate content using a number of mechanisms such as categories, tags and topics. If an article is grouped in a topic other articles in the same topic is listed.
For articles that doesn't have such lists I still want to display a list of, as far as it's possible, related articles. That's accomplished using the below method.
//using System.Collections.Generic; //using EPiServer.Find; //using EPiServer.Find.Cms; //using EPiServer.Find.Framework; public virtual IEnumerable<EditorialPageBase> GetSimilarPages(EditorialPageBase page) { //Create search request for pages of the type(s) that we're interested in //and apply a MoreLike query using the concatenated values of all searchable //properties on the current page. IQueriedSearch<EditorialPageBase> query = SearchClient.Instance .Search<EditorialPageBase>() .MoreLike(page.SearchText()); //Apply major boost to articles in the same category. foreach (var category in page.Categories) { query = query.BoostMatching(x => x.Categories.Match(category), 4); } //Apply a smaller but still significant boost to articles sharing tags with //the current page. foreach (var tagLink in page.TagReferences) { query = query.BoostMatching(x => x.TagReferences.Match(tagLink.ToString()), 2); } return query //Utilize/apply editorial settings for how desirable a certain page //is in the results. .BoostMatching(x => x.SearchPriority.Match(Priority.VeryLow), 0.1) .BoostMatching(x => x.SearchPriority.Match(Priority.Low), 0.3) .BoostMatching(x => x.SearchPriority.Match(Priority.High), 1.5) .BoostMatching(x => x.SearchPriority.Match(Priority.VeryHigh), 2.5) //Exclude the current page (which is naturally the most similar page). .Filter(x => !x.ContentLink.Match(page.ContentLink)) //Exclude unpublished and filter based on access rights and language. .FilterForVisitor() //Execute the query .Take(8) .GetContentResult(cacheForSeconds: 3600, cacheForEditorsAndAdmins: true); }
Display template for images when using EPiServer CMS and ASP.NET MVC
Given you have an URL property with a UI hint to indicate that the editor should select an image, like this:
[UIHint(UIHint.Image)] public virtual Url TopImage { get; set; }
Then you can make it render as an image by placing a partial view in the /Views/DisplayTemplates folder named Image.cshtml with the following content:
@model EPiServer.Url @if (Model != null && !Model.IsEmpty()) {<img src="@Model.ToString()" alt=""/> }
EPiServer editing delight challenge
In EPiServer 7 On-Page-Editing is the default way of editing a page. An editor clicks on a part of the page that represents a property and can update it using whatever type of editor (textbox, textarea, Tiny MCE etc) is configured for the property. All while almost instantly seeing the updated result on the page.
For us developers the live-preview way of editing pages in EPiServer 7 can present challenges. Especially when the design requires us to somehow modify whatever the editor has entered before rendering it.
In order to preserve our vocational pride and keep customers happy we need to ensure that the editors get a correct preview of the content.
Putting ourselves in the editors shoes, imagine that we edit a page and modify a couple of properties. All while checking how the page looks. Then we publish the page, only to find ourselves looking at something different from what the preview showed us. That wouldn't be very fun and we'd lose trust in the system.
On this topic and based on a simple case that I ran into not long ago, here's a challenge that will test your EPiServer development skills when it comes to rendering properties and your understanding of how EPiServer 7 CMS works.
The challenge
You have a page type class with two properties:
Name | Type | Element |
Heading | string | h1 |
Intro | string | p |
You should create a template for this page type that displays both properties. It should meet the following conditions:
- Both properties should be edited using a text area.
- If the Heading property contains line breaks those should be replaced with br tags when it's rendered, allowing editors to create headings that span multiple lines.
- If the Intro property happens to contain line breaks those should not be replaced. That is, editors should not be able to force new lines in the Intro property.
- Both properties should be editable by clicking on them in on-page-edit mode.
- Both properties should offer a realistic preview at all times.
- The preview of the page should not rely on a full refresh of the page. That is, methods whos name contain FullRefresh are not allowed.
- You are not allowed to change the types of the properties and you are not allowed to move them to a block.
Here's an example of how a page that meets these conditions should look in edit mode:
Sounds and looks pretty simple, right? As it turns out, while each requirement on its own is straight forward, combine them and it's not entirely obvious how to meet them all. Give it a try!
Solutions
Solutions can be found in separate articles:
EPiServer editing delight challenge - Web Forms solution
Starting with the simple stuff we render each of the properties using the Property control.
<EPiServer:Property PropertyName="Heading" CustomTagName="h1" runat="server" /><EPiServer:Property PropertyName="Intro" CustomTagName="p" runat="server" />
Then we make both properties editable using a text area by adding a UI hint to each of them.
//using EPiServer.Web; [UIHint(UIHint.Textarea)] public virtual string Heading { get; set; } [UIHint(UIHint.Textarea)] public virtual string Intro { get; set; }
Customise the rendering
Moving on to the next requirement, we need to replace line breaks in the Heading property with br tags before rendering it. To do so we can change the rendering of it from using the Property control to using a h1 tag that we turn into a control by adding runat="server" and an ID to it.
<h1 ID="headingControl" runat="server"></h1>
Next, in code behind, we set its InnerHtml to the modified value and make it editable in on-page-editing mode using the ApplyEditAttributes extension method.
//using EPiServer.Web; protected override void OnLoad(EventArgs e) { base.OnLoad(e); if (!string.IsNullOrEmpty(CurrentPage.Heading)) { headingControl.InnerHtml = CurrentPage.Heading.Replace("\n", "<br />"); } headingControl.ApplyEditAttributes<StandardPage>(x => x.Heading); }
At first it may seem like we've solved the challenge. Both properties are editable in on-page-edit mode using text areas and a line break in the Heading property is replaced with a br tag. However, let's look at what happens when we make a change to the Heading property:
The line break in the Heading property isn't replaced with a br when the new value is inserted into the page during editing. We're not meeting the requirement that both properties should offer a realistic preview at all times.
This happens because the CMS has no way of knowing that we're customizing the rendering of the property in our view. Instead it updates the DOM node that represents the property with what would be rendered by the Property control.
Fixing the preview
In order to fix the preview we could go back to rendering the property using the Property control and then modify how either all strings or string properties with a Textarea UI hint are rendered.
That would however violate the requirement that a line break in the Intro property should not be replaced with a br tag. Therefor we'll have to find a way to customize only the rendering of the Heading property. Luckily there are a few ways to do that.
One is to first render it using the Property control and specify a tag using RenderSettings:
<EPiServer:Property PropertyName="Heading" CustomTagName="h1" runat="server"><RenderSettings Tag="LineBreaked" /></EPiServer:Property>
Next we create a user control that inherits from PropertyControlBase<string> and add a TemplateDescriptor attribute to it in which we specify the same tag.
[TemplateDescriptor(TagString = "LineBreaked")] public partial class LineBreaked : PropertyControlBase<string> { }
Still in code behind for our custom property control we create a property that exposes the original value, which we can retrieve through the CurrentData property, modified the way we like.
protected string Formated { get { if (string.IsNullOrEmpty(CurrentData)) { return string.Empty; } return CurrentData.Replace("\n", "<br />"); } }
Finally, in the markup part of the user control we output the return value of the property.
<%= Formated %>
And with that the challenge is solved.
Alternative solution
Above we change how the Heading property is rendered by specifying a tag when rendering it with the Property control. This works well but it only modifies how the property is rendered, including preview, in a single place.
If we instead wanted to change how it's rendered everywhere where the Property control is used we could use an alternative approach - using UI hints.
[UIHint("LineBreaked")] [UIHint(UIHint.Textarea, PresentationLayer.Edit)] public virtual string Heading { get; set; }
In the above code we've added a second UIHint attribute to the Heading property. This UI hint has the same value as the tag that our custom property control supports, meaning that it will be rendered using our custom control.
However, we also want the property to be edited using a text area meaning we need to keep the existing UI hint, creating a conflict. To fix that we use an overload of the UIHintAttribute class' constructor and pass it a string representing a "presentation layer". Specifically the string exposed by the Edit constant in EPiServer's PresentationLayer class.
EPiServer editing delight challenge - MVC solution
Starting with the simple stuff we render each of the properties using the PropertyFor method.
<h1>@Html.PropertyFor(x => x.Heading)</h1><p>@Html.PropertyFor(x => x.Intro)</p>
Then we make both properties editable using a text area by adding a UI hint to each of them.
//using EPiServer.Web; [UIHint(UIHint.Textarea)] public virtual string Heading { get; set; } [UIHint(UIHint.Textarea)] public virtual string Intro { get; set; }
Customise the rendering
Moving on to the next requirement we create an extension method for strings that replaces line breaks with br tags. We make it return an IHtmlString so that the br tags won't be encoded.
public static IHtmlString ToLineBreakString(this string original) { var parsed = string.Empty; if (!string.IsNullOrEmpty(original)) { parsed = HttpUtility.HtmlEncode(original); parsed = parsed.Replace("\n", "<br />"); } return new MvcHtmlString(parsed); }
Now, how do we use this method? We could modify the view to render the property's value directly and add edit attributes to the h1 tag, like this:
<h1 @Html.EditAttributes(x => x.Heading)> @Model.Heading.ToLineBreakString()</h1>
At first it may seem like we've solved the challenge. Both properties are editable in on-page-edit mode using text areas and a line break in the Heading property is replaced with a br tag. However, let's look at what happens when we make a change to the Heading property:
The line break in the Heading property isn't replaced with a br when the new value is inserted into the page during editing. We're not meeting the requirement that both properties should offer a realistic preview at all times.
This happens because the CMS has no way of knowing that we're customizing the rendering of the property in our view. Instead it updates the DOM node that represents the property with what would be rendered by the PropertyFor method.
Fixing the preview
If you've read my article about how PropertyFor works you know that means it will replace it with what the DisplayFor method would return, meaning the default display template for strings.
To fix this we could create a display template named string or Textarea (matching the UI hint) and render the value with the help of our extension method there. Given that we update the view to again render the Heading property using either PropertyFor or DisplayFor we would then have fixed the preview issue.
That would however mean that a line break in the Intro property would also be replaced with a br tag, violating the requirement that it should not be.
What to do then? Seems like the requirements are conflicting. Perhaps all is lost and it's time to give up?
Not quite. We just need to sprinkle our code with some magic dust made exactly according to the recipe.
First we create a display template named LineBreaked that renders a string using our extension method.
@model string @Model.ToLineBreakString()
Next we add another UI hint to the Heading property, this time with the name of the display template.
[UIHint("LineBreaked")] [UIHint(UIHint.Textarea)] public virtual string Heading { get; set; }
That alone doesn't yield any effect as we now have two UI hints and, at least for me, the Textarea one will be used.
The final step that makes it all work is to modify the second UI hint so that it won't be used by PropertyFor or DisplayFor, only by the CMS when locating the approriate editor for the property. To do so we use an overload of the UIHintAttribute class' constructor and pass it a string representing a "presentation layer". Specifically the string exposed by the Edit constant in EPiServer's PresentationLayer class.
[UIHint("LineBreaked")] [UIHint(UIHint.Textarea, PresentationLayer.Edit)] public virtual string Heading { get; set; }
And with that the challenge is solved.
Alternative solution
The above solution works and will effect all of the places where the Heading property is rendered. There is however also an alternative, simpler, solution that will only effect the rendering in a specific place.
Instead of adding the additional UI hint and specifying presentation layer for the Textarea UI hint we can render the property using PropertyFor and specify a tag matching the display templates name.
<h1>@Html.PropertyFor(x => x.Heading, new { tag = "LineBreaked"})</h1>
Lessons learned from a small tech startup
Last week I spoke at the conference DevSum in Stockholm. The topic of my talk was the story of the company 200OK AB started by Marcus Granström, Henrik Lindström and myself and a number of lessons that I learned from that adventure.
In the talk I first told the company's story from start to end, or from idea to being acquired. I then covered a number of lessons learned. In this article I'll skip the details of the story but tell you about the lessons. I kindly ask the reader (that'd be you) to keep in mind that this is purely based on my experiences from one specific company, although I personally think that they may be applicable to many other situations as well.
Background
200OK was a company with a product called Truffler. Truffler was a Software-as-a-Service solution for free text search and content retrieval. Somewhat simplified we offered preconfigured ElasticSearch indexes "in the cloud" along with licenses for our custom .NET API.
Beyond making it easy to build traditional free text search functionality the product helped developers leverage the significant power and speed of ElasticSearch for querying. Using Truffler developers could very easily create functionality such as "the latest articles by a given author" or "number of products in a given category within a given price range" etc.
We started the company in 2011 and worked almost exclusively on our spare time. In 2012 we sold it to the Swedish CMS and e-commerce vendor EPiServer. The product is now marketed as EPiServer Find.
During the brief but intense life of our company I learned a lot of things. A lot of those technical, but the most valuable was non-technical. About running a company. About building and marketing a product. And about how sales work in the corporate domain.
Many of those lessons may seem obvious. However, there are many possible pitfalls when starting a company and building a product and it's hard to think of everything when you're in the thick of it.
This is a summary of the most important lessons that I learned.
Focus
It's important to focus on the things that add value to the company. For a startup without a product, or without a product that is yet so complete that it's possible to market it, that means to focus all efforts on developing the product.
I realize this may be stating the obvious but if you think about it there are a lot of things that you may think that you need, and therefor can be spending time on, when starting a new company besides developing your product.
Maybe you need to create a logotype and a web site. Maybe you need to pick and implement a good development process. While such things may be needed once you have a product, spending time on them before you actually need them prolongs your time-to-market.
Slower time-to-market may not seem like a problem for a company with no expenses or with enough capital to handle it. However, every day that you are developing your product without users is a day without feedback. Feedback from those that actually use the product in the wild. Each such day is a day that you spend guessing what the market needs.
In our case we managed to bring our product from idea to an initial marketable state in a couple of months. This was much thanks to the fact that we spent almost all of our time developing the product and either pushed peripheral problems to the side or found quick solutions to them.
Two such peripheral problems were related to design. While I'm an avid advocate of the need for great graphical design we didn't have much design skills between us. Also, while we needed some sort of graphical design for our logotype and website they weren't essential for our very technical product.
Instead of getting hung up on these problems or spending time trying to solve them ourselves we bought a logotype along with an idea for the product name from BrandCrowd. We also bought a simple, ready to use design for a Software-as-a-Service provider website.
In summary - focus on what you must have in order to reach your initial goals rather than what you think that you may need once you get there.
Tools
A company founded by developers has great potential! You can build anything! You may also have many opinions and ideas about how to build your product. Often such ideas involve tools of some sort. In a company founded by developers the opportunity to pick the optimal tools can be a dangerous trap when it comes to focusing on the core product development.
It's not uncommon for developers to start a project by setting up a build server or discussing what Scrum/Kanban/Whatever dashboard to use. I'm not going to argue that investing in tools and processes that speed up development isn't worthwhile. I am however going to say that in the early days of a startup you need to consider all costs, especially when the cost is time that could be spent on developing the product.
I think this is especially important in a company founded by developers. Why? Well, first of all in such a company you have the ability to create whatever tools you need. Second, developers are often not in a position to choose their own tools or processes during their regular work, adding a significant allure to choosing the "perfect" tools in an own company.
In my experience discussing what dashboard you are going to use, setting up a build server before you have anything to build or discussing in length what development stack to use (often ending up choosing something you want to learn rather than something you already know), while fun, is a perfect recipe for not building a product that will reach the market.
I'm not saying that you shouldn't invest in continuous integration and delivery or find a process for managing the development. However, as developers it may be tempting to solve such problems prior to actually having them.
Make sure that you spend your limited time and resources wisely. To do that you should use tools with no or low thresholds.
For us that meant, amongst other things, managing the backlog and work items in a simple spreadsheets in Google Docs, building our website on ASP.NET (which we knew well) and hosting our website on AppHarbor.
Communication
Communication is important.
No shit, you wrote a blog post saying that!?
Well, it is. However, what I really learned was that regular communication is key. For us, working part time with the company we quickly found that it was hard to keep everyone on the same page as we often didn't work at the same locations or during the same time of the day.
To tackle this we decided to have weekly meetings on Skype. Every Sunday at 10 PM we all got together on Skype and went through our backlog, discussed important decisions and how to proceed with our product. These meetings were mandatory and there were no excuses for not attending.
We of course also talked to each other at other times using different mediums and in various constellations, but the sunday meetings was our "anchor" in terms of communication. Besides the benefit of having everyone involved in a conversation at least once per week having a fixed time for it meant that it was easy to get an understanding from everyone else in our lives that we needed to spend time talking about what was going on in the company.
So, while it's obvious that communication is important keep in mind that it's easy to take it for granted only to suffer from a lack of communication. A fixed schedule for meetings where everyone can attend is an easy solution to that.
Conflicts
You are a few devs who has gotten together to start a company and fulfill your dreams of building an awesome product. You're likely to have some heated arguments over technical details of course, but having freed yourselves from the regular corporate BS the last things on your minds is that you may have to deal with conflicts in the workplace. Right?
What we found was that in the beginning everything went fairly smooth between the three of us. There where disagreements but they always revolved around different ways of achieving the same goal - making the product awesome.
However, after a number of months the high paced development done, often during late nights or weekends, started to take its toll. Combine that with us, at the time, not having a single paying customer to validate that we were doing something right and some of the same types of conflicts that we'd experienced working as employees started to surface.
For us the conflicts revolved around two related things. One was frustration that the product development had stalled. The second was feelings of imbalance in terms of how much effort each individual put into the company and the product.
Luckily we, after while, faced the issues that we had head on and discussed them. Once we did that it quickly became clear that there was an obvious solution. We needed to manage expectations.
While we had all agreed to putting in a lot of time and effort during the early days we hadn't defined what that meant. Later, when we all started to tire and were faced with other things that needed attention in our lives, we found ourselves in a situation were we spent drastically different amount of our time on building the company.
So, having recognized the issue we found a simple solution. We decided that each of us should spend ten hours per week on things that added value to the company. Had we been working full time this number would probably have been forty instead.
Having defined how much effort we could expect from each other we got rid of many of the "unfairness" conflicts. This also brought another benefit as we could now more easily estimate the development pace, making it easier to plan and prioritize new features.
What I took away from this was that no matter what you're doing, if you are more than one people starting something, there is bound to be tough times and at those times conflicts will surface. It's hard to predict what those will be exactly, but some may be more or less obvious that they will happen unless they are prevented. In a company founded by a number of friends or associates I believe that the key to preventing many conflicts is to manage expectations regarding how much effort each person should put in to the company. It's also important to discuss what happens if someone doesn't come through on what you have agreed on, or does the opposite and works much harder.
Legal
Starting a company means doing business. Doing business means there are legal aspects to consider. That's especially true for a company that builds a product who's value lies in the intellectual property rights, such as software.
There are many legal aspects to consider, such as:
- Regulating ownership of the company, or of the product if you have yet to start a company.
- Intellectual property rights. For instance, if you work on the product(s) while still under employment you should verify that all your employers are OK with what you are doing. Also, bringing in a friend to write some code or do some other work may seem like a good idea but you should beware of what that means in terms of ownership of the code. Likewise, maybe you start out developing the product during a hackathon in a larger group and then only a few of you continue working on it. If so, you need to regulate the IP rights somehow.
- Intellectual property rights and licenses for any third party software that you use.
- General conditions/terms of use/end user license agreements for your product.
It's of course possible, and tempting, to start a company, build and sell a product without considering any of these things. However, once you start getting customers there will be questions regarding some of these things. Not to mention if your company tries to raise funding of is acquired. In other words, deal with the boring legal stuff early on.
While it's of course best to have the legal aspects taken care of by a kick ass legal team that costs money that you may not have. For many things however that's not *really* needed from the start. Agreeing to things and documenting things in plain English in e-mails gets you a long way. It gives you something to fall back on in case of conflicts and serves as a good starting point once you later on bring in lawyers that write things in legal-speak.
Sales and marketing
Having a great idea and a great team of developers is a great start when building a company. However, while it's tempting to believe that "A great product sells itself" that's not really true. First of all prospective buyers or partners need to know that the product exists. Second they need to recognize that they need it.
We introduced our product at a local user group in Stockholm. It took almost four months after that before we had our first paying customer. While we did a lot of other marketing activities after that, we later found out that this, the first customer, had found us thanks to that first presentation at the user group.
That is, if we hadn't spent time on showing off our product early on we never would have gotten that first, and for us very important, customer. Marketing is important.
That doesn't mean that you have to spend a lot of money on it. Talking at user groups, writing on Twitter, sending out press releases, being interviewed on podcasts, putting up vides on YouTube and drinking beers with people in the business doesn't have to cost you a dime.
However, if you don't do any of that, or spend a pile of cash on ads, no one will know that your product exists. If no one knows that your product exists no one will buy it.
Now, given that you do have a plan for how to market it, that doesn't necessarily mean that anyone will buy it anyway. Especially if you intend on selling it to other companies. You see, for anything but the cheapest off products most companies are used to being sold to.
That is, while you may know that your product is a no-brainer for the customer and you've priced it in such a way that they will obviously make money from buying it that's not enough. You need to convince the customer.
Doing that probably involves meetings, showing and handing over PowerPoint presentations, presenting feature comparisons with any competitors that you may have and discussing sales agreements back and forth.
So, you will likely have to spend quite a lot of time not only marketing your product but also on selling it. If you don't plan on going bankrupt thanks to selling your product you need to take those efforts into account when you set the price for your product.
Joel Spolsky wrote a great blog post about pricing software in 2004 titled Camels and Rubber Duckies. In that post he sums up our experiences pretty well:
"[...] big companies protect themselves so well against the risk of buying something expensive that they actually drive up the cost of the expensive stuff, from $1000 to $75000, which mostly goes towards the cost of jumping all the hurdles that they set up to insure that no purchase can possibly go wrong."
Besides including your costs for marketing and selling your product when setting its price you should also beware of the costs in terms of time and what that means for product development. For us, being a company made up of three developers we found that once our product started to get some traction on the market we weren't really developers anymore. We had become salesmen.
We spent so much time in meetings, creating presentations and other sales material and writing sales agreements that there simply wasn't much time left for any real development. While that may seem like a nice problem to have, we were making money after all; we weren't making enough money to hire someone to take care of the sales for us.
So, the next time I'm starting a product company together with a few developers I'm going to insist on also having a partner that is responsible for sales.
Conclusion
Building and designing your own product without any external agendas or constraints is something many developers dream of. True enough, starting 200OK and building a product together with two other great developers is one of the most fun things I've ever done. However, we quickly found that the actual product development is only a small part of building a business.
To summarize, the key takeaways from our little startup adventure, and this article is:
- Focus on building and bringing your product to the market. Using a startup as a place to try out new things, at least in the early stage, is a great way of not building and not selling a product.
- Discuss and put in writing your expectations for the company and for each other. If your doing the development on the side from regular work, decide how much time you should spend on it per week.
- Don't take communication for granted. If you're building the company on your spare time or are not located together find a fixed interval for meetings that work for everyone.
- Have a plan for marketing and sales. If you're building a product that you plan on selling to companies and its price is above almost free make sure to include sales efforts in the price.
Most importantly of all - if you don't have a way to sell your product you won't have a business. So, if you're into doing startup hackathons or thinking about starting a company with a few devs, consider bringing someone that knows how to sell along as well.
Photo credits