Quantcast
Channel: Joel Abrahamsson
Viewing all 78 articles
Browse latest View live

Cool new features in the Truffler .NET API

$
0
0

Note: Since writing this post the company behind Truffler, 200OK, has been sold to EPiServer and the product Truffler has been replaced by EPiServer Find. Most of the content of this blog post is however applicable to EPiServer Find as well. For questions regarding Find, be sure to visit the forum on EPiServer World.

It’s been a while since I last blogged about Truffler. We’ve been kind of busy with sales, supporting customers and building some really cool new major features. But that doesn’t mean that our .NET API hasn’t evolved. On the contrary, it has been enhanced with quite a few new features, often driven by demand from developers (thanks!). Some of my favorite new features include geographical distance facets, the Include method and the AnyWordBeginsWith method.

Geographical distance facets

As I’ve previously written about ElasticSearch and the Truffler .NET API  has some geo search capabilities that outshines most other search solutions. Just check out the map example ;-). Now however being able to filter and sort by geographical distance as well as filter by geographical bounding boxes has been complemented with support for geographical distance facets.

geographical-distance-facetA geographical distance facet requires a from-location and a number of ranges (as in X kilometers from and Y kilometers to) and returns the number of documents that fall within each of those ranges. To check out how it works in detail be sure to read the documentation, but here’s a snippet of example code that could be used to build filtering functionality such as the one in the image to the right here (in which I used EPiServer CMS’ geolocation API to find the users location).

client.Search<TravelDestination>()
  .GeoDistanceFacetFor(x => x.Coordinates, 
    UserLocation,
    new NumericRange { From = 0, To = 1000 },
    new NumericRange { From = 0, To = 2500 },
    new NumericRange { From = 0, To = 5000 },
    new NumericRange { From = 0, To = 10000 },
    new NumericRange { From = 0, To = 25000 })

The AnyWordBeginsWith filter method

Truffler has always supported filtering on string values by exact match, case insensitive  exact matches as well as by matching the beginning of strings. The AnyWordBeginsWith method however filters by matching the beginning of any word in a string. This of course comes in pretty handy when building autocomplete or search-as-you-type-functionality.

client.Search<BlogPost>()
  .Filter(x => x.Title.AnyWordBeginsWith("Ban"));

An important word of warning though, with great power comes great responsibility and this method can put even the fastest of search engines under strain if used on long texts so we only recommend it’s usage on titles and other short string values. Luckily, in cases such as autocomplete and the like it’s only short string values we’d like to use it on anyways :-)

The Include method

Another of my favorites is the Include method. This method has a similar signature as the BoostMatching method that I’ve previously written about. It too accepts a filter expression as an argument, meaning that we can pass it however complex criteria as we like. But instead of boosting hits that match a filter it ensures that documents that match it is included in the search result, even if they don’t match the free text search query and/or previously applied filters.

What’s it good for? Well, combine it with the For method for free text search and pass in a filter that uses the AnyWordBeginsWith method and you’ve got yourself a pretty cool search-as-you-type-functionality in about four lines of code ;-)

var q = "Bar";

searchResult = client.Search<BlogPost>()
  .For(q)
  .Include(x => x.Title.AnyWordBeginsWith(q), 1)
  .GetResult();

Facets for EPiServer categories with ease

Last but not least I’d like to mention a little utility method that we’ve added to our EPiServer CMS integration; the CategoriesFacet method. This little gem adds a request for a facet for EPiServer categories to a search request. Once we execute the search request and get a result a method with the same name can be used to extract an IEnumerable<CategoryCount>. Each CategoryCount contains a category and the number of pages in that category that matched the search query.

var result = EPiSearchClient.Instance
    .Search<PageData>()
    .For("Banana")
    .CategoriesFacet()
    .GetPagesResult();

foreach (var categoryCount in result.CategoriesFacet())
{
    var categoryName = categoryCount.Category.Name;
    var count = categoryCount.Count;
}

Building a search page for an EPiServer site using Truffler

$
0
0

Note: Since writing this post the company behind Truffler, 200OK, has been sold to EPiServer and the product Truffler has been replaced by EPiServer Find. Most of the content of this blog post is however applicable to EPiServer Find as well. For questions regarding Find, be sure to visit the forum on EPiServer World.

This is the first in a series of posts about building search functionality for EPiServer CMS based sites using Truffler. While Truffler can be used for many things such as querying for data and finding related content we’ll here focus on the traditional search page.

To have some context to work in I’ll start by walking through the characteristics of a fictive EPiServer site built with Page Type Builder and EPiImage. We’ll then look at how to add the Truffler .NET API to it and build a search page from the ground up. Be ware that while I won’t go into all of the nitty gritty details of the .NET API I’ll often cover multiple solutions to a given problem.

An example site

FlyTruffler-page-typesIn this tutorial we’ll use a site for fictive company, the airline Fly Truffler. It’s site is straight forward and consists of only a few page types. Most notable are the Standard Page and the Destination page types. The first is used for articles of various kinds, such as travel information and news bulletins. The Destination page type is used to describe the various destinations that the airline flies to.

The standard page type has three properties: MainIntro, MainBody and ListingRoot. The latter can be used by editors to set a page whose children should be listed below the main body.

FlyTruffler-standard-page-page-type

An example of a page using the Standard Page page type used for an individual news item:

FlyTruffler-standard-page-used-for-news

An example of a page using the Standard Page page type with a listing:

FlyTruffler-standard-page-used-for-listing

The Destination page type has more properties. However, for the search page we’ll mainly be interested in the MainIntro and MainBody property. The others, such as coordinates would come in handy when building some cool geosearch though :)

FlyTruffler-destination-page-type

An example of a page using the Destination page type:

FlyTruffler-destination-page

FlyTruffler-page-structureThe site structure is straight forward. We have four main sections: Destinations, Travel Information, News and About Fly Truffler. The three latter sections are standard pages themselves used to group the child pages below them. This means that a standard page below Travel Information is conceptually a “Travel Information page” although it’s page type is the same as those under for instance the News section.

Page Type Builder

The site uses Page Type Builder to define page types in code. While Truffler doesn’t require Page Type Builder and has no dependency to it, meaning that it will play well with the typed pages support in the next version of the CMS too, it’s really when using Page Type Builder, or other mechanisms for typed pages, where Truffler really shines.

Getting started

In order to build a search page with Truffler we’ll need two things: a Truffler index and the Truffler integration for EPiServer CMS. A development index can be created on the Truffler website and the EPiServer Integration can be installed using NuGet after adding EPiServer’s NuGet feed. I won’t go into any details here as the process is straight forward and well documented.

Initial indexing

Indexing-job-linkWith an index created and the EPiServer integration referenced and configured we need to do an initial indexing. This is done by triggering Truffler’s scheduled job for indexing in EPiServer CMS’s admin mode.

The job usually completes within seconds on a small site and after it’s complete we can ensure that everything went OK by looking at the job’s history. In case something went wrong we’ll see a message about failed batches. This means that one or more batches of pages have failed to be indexed. If this happens we need to fix the problem and run the job again. The most common problems are pages that have properties defined as reference types while there’s no value causing a null reference exception. This is easily corrected by changing the (code) property’s type to the nullable equivalent of the reference type, such as from int to int?, which is a best practice when using typed pages anyway.

Indexing-job-complete-message

Note that we only need to run the job to index existing content or when we’ve made modifications that require the index to be updated. The EPiServer integration will listen for events from EPiServer’s DataFactory and index pages whenever they are published meaning that pages are indexed in close to real-time when they are published.

A first implementation of the search page

In order to display search results we’ll need input from the user so the first step in creating the page is to add a textbox and a button:

<asp:Panel DefaultButton="btnSearch" runat="server">
    <asp:TextBox ID="tbQuery" runat="server"></asp:TextBox>
    <asp:Button ID="btnSearch" runat="server" Text="Search"/>
</asp:Panel>

If the user has entered a search query we’ll want to display it and the results. Therefore we’ll begin by adding some initial code to the markup of the search page that checks if a codebehind property named Query has a value. If so it displays the value, the number of hits (hard coded) and a so far empty list which we’ll later render the search results in.

<% if (Query != null) { %> 
<p class="hitcount">
    Found <strong>123 hits</strong> for <strong>"<%: Query %>"</strong>.
</p>
<ul id="search-results">
</ul>
<% } %>

In order to handle user interaction and implement the Query property we head into the the code behind file for the search page template. There we add the Query property and have it return the value of a query string parameter named “q”:

protected string Query
{
    get { return Request.QueryString["q"]; }
}

Next we override the OnLoad method (or implement the Page_load event handler) to set the textbox’s value if the page isn’t post back. We also bind an event handler to for the button that will redirect to the same page but with the value from the textbox as the query string parameter “q”.

protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);
    if (!IsPostBack)
    {
        tbQuery.Text = Query;
    }

    btnSearch.Click += BtnSearchOnClick;
}

private void BtnSearchOnClick(object sender, EventArgs eventArgs)
{
    var redirectUrl = Request.RawUrl;
    redirectUrl = UriSupport.AddQueryString(
        redirectUrl, "q", tbQuery.Text);
    Response.Redirect(redirectUrl);
}

With this code and the markup in place we now have a search page that responds to user interactions and displays the entered search query. With some styling it should look something like the image below after entering something in the textbox and pressing the search button.

FlyTruffler-search-page-1

Searching

Next we need to show the correct number of hits instead of the hard coded value and display some search results. In the simplest of situations we could search for all pages and get the actual PageData objects that match the search query. This can easily be done using the EPiServer CMS integration’s GetPagesResult method. Doing so is great in other types of querying situations, especially since the GetPagesResult method will also cache the result. However, for a search page such as this we’ll probably want to display other things than what’s necessarily contained in the pages, or subsets of what is, such as highlights from text that match the search query.

Therefore we’ll create new little class that we’ll use as a view model. We’ll still be searching for pages but we’ll retrieve the results as instances of this class which is tailor made for display on the search page.

public class SearchHit
{
    public string Title { get; set; }
    public string Url { get; set; }
    public string Text { get; set; }
}

Next we add a property of type SearchResults<SearchHit> to the code behind of our search page. The SearchResults<T> class is a Truffler class so we’ll need to add a using statement for Truffler. While we’re at it we also add a couple of using statements for the EPiServer integration which will come in handy later on.

using Truffler;
using Truffler.EPiServer;
using Truffler.EPiServer.Cms;

//In the class
protected SearchResults<SearchHit> Results { get; set; }

We can now implement the number of hits text that we earlier hard coded in the markup. The total number of matching hits is exposed by the SearchResults<T> class’ TotalMatching property.

Found <strong><%= Results.TotalMatching %> hits</strong> 
for <strong>"<%: Query %>"</strong>.

The SearchResults<T> class implements IEnumerable<T> meaning that we can iterate over the search hits in it with, for instance, a simple for each loop:

<ul id="search-results">
<% foreach (var hit in Results) { %>
    <li>
        <h4><a href="<%= hit.Url %>"><%= hit.Title %></a></h4>
        <p>
            <%= hit.Text %>
        </p>
    </li>
<% } %>
</ul>

We now have everything in place for displaying search results. There’s just one thing missing: searching. We’ll begin by creating an empty method named Search that we execute given that the page isn’t post back.

protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);
    if (!IsPostBack)
    {
        tbQuery.Text = Query;
        Search();
    }

    btnSearch.Click += BtnSearchOnClick;
}

private void Search()
{
            
}

The objective of the Search method is to search for pages that matches the query, or search text, entered by the user and which is exposed by the Query property. Matching pages should be projected to the SearchHit class and the Results property should be given a value. We’ll begin by searching only for pages of the standard page page type.

When using Truffler’s EPiServer integration all interactions with the search engine is typically done through the singleton exposed by Truffler.EPiServer.EPiSearchClient.Instance. To create a search query we use the Search method specifying what type to search for as a type parameter. In order to search based on a text inputted by a user we can use the For method. In order to project from the StandardPage type to our view model, SearchHit, we use the Select method, just like we would have with LINQ. Finally we execute the search request using the GetResult method and assign the result to the Results property.

private void Search()
{
    var result = EPiSearchClient.Instance
        .Search<StandardPage>()
        .For(Query)
        .Select(x => new SearchHit
            {
                Title = x.PageName,
                Url = x.LinkURL
            })
        .GetResult();

    Results = result;
}

It may not be pretty just yet, but we now have working search page in the sense that search results are displayed for the user. It should look something like this:

FlyTruffler-search-page-2

Summary

We’ve now built a very basic search page that displays search results matching a search query entered by a user. A lot of the code so far has not been specific to Truffler but has been necessary to display search results and handle user input. The most important, and Truffler specific part of this post was the last code snippet, the Search method. In it we use the EPiSearchClient class to create a search query for a specific type of object or page type using the Search<T>() method. We then add a free text search query to it using the For(string) method. Finally we provided a projection from the StandardPage type to a view model type using the Select method, just like we might have done with LINQ and executed the search query with the GetResult() method.

The search page is pretty basic at this point but and hardly production ready. We’ll fix that in the next post. There we’ll look at how we can improve the free text search by using stemming and how we can search over multiple types. We’ll also improve it visually by adding excerpts of text with keywords highlighted to the search hits as well as implement paging functionality.

Building a search page for an EPiServer site using Truffler - Part 2

$
0
0

Note: Since writing this post the company behind Truffler, 200OK, has been sold to EPiServer and the product Truffler has been replaced by EPiServer Find. Most of the content of this blog post is however applicable to EPiServer Find as well. For questions regarding Find, be sure to visit the forum on EPiServer World.

In the previous post we covered building a very basic search page using Truffler for an EPiServer CMS based site for a fictive airline. The search page displayed search results based on user input but lacked some very basic functionality needed by any self-respecting search page. In this post we’ll fix that.

Stemming and specifying fields to search in

In the previous post we managed to find pages that matched a given search term using the Search and For methods, like this:

EPiSearchClient.Instance
    .Search<StandardPage>(Language.English)
    .For(Query)

With that code the search query will be executed in a language neutral way over a special field containing all indexed values for each page. To improve search results we could tell the search engine what language we’re searching in, English in our case. By doing so the search engine will use stemming, meaning that not just exact matches of words but also abbreviations, for instance car and cars, will match. To do so we pass an instance of the Language class to the Search method. To make that convenient the Language class has a static property returning such an instance for each supported language.

var result = EPiSearchClient.Instance
    .Search<StandardPage>(Language.English)
    .For(Query)

In order for stemming to be used we also need to explicitly specify what fields to search in. That can be done using the methods InField and InFields. The InField method requires a lamda expression specifying what field to search in. The InFields method does the same, but accepts multiple expressions. In our case the page’s name, preamble (MainIntro) and body text (MainBody) seems suitable to search in.

var result = EPiSearchClient.Instance
    .Search<StandardPage>(Language.English)
    .For(Query)
    .InField(x => x.PageName)
    .InFields(x => x.MainIntro, x => x.MainBody)
    .InAllField()

Note that we use a combination of the InField and InFields method here. That’s by no means necessary and I’ve only done so to illustrate that it’s possible and to set the stage for some tweaking that we may discuss in a future post. Anyway, but specifying a language to use and a number of fields to search in the search engine will match keywords and phrases in those fields in a language sensitive way.

However, for some fields we may not care about stemming, or we may for some other reason want to search in the field with all values. We can do that using the InAllField method.

With a language parameter passed to the search method, and after specifying a few fields to search in, including the field with all values the Search method looks like this:

private void Search()
{
    var result = EPiSearchClient.Instance
        .Search<StandardPage>(Language.English)
        .For(Query)
        .InField(x => x.PageName)
        .InFields(x => x.MainIntro, x => x.MainBody)
        .InAllField()
        .Select(x => new SearchHit
            {
                Title = x.PageName,
                Url = x.LinkURL
            })
        .GetResult();

        Results = result;
}

Searching over multiple types

So far we’ve limited the search functionality to searching for pages of the StandardPage type. In many situations it’s desirable to not search for all page types as some may not be of interest to visitors, but as we saw in the previous post, there is at least one other page type we’d like to search for, the Destination page type.To do so we have two options.

The IncludeType method

The first option is to explicitly include pages of that type using the IncludeType method. This method has two generic type parameters, the first specifying the result type, SearchHit in our case, and the second specifying the type to include. It also requires a lambda expression with a projection from the included type to the result type. This means that we could include pages of the DestinationPage type by modifying the search method like this:

private void Search()
{
    var result = EPiSearchClient.Instance
        .Search<StandardPage>(Language.English)
        .For(Query)
        .InField(x => x.PageName)
        .InFields(x => x.MainIntro, x => x.MainBody)
        .InAllField()
        .Select(x => new SearchHit
            {
                Title = x.PageName,
                Url = x.LinkURL
            })
        .IncludeType<SearchHit, DestinationPage>(x => new SearchHit
        {
            Title = x.PageName,
            Url = x.LinkURL
        })
        .GetResult();

        Results = result;
}

Inheritance

The IncludeType method can come in handy when searching over very different types, but in our case there’s a far easier solution. We can simply let both the StandardPage type and the DestinationPage type inherit from a common base class and utilize the inheritance support in Truffler’s .NET API. Common to both types are the MainIntro and MainBody properties so we begin by creating a an abstract base class with those that we let both types inherit from.

public abstract class EditorialPageBase : TypedPageData
{
    [PageTypeProperty(EditCaption = "Preamble", 
        SortOrder = 10, 
        Type = typeof (PropertyLongString))]
    public virtual string MainIntro { get; set; }

    [PageTypeProperty(SortOrder = 20)]
    public virtual string MainBody { get; set; }
}

With the common base class extracted we can modify the search method to search for pages of that type instead of using the IncludeType method:

private void Search()
{
    var result = EPiSearchClient.Instance
        .Search<EditorialPageBase>(Language.English)
        .For(Query)
        .InField(x => x.PageName)
        .InFields(x => x.MainIntro, x => x.MainBody)
        .InAllField()
        .Select(x => new SearchHit
            {
                Title = x.PageName,
                Url = x.LinkURL
            })
        .GetResult();

        Results = result;
}

Note that after introducing the base class and modifying the Search method we need to run the re-indexing job in order to get any search results as we’re relying on a type hierarchy that wasn’t there when the pages were initially indexed.

Highlighting

We’re now searching, with stemming, over multiple types, projecting search results to a common result item class. So far each search result item only contain a linked title though. Let’s extend them to also include a snippet of text. There’s several ways of doing that with Truffler. One would be to fetch a reference to each matching page and get the text, such as the MainIntro property, from the actual page. That works great in scenarios where we want to list pages based on other criteria than free text search, but for the classical search page we’d typically want a more context sensitive solution, highlighting.

Highlighting means that a part, or all, of the text is returned with keywords from the search query encased in HTML tags, em tags by default, that make them stand out. In order to retrieve highlights we need to specify at least one field, such as the MainIntro property, from which we want to extract them. There are multiple ways of doing that using the .NET API but the by far easiest is to use a special method named AsHighlighted in our existing projection expression.

private void Search()
{
    var result = EPiSearchClient.Instance
        .Search<EditorialPageBase>(Language.English)
        .For(Query)
        .InField(x => x.PageName)
        .InFields(x => x.MainIntro, x => x.MainBody)
        .InAllField()
        .Select(x => new SearchHit
            {
                Title = x.PageName,
                Url = x.LinkURL,
                Text = x.MainIntro.AsHighlighted()
            })
        .GetResult();

        Results = result;
}

In the above code we’ve modified the Search method to populate a third property in the returned search hits with the value of the MainIntro property with keywords highlighted. It’s important to note that while it may look like we’re doing the highlighting in memory on the web server we’re not. Instead the .NET API intercepts the AsHighlighted call and works some magic to the search query, instructing the search engine to return highlights for the MainIntro field.

In fact all of the code prior to the GetResult method call only builds up a search query on the web server side and the rest, with a very limited set of exceptions happen on the search engine side. An example search results listing now looks like the image below.

FlyTruffler-search-page-3-with-highlights_thumb[1]

In the above example almost all of the matching pages had the search term (beaches) in their MainIntro properties. But one of them didn’t and therefor no excerpt is displayed. We can fix this in the projection expression by using the original text if no highlight is returned:

Text = !string.IsNullOrWhiteSpace(x.MainIntro.AsHighlighted()) 
    ? x.MainIntro.AsHighlighted() 
    : x.MainIntro

A better solution would be to not only retrieve highlights from the MainIntro property but from the MainBody property as well. This could be achieved using the below code:

Text = !string.IsNullOrWhiteSpace(x.MainIntro.AsHighlighted()) 
    ? x.MainIntro.AsHighlighted()
    : x.MainBody.AsHighlighted(new HighlightSpec
        {
            FragmentSize = 300, 
            NumberOfFragments = 1
        })

With the above code we use the MainIntro propertys value given that it’s returned with highlights, otherwise we use highlights from the MainBody property. By default the entire string is returned when using the AsHighlighted method which works well for the MainIntro property but produces way too long results from the MainBody property.  We therefore give the AsHighlighted call for MainBody an instance of the HighlightSpec class setting the FragmentSize to 300, ensuring that the returned text won’t be much longer than 300 characters. In order for this setting to take effect we also specify that we want a single fragment of text returned.

Indexing a special property for highlighting

Using multiple invocations of the AsHighlighted method works well, but it looks a bit messy. In most situations an easier and better approach would instead be to index a special property from which we fetch our highlights. Let’s therefor add a new, non-EPiServer-property, to the base class for page types that we created before:

public virtual string SearchText
{
    get { return MainIntro + " " + MainBody; }
}

After adding this property and reindexing (as we’ve modified what should be in the index without actually making any changes to the pages) we can simplify our projection expression:

Text = x.SearchText.AsHighlighted(new HighlightSpec
    {
        FragmentSize = 300, 
        NumberOfFragments = 1
    })

Using the AsCropped method as fallback

The highlighting part of the projection to SearchHit again looks better, and the approach is quite flexible as any class that inherits from our base class can override the SearchText property to include or exclude properties. There’s just one problem. What if none of the included properties contain a keyword from the search query? Remember that we’re searching in the field with all values meaning that we could for instance get a hit for a page based on the names of one of its categories. One approach would of course be to only search in the fields we’re highlighting. Another would be to retrieve a part of the text without highlights. That can be achieved using another special method named AsCropped:

Text = FirstNonEmpty(
    x.SearchText.AsHighlighted(new HighlightSpec
        {
            FragmentSize = 300,
            NumberOfFragments = 1
        }),
    x.SearchText.AsCropped(300))


//... Helper method used above
private string FirstNonEmpty(params string[] texts)
{
    foreach (var text in texts)
    {
        if (!string.IsNullOrEmpty(text))
        {
            return text;
        }
    }
    return "";
}

Just like with the AsHighlighted method the AsCropped method may look like it does something on full string value on the web server while in fact the cropping is done on the search engine side, meaning that the full length of the text doesn’t have to be sent over the wire.

Paging

The search results now look pretty good with excerpts of text from the pages. There’s just one thing left to complete the basic search functionality – paging. Contrary to a database query, but similar to many other search engines, the search engine will default to returning the ten first hits. Changing how many hits should be returned and how many to skip is done using the Take and Skip methods, just like we’re used to from LINQ. With that said I won’t bore you with the details of how to implement paging functionality as that, apart from the fact that we can use Skip and Take, has little to do with Truffler. However, here’s one implementation Ler

A paging control – markup

<ul>
    <% if (ActivePageNumber > 1) { %>
        <li>
            <a href="<%= GetPageUrl(ActivePageNumber-1) %>">
                Prev
            </a>
        </li>
    <% } %>
    <% for (int page = 1; page <= NumberOfPages; page++) { %>
        <li>
            <a href="<%= GetPageUrl(page) %>" class='<%= page == ActivePageNumber ? "active" : ""%>'>
                <%= page %>
            </a>
        </li>
    <%} %>
    <% if (ActivePageNumber < NumberOfPages) { %>
        <li>
            <a href="<%= GetPageUrl(ActivePageNumber+1) %>">
                Next
            </a>
        </li>
    <% } %>
</ul>

A paging control – code behind

public Paging()
{
    //Set default values, overridable by setting the properties
    QuerystringKey = "p";
    PageSize = 10;
}

public string QuerystringKey { get; set; }

public int PageSize { get; set; }

public int ItemCount { get; set; }

public int ActivePageNumber
{
    get
    {
        var pageNumber = 1;
        if (!int.TryParse(
            Request.QueryString[QuerystringKey], 
            out pageNumber))
        {
            pageNumber = 1;
        }
        return pageNumber;
    }
}

public int NumberOfPages
{
    get { return (ItemCount + PageSize - 1)/PageSize; }
}

protected string GetPageUrl(int pageNumber)
{
    return UriSupport.AddQueryString(
        Request.RawUrl, 
        QuerystringKey, 
        pageNumber.ToString(CultureInfo.InvariantCulture));
}

Using the paging control

With the above user control added to our search page we can easily implement paging by adding three lines of code to the Search method:

private void Search()
{
    var result = EPiSearchClient.Instance
        .Search<EditorialPageBase>(Language.English)
        .For(Query)
        .InField(x => x.PageName)
        .InFields(x => x.MainIntro, x => x.MainBody)
        .InAllField()
        .Select(x => new SearchHit
            {
                Title = x.PageName,
                Url = x.LinkURL,
                Text = FirstNonEmpty(
                    x.SearchText.AsHighlighted(new HighlightSpec
                        {
                            FragmentSize = 300,
                            NumberOfFragments = 1
                        }),
                    x.SearchText.AsCropped(300))
            })
            .Take(Paging.PageSize)
            .Skip((Paging.ActivePageNumber - 1) * Paging.PageSize)
        .GetResult();

    Results = result;
    Paging.ItemCount = result.TotalMatching;
}

Summary

We now have a full blown search page. Free text search is made in a language sensitive way. A subset of the content is displayed for each search result with keywords highlighted. We also have paging functionality.

We’ve seen that there are a number of ways to work with highlights and projections. In my opinion this flexibility is great, but in most situations a recommended pattern is to index a separate field/property with text suitable for highlighting as that makes the search query easier to write.

While we now have a pretty good search page in terms of free text search and listing of results there’s more we can do to make it better and more powerful for users, especially by the use of facets. We’ll look at that in the next post.

On selling 200OK and Truffler to EPiServer

$
0
0

Last week it was announced that EPiServer has acquired 200OK AB. As one of the founders, but also as a developer often working with EPiServer products, I think this is great!

While providing a great search and content retrieval solution for EPiServer products wasn’t our only goal it certainly was an important one. For me personally, having previously worked on Page Type Builder, it seemed like a logical next step to offer a strongly typed way to build querying and search functionality for EPiServer CMS. Having built such a solution we of course wanted to bring the Truffler goodness to every single EPiServer site out there. And what could be a better way of doing that than making it an EPiServer product? :)

I also believe that this is great for our existing customers. While we’ve certainly enjoyed building the product at a rapid pace EPiServer will bring stability and credibility. EPiServer will also be able to offer more in terms of service level agreements than we could being a small company hosting on Amazon.

As for EPiServer I think it’s great that they will now be able to offer a modern search and content retrieval solution for its product that reaches beyond the crawler based search engines that have been dominating the market thus far. I strongly believe that this will enable both EPiServer and partner developers to build some truly great things. For developers, for editors and for end users.

One question that I’ve gotten a lot since the announcement is whether we, the founders of 200OK will continue to work with the product, and whether I’ll now work for EPiServer. The other two founders, Henrik and Marcus, will be working for EPiServer. As for me, I won’t be employed by EPiServer.

While I’d certainly enjoy working with the many very smart people at EPiServer I recently started running my own consulting company working as a freelancer, and that’s an exciting journey that I’d like to continue on for a while. With that said I’ll continue working on Truffler for a while, ensuring a smooth transition for it into the EPiServer product family. And given my history in the EPiServer eco system I think you’ll believe me when I say that I’ll continue to be involved in one way or another :)

Extending ASP.NET MVC Music Store with elasticsearch

$
0
0

elasticsearch-logoLast week I held a presentation at DevSum12 titled “elasticsearch For .NET Developers”. In it I talked about how we can use search engines in general and elasticsearch in particular to solve many types of querying problems.

To illustrate this and to demonstrate how elasticsearch can be used from .NET I did a demo where I extended the ASP.NET MVC Music Store project with free text search capabilities. I also rewrote the genre view and the genre menu to use elasticsearch instead of a database queries. This post is a write up of that demo.

Running elasticsearch

Setting up and running an elasticsearch server couldn’t be easier. Simply download the latest version, unzip it and run bin/elasticsearch.bat from a the console.

console

A .NET client API – NEST

elasticsearch exposes a great JSON based REST API. However, to interact with it easily from .NET we’ll use a client API. There are several such APIs for .NET, not to mention other languages. As I’m one of the co-founders of Truffler, a SaaS solution for search and content retrieval based on elasticsearch I’d of course love to use the Truffler .NET API. But as one key point of that API is to make it even easier than it already is to interact with elasticsearch it isn’t a good fit for this post.

Other .NET APIs include ElasticSearch.NET and PlainElastic.Net. In this post we’ll use a third one, called NEST, which seems to be the open source .NET API for elasticsearch with the most traction. It’s also well suited for this tutorial as it maps closely to elasticsearch’s REST API while utilizing some of the strengths of C#.

To add NEST to the ASP.NET MVC Music Store project we first need to download and compile it. That may seem tedious but it’s actually straight forward. Simply download the source code from GitHub, open it up in Visual Studio and compile. Once compiled reference the Nest, Fasterflect and Newtonsoft.Json assemblies in the Music Store project.

Indexing albums

Before we build search functionality we need to populate an index. For a real production site we’d probably like to index albums when they are added, updated or removed. However, it’s usually also a good idea to have the ability to index all content. As this post is about building search functionality we’ll settle for building just that, functionality to index all albums.

The ASP.NET MVC Music Store project consists of six controllers. One of these, the StoreManagerController class, is used for admin functionality so it seems reasonable to place an action for indexing, or re-indexing, all albums there. We’ll name it ReIndex.

As a first step we modify the StoreManagerController by adding a using statement for NEST and by adding the ReIndex action. We let the action return a redirect to the index view.

//Other, existing using statements
using Nest;

namespace MvcMusicStore.Controllers
{
    [Authorize(Roles = "Administrator")]
    public class StoreManagerController : Controller
    {
        public ActionResult ReIndex()
        {
            return RedirectToAction("Index");
        }
        
        //Other, existing class members
    }
}

With this in place we’re ready to add code for indexing all albums. First we need an instance of NEST’s ElasticClient class which we’ll use to interact with the search engine. To instantiate it we need to provide a URL and port number. Given that we’re running the server of our local machine and haven’t changed the setting for port number we can use localhost and port number 9200.

var setting = new ConnectionSettings("localhost", 9200);
var client = new ElasticClient(setting);

With our client in place we iterate over a list of all albums which we retrieve from the database and index each album. In order to index an album we use the ElasticClient’s Index method. More specifically we use an overload that allows us to specify index name, type name and ID.

foreach (var album in db.Albums)
{
    client.Index(album, "musicstore", "albums", album.AlbumId);
}

Note that we haven’t created the index named musicstore. While elasticsearch provides API methods for explicitly creating indexes it can also automatically create an index if it doesn’t exist when we try to feed a document to it. The full ReIndex method should now look like this:

public ActionResult ReIndex()
{
    var setting = new ConnectionSettings("localhost", 9200);
    var client = new ElasticClient(setting);

    foreach (var album in db.Albums)
    {
        client.Index(album, "musicstore", "albums", album.AlbumId);
    }

    return RedirectToAction("Index");
}

As we haven’t create a button in the StoreManager’s Index view we don’t have any user friendly way to invoke it. We can however use it by going to <siteURL>/StoreManager/ReIndex. There’s just one problem. When indexing albums each album will be serialized to JSON and the the Album class contains the nemesis of most serializers, a circular reference.

The Album class has a property of type Genre, which in turn has a list of Albums, meaning that the serialization process will end up in an endless loop or crash if we don’t prevent that. Luckily doing so is easy. All we have to do is either exclude the Album class’ Genre property, or the Genre class’ Albums property, from being serialized using a JsonIgnore attribute. As we’d like the Genre, except for its list of albums, to be indexed with each album so that albums can be searched by genre we’ll add the attribute to the Genre class.

using System.Collections.Generic;
using Newtonsoft.Json;

namespace MvcMusicStore.Models
{
    public partial class Genre
    {
        public int GenreId { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }

        //The below attribute excludes the Albums
        //property from JSON serialization, preventing
        //circular references when serializing albums.
        [JsonIgnore]
        public List<Album> Albums { get; set; }
    }
}

We’ve now added functionality, in the form of the ReIndex action, to index all albums. After invoking it we have an index containing the site’s albums. To verify that so is actually the case we can use a simple query string query to see what’s in the index by directing a browser to http://localhost:9200/_search?q=*. We’re ready to create a search page.

Adding free text search

In a real production scenario we’d probably start out by creating a new view, view model, controller action and possibly also a separate controller for the search page. However, in order to simply illustrate how we can use elasticsearch to build a search page for the ASP.NET MVC Music Store project we can do a simple implementation by borrowing an existing view and model class.

We’ll add a new action named Search to the Store controller which is otherwise used to list albums by genre, display details for a single album and so on. The action will return the same view as the Browse action which lists all albums in a specific genre and feed it an instance of the Genre class as model. As the Browse view display’s the models title as headline we can use it to display a custom headline for search results. Let’s do that before getting down to search business.

using System.Linq;
using System.Web.Mvc;
using MvcMusicStore.Models;
using Nest;

namespace MvcMusicStore.Controllers
{
    public class StoreController : Controller
    {
        MusicStoreEntities storeDB = new MusicStoreEntities();

        public ActionResult Search(string q)
        {
            var genre = new Genre()
            {
                Name = "Search results for " + q
            };

            return View("Browse", genre);
        }

        //Other existing actions
    }
}

Our new Search action will return the Browse view displaying information about what the user searched for in the form of input from the query string parameter q. However, If we tried to invoke the action we’d get a null reference exception from the view as it tries to iterate over the albums in the model’s Albums property. Let’s populate that with albums matching the search query.

As with the ReIndex action we’ll need an instance of the ElasticClient class to interact with the elasticsearch server. We’ll create a static helper property to create it, this time specifying a default index with the same name as we used when indexing, “musicstore”.

private static ElasticClient ElasticClient
{
    get
    {
        var setting = new ConnectionSettings("localhost", 9200);
        setting.SetDefaultIndex("musicstore");
        return = new ElasticClient(setting);
    }
}

To search for albums matching the query from the query string in the Search action we’ll use the ElasticClient’s Search method and add a QueryString query to the request body. We then add the matching albums to the Genre that we pass as a model to the view.

public ActionResult Search(string q)
{
    var result = ElasticClient.Search<Album>(body =>
        body.Query(query =>
        query.QueryString(qs => qs.Query(q))));

    var genre = new Genre()
    {
        Name = "Search results for " + q,
        Albums = result.Documents.ToList()
    };

    return View("Browse", genre);
}

We now have working free text search on our music store, albeit without a form for our users to enter their query in but we can try it out by browsing to, for instance, /store/search?q=deep%20purple. So, what did we do to create the search request in the code above?

Well, we used the Search method specifying what type to search for using it’s type parameter. We also passed it a single argument, a delegate that modifies the search request body. In elasticsearch the search request body can contain a number of things such as a filter, requests for facets, number of hits to retrieve and so on. Perhaps most prominently it can also contain a query, which we added using the Query method. elasticsearch supports many types of queries and in this case we used a query string query (not directly related to query strings in URLs).

A query string query will be parsed by the search engine and converted to various other types of queries, allowing users to use Lucene syntax when searching. This is powerful as it allows users to use keywords such as AND and OR, specify fuzziness and quite a few other options. Be ware though that this also means that the search engine will throw an exception if the query isn’t syntactically valid.

Using elasticsearch for querying

While elasticsearch is great for free text search we can also use it for many other tasks that aren’t perceived as searching per se by users. A simple example of this is replacing the database query in the Browse action. While doing so doesn’t make much sense considering the small amount of data in the database that ships with MVC Music Store it does make for a good example of how we can use filters to query for data. And if the site had contained tens of thousands of albums instead of a couple of hundred using elasticsearch to list albums in a specific genre might be faster than querying the database. Not to mention that it would take a load off the database allowing it to focus on handling admin functionality and purchases where it’s transaction management functionalities comes in handy.

In it’s original form the Browse action looks like this:

public ActionResult Browse(string genre)
{
    // Retrieve Genre and its Associated Albums from database
    var genreModel = storeDB.Genres.Include("Albums")
        .Single(g => g.Name == genre);

    return View(genreModel);
}

As you can see we fetch the genre matching the input parameter to the action from the database, joining it with the Albums table. While we haven’t indexed any genres separately we have all the relevant data in the index as we’ve indexed albums along with their genres. Therefor we can re-implement the Browse action using a simple search request with a filter.

public ActionResult Browse(string genre)
{
    var result = ElasticClient.Search<Album>(body =>
        body.Filter(filter =>
            filter.Term(x => 
                x.Genre.Name, genre.ToLower()))
        .Take(1000));

    var genreModel = new Genre()
        {
            Name = genre,
            Albums = result.Documents.ToList()
        };

    return View(genreModel);
}

The above code yields the same result as the original implementation, only we’re using elasticsearch instead of going to the database through Entity Framework. But what does the code do?

Just as before we use the ElasticClient’s Search method and pass it a delegate that modifies a search request body. This time we don’t add a query to it but instead add a filter. The filter, a TermFilter, specifies that we want to match albums whose genre equals that of the input parameter to the action. As we’re using an automatically created index it will have default mappings meaning that string values will be indexed in a way suitable for free text search. This means that they will, among other things, be normalized in terms of casing. We could change this by adding a mapping for the Genre.Name field but for this simple example we make due with lowercasing the input value.

We also modify the search request body to request a thousand albums, which is plenty considering that the site only contains 246 albums. If we hadn’t done so we’d only get the default number of search results back from elasticsearch, ten.

Finally we create a genre populating it with the matching albums and the genre name from the input to the action. Of course setting the name to the action parameter opens up all kinds of security holes, but for this example it’ll do.

Queries and filters

In the above example we added a filter directly to the search request body. That works, but in production we’d typically would like to wrap the filter in a query. Doing so yields better performance as the search engine then doesn’t have to bother with relevance scoring. Of course in the above example we don’t actually want to use a query that effects the result. In such cases a constant score query comes in handy. In other words, for optimal performance we should rewrite the search part of the Browse action to something like this:

var result = ElasticClient.Search<Album>(body =>
    body.Query(query => 
        query.ConstantScore(
            csq => csq.Filter(filter =>
                filter.Term(x =>
                    x.Genre.Name, genre.ToLower()))))
    .Take(1000));

Rewriting the genre menu using a facet

A second example of how we could take a load off the database by using elasticsearch is by modifying the left hand side menu of the store that lists genres. Again, while we haven’t indexed individual genres we have indexed them as nested objects in the albums. This means that we can feed the GenreMenu action’s view with an aggregated list of genres retrieved from the albums. A perfect job for a terms facet, one of many facets supported by elasticsearch.

In it’s original form the GenreMenu action is implemented like this:

public ActionResult GenreMenu()
{
    var genres = storeDB.Genres.ToList();

    return PartialView(genres);
}

Using elasticsearch we could re-implement it like this:

public ActionResult GenreMenu()
{
    var result = ElasticClient.Search<Album>(body =>
        body.Take(0)
        .FacetTerm(x => x.OnField(f => f.Genre.Name)));

    var genres = result
        .FacetItems<TermItem>(x => x.Genre.Name)
        .Select(x => new Genre {Name = x.Term});

    return PartialView(genres);
}

In the above example we again create a search request for albums. Only this time we specifically ask for zero hits and instead add a request for a terms facet for the Genre.Name field. We then retrieve the facet items, consisting of the term (the genre name) and a count (number of albums in the genre), from the result and use those to build up a list of genres.

This implementation consists of a lot more code than the original version and I’m not saying that re-implementing the GenreMenu action like this is necessarily a good idea. But it’s interesting to see that we can.

I should also add that this implementation won’t produce the exact same result a the original. As we haven’t added any custom mappings to our index the genre names will be returned as lower cased. Fixing that could fairly easily be done by adding mappings should we want to, but that’s beyond the scope of this post.

Summary

While this post doesn’t discuss the nitty gritty details of using a search engine such as elasticsearch, such as mappings, specifying fields to search in and scalability (which is just awesome) we’ve seen how we can easily add free text search to an ASP.NET site. We’ve also seen that we can use elasticsearch to create functionality beyond free text search. By doing so we can build highly scalable websites and free up any relational database we may be using so that it can focus on what it does best, handling relations and transactions.

Figuring out the current language branch in EPiServer CMS

$
0
0

Sometimes we need to figure out what language context we’re in in EPiServer. If we’re coding a template we may be tempted to look at the LanguageBranch property of the CurrentPage property. However, in other situations that may not be available and that won’t actually tell us what language context we’re in but what language branch the current page is loaded from. In most situations those two values will be the same, but if the page has fallback or replacement languages configured it won’t.

To figure out the actual language context we’re in, disregarding fallback or replacement languages, we can instead use the static PreferredCulture property of the ContentLanguage class located in the EPiServer.Globalization namespace. Like this:

//using EPiServer.Globalization;

CultureInfo currentCulture = ContentLanguage.PreferredCulture;
string languageBranch = currentCulture.Name;

EPiServer CMS 7 Preview – Content, Pages and Blocks

$
0
0

EPiServer 7 features a remade model for content with support for multiple types of content and an interesting new concept called blocks. In this post I’ll visualize and walk through some of the most significant new concepts and changes from a developer’s perspective. 

The first preview of the new version of EPiServer CMS brings a lot of exciting new functionality both for editors and developers. The most conspicuous being the brand new editorial UI. There has however also been a lot of changes to the underlying APIs and the way content is represented as classes and objects. In fact, the content/data model in EPiServer CMS 7 has undergone almost as big of a change as the editorial UI.

Understanding the changes made to the UI is fairly straight forward. All one has to do is log in to the edit mode of an EPiServer 7 site and look around. Grasping the revamped content/data model isn’t as easy as classes and objects are abstract concepts that aren’t visible. As I’ve had the opportunity to dig into these changes and believe that I’m starting to grasp them I thought I’d make an attempt at visualizing them, hopefully making them slightly less abstract.

In EPiServer CMS 6 content is pages

Before we dig in to the content/data model of EPiServer 7, let’s look at what we’re coming from. In earlier versions of the CMS it could be illustrated like this:

EPi Data model 6
(Click on the image to view in full size)

In EPiServer 6 all content is represented by PageData objects, with the exception of uploaded files such as images, PDFs and the like. A PageData object has a name, an URL and various properties defined by it’s page type. The page type in turn points to a template, an ASPX page, responsible for rendering pages of that type. The identity of a page is represented by a PageReference object made up of an integer ID property, a string property named RemoteSite which specifies if the page is persisted by a custom page provider and, optionally a version identifier in the form of the integer property WorkID.

With the exception of container pages, a special type of pages whose page type doesn’t point to a template, a PageData object is clearly designed for representing a page that can be viewed by a visitor. That is, a single PageData object is designed to corresponds to one URL on a site. However, for lack of other entity types for modeling content in the CMS and thanks to PageData objects being very flexible they are often used to represent other types of content. A common example of such usage is teasers or widgets that are used not as stand-alone pages but as parts of other pages. Another example is to use PageData objects to model structure, such as menus where pages in a certain node in the page tree is only rendered as links to other pages.

EPiServer CMS 7 brings a content model based on actual usage

While the page based content model of earlier versions of EPiServer CMS has proven to be robust and flexible it’s also obvious that it’s been used in ways that it wasn’t designed for. After all, EPiServer CMS is a content management system and not a page management system but the only way to model content was with pages.

To better accommodate real world usage on modern websites and in order to make the new editorial UI possible the new version of the CMS features a more complex and flexible content model. In the new model content isn’t solely represented by pages.

An API and entity model that maps closely to real world usage is of course great, but it does come at a price in the form of higher complexity. Therefore, an illustration of the content/data model in EPiServer 7 is significantly larger:

 EPi Data model 7
(Click on the image to view in full size)

Wrapping one’s head around the above image isn’t easy at first glance. The image looks quite different compared to the CMS 6 version. Not to mention that it’s more complex. Let’s go through the main differences and concepts.

Content is more than pages, it’s IContent

In earlier versions of EPiServer pages was the only form of content and much revolved around their object representation, PageData objects. In EPiServer 7 the content concept has been widened and any type of object that implements an interface named IContent is considered content and thereby possible to persist and retrieve through EPiServer’s data access layer for content.

What this means in practice is that DataFactory’s methods, or rather IContentRepository’s methods, such as Get (by ID), GetDefault, Save, GetChildren etc no longer exclusively deals with PageData objects but instead works with any type that implements IContent. EPiServer 7 utilize this by introducing two new types of content to complement pages, content folders and “shared blocks”. Of course, this also means that we, as developers of sites or third party products that extend EPiServer CMS are able to create custom types of content that will work with EPiServer’s data access layer.

The IContent interface

EPi Data model 7-IContentThe IContent interface declares a number of properties and also inherits the IContentData interface. Thus, any class that implements IContent must have a number of members from both IContent and IContentData:

  • Properties –  a collection of (EPiServer) properties. Essentially the same as the Property property on PageData objects. Declared by IContentData.
  • IsNull – Declared by IContentData and can be used to check if such an object, while not being null it self, doesn’t have any properties and therefor could or should be treated as being empty.  
  • Name – The name of the content item. Fills the same role as PageName did for pages as it’s used when displaying links to content, be it pages, blocks or other types of content in the CMS.
  • ContentLink – a unique identifier for the content in the form of a ContentReference. Performs the same roles as the PageLink property did for pages.
  • ParentLink – a unique identifier for the content’s parent content. All content, except for the root page, still has to have a parent in EPiServer 7. Unlike earlier versions though the parent doesn’t have to be a page but can be any type of content. Typically another page for pages and a content folder for shared blocks.
  • ContentGuid – also a unique identifier for the content.
  • ContentTypeID – The integer ID of the corresponding content type.

ContentReference

EPi Data model 7-ContentReferenceIn earlier versions of the CMS the identity of an individual page was of type PageReference. The PageReference class had three significant properties, ID, RemoteSite and WorkID. The combination of ID and RemoteSite is the unique identifier of a single page while the same combination also including WorkID is the unique identifier for a specific version of a single page.

From EPiServer 7’s perspective there was two problems with the PageReference class, it’s name and the name of its RemoteSite property. As there are now other types of content than pages “PageReference” isn’t a very good name for their identifiers. The “RemoteSite” property was always used to identify which page provider the page belonged to meaning that the name was awkward.

EPiServer’s developers could have fixed these issues by renaming both the PageReference class and the RemoteSite property. That would however be a major breaking change considering how often the PageReference class is used as method parameters in sites built on the CMS. Instead a new class, ContentReference has been introduced that PageReference now inherits from. The ID and WorkID properties of PageReference has been moved up to ContentReference. RemoteSite however is left in PageReference and ContentReference instead has a property named ContentProvider that serves the same purpose but with a better name.

ContentType

While EPiServer 7 now has native support for declaring page types in code it still supports the old way of managing page types from admin mode and the underlying model where a page always has a page type is intact. However, the PageType class isn’t suitable for other content types as some of its properties only make sense for pages. Therefore EPiServer 7 has two new classes similar to the PageType class, ContentType and BlockType.

EPi Data model 7-ContentTypeContentType is extracted from PageType and it’s the base class of both PageType and BlockType. A content type contains the collections of property definitions (called page definitions in earlier versions), information about what Web Forms page or MVC controller to use when rendering content of that type as well as other properties previously found in the PageType class. The PageType class is still around and has some properties relevant only for pages.

BlockType plays the same role for blocks as PageType does for pages. Unlike the PageType class BlockType doesn’t add any additional members and merely overrides a couple of members of ContentType which are used by the editorial UI to provide localized names and such.

IContentRepository, a much sought after abstraction for DataFactory

One criticism of the API in earlier versions of EPiServer CMS was its lack of abstractions, particularly for the DataFactory class. As the relevant methods for working with pages in the DataFactory class weren’t virtual and not declared by an interface it made it very hard to isolate code that worked with pages from the EPiServer API and all the plumbing, such as the database, that it needed. Therefor it was difficult to create unit level tests for such code or to follow some of the SOLID principles.

EPi Data model 7-IContentRepositoryEPiServer 7 addresses this with the introduction of the IContentRepository interface which declares all the significant methods for working with pages that DataFactory exposes. DataFactory of course implements this interface. Similar changes has been made to other parts of the API as well, but IContentRepository is the most sought after.

EPiServer 7 also uses an IoC container, StructureMap, through which an instance of IContentRepository and many other interfaces in the EPiServer API can be obtained. Backwards compatibility is maintained as the DataFactory class still has an Instance property following the Singleton pattern.

Pages

EPi-Data-model-7-Page-tree-gadgetPages have always played a central role in EPiServer CMS. While it's lost its total dominance in EPiServer CMS 7 the PageData class is still a key player in the CMS' content and data model. The page tree in edit mode is still there. It no longer shows by default as the new edit mode makes it easy to navigate the site without it in many situations. It has it’s very own button at the top left of the edit mode though and it will continue to be a key component for finding and arranging pages on the site. 

The PageData class hasn't undergone any significant changes compared to previous versions and more or less maintain backward compatibility. Quite a few of the characteristics of PageData objects, such as having an access control list and being versionable has been lifted to separate interfaces, but as PageData implements those interfaces that doesn't have any significant impact on how we work with PageData objects. EPi Data model 7-PageData

While it implements IContent and thereby has properties declared by it such as ContentLink and Name the corresponding old properties such as PageLink and PageName is still around and many of the new properties simply acts as aliases for the old ones.

One significant change from PageData's perspective is of course the introduction of native support for declaring page types in code. That doesn't really affect the content model however and many have used Page Type Builder for that with previous versions of the CMS.

Blocks

Blocks, inheriting from BlockData, are a special, and at first confusing, type of entity in EPiServer 7. The BlockData class doesn't implement IContent but an instance of BlockData may. Yep, that's right, a block object may, or may not, be an IContent. If it is, it's possible to persist it just like pages and other types implementing IContent. This is achieved by mixins, a concept commonly used in some other programming languages, but not very common in C#.

Local blocks

EPi Data model 7-Local blocksBlocks serve dual purposes in EPiServer 7. A block may be a grouping of properties on a single page, similar to property groups in Page Type Builder. Such blocks don’t really have a formal name, they’re just “blocks”, but I’ve found myself talking about them as “local blocks”. Local blocks are essentially just data containers. They don’t have a publication status, can't have access rights or be versioned themselves. Instead the versioning of their values and other such things are handled along side other properties on the page (or some other form of IContent) that they belong to.

Local blocks allows reuse of groups of properties. This reuse can be on a single content type as well as between different content types. For instance, we could create a block type that defines two properties for an image, a URL and an alternative text. We can then add three code properties of this type to a single page type effectively creating six properties under the hood. We could also use the same block type on multiple page types.

Shared blocks

EPi Data model 7-Shared blocksA block may also be a stand alone entity that is saved individually in the database. Such blocks are referred to as shared blocks and can, after they have been saved, be added to pages, other shared blocks or other types of content. Adding a shared block to a page doesn't actually save any of it's values to the page but instead involves adding a link to the block to the page in a special type of property called content area. Shared blocks can have different versions and they maintain a publication status and access control list, just like pages.

Shared blocks are very similar to EPiServer Composer functions and a host of other solutions to the UI composition and content reuse problem created by various EPiServer partners. Composer and many other solutions involved creating pages that weren't used as stand alone pages but instead added as links to other pages and had their properties rendered as components/widgets (or blocks) on the pages they were added as links to. Shared blocks serve the same purpose but are free of all of the infrastructure required for pages and aren't displayed in the page tree. In fact, one explanation of shared blocks that I've heard is that they are "like lightweight pages".

They are created and edited separately from pages. WYSIWYG editing is accomplished by, optionally, providing a template in which they are rendered while editing. Existing shared blocks are not listed in the page tree but in separate gadget. They can then from there be added to content areas on pages or other shared blocks using drag-n-drop.

EPi-Data-model-7-Shared-blocks-gadget
The Shared Blocks gadget.

EPi-Data-model-7-Content-areas
Two content areas on a page in forms editing mode.

Shared blocks are created by using the IContentRepository.GetDefault() method just like pages. During this creation, at runtime, a class will be generated that inherits from the block's original type. A proxy class. This class will implement IContent as well as a number of other interfaces, such as IVersionable.

Content folders

EPi-Data-model-7-Content-foldersContent folders is another type of content that ships out of the box with EPiServer 7. They are used for grouping blocks, or possibly custom content, that isn’t displayed in the page tree. While pages are naturally grouped in the page tree blocks and custom content isn’t showed there and while it would be possible to just list all blocks in a gadget that wouldn’t be very practical for editors of a site with many blocks.

Content folders are instances of the ContentFolder class and just like pages and shared blocks they implement the interfaces necessary for having different language versions and an access control list. However, unlike pages and shared blocks content folders cannot be versioned.

Custom Content

With earlier versions of EPiServer CMS it was very common to use pages as a means to persist other types of data that wasn’t pages or necessarily even content on the site. While there were other means of storing non-page data, such as the DDS or custom database tables, storing it as pages allowed us to get an administrative interface (although not necessarily well suited) for it for free as well as access rights handling, versioning etc. For those reasons doing so is something that I’ve used often myself and advocated for building integrations with external systems.

One downside of storing non-page data as pages is that we risk crowding the page tree for editors as well as risk exposing it to users who shouldn’t see it unless we explicitly filtered it out in page listings. EPiServer 7 provides an elegant solution to this by letting us save any object through the data access layer (DataFactory/IContentRepository) for content as long as it implements IContent.

If we create custom content classes that don’t inherit from PageData and save them through an IContentRepository they won’t show up in the page tree. Likewise, if they don’t inherit from BlockData they won’t show up in the blocks gadget either. Of course this means that if we do want to look at such saved content we may need to build some custom functionality for listing it and linking to it in the edit UI as it won’t be listed in the page tree or shared blocks gadget. Having done so, or by figuring out the edit URL for the content we may however still utilize the CMS’ editing functionality for it.

EPi-Data-model-7-Editing-custom-content
Editing a custom content object by specifying it’s ID in the URL.

Summary

To summarize, in EPiServer 7:

  • Content is more than pages
  • An IContentRepository, DataFactory, can persist all objects that implement IContent
  • Pages are complemented with two new types of content, blocks and content folders
  • Blocks can be used to reuse code and editorial content and shared blocks can be used to compose parts of pages
  • A lot has changed, but backwards compatibility and familiarity is high

Conclusion

Many of the changes and new features of EPiServer 7 are made to align the CMS with how it’s used by us partners. Blocks solves the problem of dynamic UI composition and reuse of UI components in a far more elegant way than Composer did. The new content/data model draws on the flexibility for modeling various kinds of data that PageData objects offered in previous versions without forcing all objects to be listed in the page tree and available for display on the site.

With a more powerful and flexible model for content comes complexity. EPiServer’s developers have however done a great job at maintaining the core strengths of the CMS. The PageData class is still there and it’s almost fully backwards compatible. So is the page hierarchy concept and data access layer.

The most confusing part of the new API is probably blocks and the concepts of shared and local blocks where one instance of a class may implement IContent while another may not. This solution does allow us not only to reuse definitions of groups of properties but also the components used for rendering though.

Working programmatically with Local Blocks in EPiServer 7 Preview

$
0
0

Blocks is a new, exciting and much needed concept in EPiServer 7. As I’ve explained in my post about the new data model in EPiServer 7 there are two kinds of blocks, shared blocks and local blocks. A single block type and renderers for it can be used both for local and shared blocks. Both Anton Kallenberg and Alexander Haneng have written great introductions and how-tos for both shared and local blocks so I’ll direct you to either of them for an introduction. Anton’s post is available here and Alexander’s here.

In this post we’ll look at how to work programmatically with local blocks. We’ll also see some interesting aspects of local block properties.

Example block type and local block property

Local blocks are created by first creating a block type and thereafter adding a property of that type to a page type, or some other content type. In order to work with a local block programmatically we’ll of course need a local block, so here’s an example of simple block type that I will use throughout this post:

[ContentType(
    DisplayName = "Fact box",
    GroupName = "AlloyTech",
    Description = "For displaying fact boxes related to "
                    + "the main content in articles.")]
public class FactBoxBlock : BlockData
{
    public virtual string Title { get; set; }
    public virtual XhtmlString Content { get; set; }
}

And here’s a page type with a local block property of the above block type.

[ContentType]
public class ArticlePage : PageData
{
    public virtual XhtmlString MainBody { get; set; }
    public virtual FactBoxBlock FactBox { get; set; }
}

Creating local block objects

Local blocks always live on a page or other form of IContent and they are automatically created when the host content, articles in this example, is created. Therefore there isn’t really a concept of creating local block objects, only assigning their values. For instance, to programmatically create an ArticlePage and set the values on its fact box block we could write code such as this:

//Retrieve an IContentRepository using the ServiceLocator.
//In an MVC scenario use constructor injection instead.
//We could also have used DataFactory.Instance, but this
//is "the new way".
var contentRepository = 
    ServiceLocator.Current
        .GetInstance<IContentRepository>();

//Create an article page under the start page and
//set the required PageName property. This will also
//implicitly set the required IContent.Name property
//as PageData's Name property maps to the PageName property.
var newArticle = contentRepository
    .GetDefault<ArticlePage>(PageReference.StartPage);
newArticle.PageName = tbArticleName.Text;
            
//Set the Title and Content of the local FactBox block
newArticle.FactBox.Title = tbFactBoxTitle.Text;
newArticle.FactBox.Content = 
    new XhtmlString(tbFactBoxContent.Text);

//Save the page. This will save all of the values we've
//set including those on the local block.            
contentRepository.Save(
    newArticle, 
    SaveAction.Publish, 
    AccessLevel.Publish);

Local Blocks cannot be null

There’s not much of interest to the above code except for one detail, we assume that the FactBox property has a value. That is, we don’t assign it a new FactBox instance and we don’t bother with null checking it. We can count on it being there as, as stated earlier, local blocks are created with their host content. In fact, we couldn’t even make it null even if we wanted to.

newArticle.FactBox = null;
//Will be false
var factBoxIsNull = newArticle.FactBox == null)

var contentLink = contentRepository.Save(
    newArticle, 
    SaveAction.Publish, 
    AccessLevel.Publish);
newArticle = contentRepository.Get<ArticlePage>(contentLink);
            
//Still false
factBoxIsNull = newArticle.FactBox == null;

In the above code we explicitly set the FactBox property to null, but doing so has no effect for the page that we’re saving. Unsurprisingly it doesn’t effect the saved page, retrieved from the database, either.

Similarly, just as we can’t force a local block property to be null by setting it to null we can’t assign a new, non-null, value to it. Or rather, while we can assign it a new object, the values of the new object won’t be persisted.

//Create a new FactBox block and assign it
//to the local block property
var factBox = new FactBoxBlock();
factBox.Title = tbFactBoxTitle.Text;
factBox.Content = 
    new XhtmlString(tbFactBoxContent.Text);
newArticle.FactBox = factBox;

//Will be true
newArticle.FactBox == null;

var contentLink = contentRepository.Save(
    newArticle, 
    SaveAction.Publish, 
    AccessLevel.Publish);

newArticle = contentRepository.Get<ArticlePage>(contentLink);

//Will be false
newArticle.FactBox == null;

//Will be true
newArticle.FactBox.Title == null;

Somewhat surprisingly setting a local block property to a new object actually makes the property’s value null. This is only temporary though as order is restored after loading the host object from the database after saving.

Updating Local Blocks

Updating a local block programmatically involves updating the host content as local blocks are persisted on other content. The steps to update a local block is identical to updating any other property on the host content. We need to retrieve the host content, clone it, change one or more values and finally save it.

//Clone the host content, in this case retrieved
//by using the CurrentPage property in a template/renderer.
var clone = (ArticlePage) 
    CurrentPage.CreateWritableClone();

//Update values on the local block
clone.FactBox.Title = tbTitle.Text;
clone.FactBox.Content = 
    new XhtmlString(tbContent.Text);

//Save the host content
contentRepository.Save(
    clone, 
    SaveAction.Publish, 
    AccessLevel.Publish);

Clearing the values of a Local Block

What if we want to programmatically clear all of a local block’s values? We’ve seen that we can’t force a local block property to be null by assigning it null as value. Assigning it a new value of the same type (FactBoxBlock in our case) doesn’t have any significant effect either. There is a SetDefaultValues method on BlockData, but that doesn’t have any effect for local blocks.

So, what to do? We could of course assign a null value to each individual property of the block explicitly. That’s not a big problem for blocks with few properties, such as the fact box block we’ve been using as an example. But for blocks with a lot of properties or perhaps of an unknown type that’s not a good option. What we can do then is utilize that BlockData implement IContentData and therefor must have a Properties property through which all properties are exposed. We can iterate over each of the block’s properties and clear their values. In the case of BlockData the Properties property from IContentData is explicit so we’ll first have to cast the local block to IContentData.

var clone = (ArticlePage) CurrentPage.CreateWritableClone();

var contentData = (IContentData)clone.FactBox;
foreach (var property in contentData.Properties)
{
    property.Clear();
}

contentRepository.Save(
    clone,
    SaveAction.Publish,
    AccessLevel.Publish);

Checking if a Local Block is empty

We’ve seen that there’s no point in null-checking a local block property. It will always have a value given that the host content object has been created or loaded using an IContentRepository. What if we wanted to check if the block should be rendered or not depending on if it has any values? Hadn’t it always been non-null we could simply have checked if it wasn’t null and then assumed that it should be rendered, but we can’t. What we can do is take again iterate over each of its properties to see if any of them has any values.

//using System.Linq;

var contentData = (IContentData) CurrentPage.FactBox;
var factBoxIsEmpty = contentData.Properties
    .Any(x => x.IsNull);

Wildcard queries with EPiServer Find

$
0
0

A few days ago a question was posted in the Find forum on EPiServer World that basically boiled down to how to do wildcard queries with EPiServer Find. That is, how to match a part of a word. Find’s .NET API is made up of several “layers” with a fluent API for querying at the top and classes that map more closely to the REST API exposed by the search engine at the bottom. While the fluent API doesn’t currently support wild card queries the REST API certainly does and the lower level components exist for it in the .NET API. Therefor it’s possible to add a custom extension method with which wild card queries can be created.

using System;
using System.Linq.Expressions;
using EPiServer.Find;
using EPiServer.Find.Api.Querying.Queries;

public static class SearchExtensions
{
    public static ITypeSearch<T> WildCardQuery<T>(
        this ITypeSearch<T> search, 
        string query, 
        Expression<Func<T, string>> fieldSelector,
        double? boost = null)
    {
        //Create the Wildcard query object
        var fieldName = search.Client.Conventions
            .FieldNameConvention
            .GetFieldNameForAnalyzed(fieldSelector);
        var wildcardQuery = new WildcardQuery(
            fieldName, 
            query.ToLowerInvariant());
        wildcardQuery.Boost = boost;

        //Add it to the search request body
        return new Search<T, WildcardQuery>(search, context =>
        {
            if (context.RequestBody.Query != null)
            {
                var boolQuery = new BoolQuery();
                boolQuery.Should.Add(context.RequestBody.Query);
                boolQuery.Should.Add(wildcardQuery);
                boolQuery.MinimumNumberShouldMatch = 1;
                context.RequestBody.Query = boolQuery;
            }
            else
            {
                context.RequestBody.Query = wildcardQuery;
            }
        });
    }
}

Using this method in it's simples form could look something like this:

var result = SearchClient.Instance
  .Search<PageData>()
  .WildcardQuery("*ppl*", x => x.PageName)
  .GetPagesResult();

The above query will match any page that has a name containing a word which contains the character sequence “ppl”, meaning that pages named “Apple”, “Supply”, “Green apples” and “My application” will be matched while a page named “App” won’t be. Modifying the above to instead only have question marks around the characters (“?ppl?”) would limit the matched pages to the one named “Apple” while “?ppl*” would also include the page named “Green apples”.

As you’ve probably noticed the method requires an expression which specifies what field to search in. There’s nothing stopping us from invoking the query multiple times to search in multiple fields. However, please note that wild card queries can become slow when applied to many documents and/or fields with much text. Therefor it’s best to try to limit their use to fields with short text, such as PageName and not MainBody for EPiServer CMS pages. It’s also a good idea to question requirements if they specify that a wild card query should be used on all text as that tends to produce very broad results and that’s typically not the expected behavior (you don’t get hits for “Apple” if you search for “ppl” with Google).

You can of course combine the WildcardQuery method with the regular For method. For instance, the below query might work well for search-as-you-type functionality.

var result = SearchClient.Instance
  .Search<PageData>()
  .For("ppl")
  .InFields(x => x.PageName, x.MainBody)
  .WildcardQuery("*ppl*", x => x.PageName)
  .GetPagesResult();

The last, optional parameter can be used to specify a boost level for the wildcard query. Setting it to a very low value (like 0.01) will have the effect of including documents that match the query in the result but favor those matched by other queries, such as the one added with the For method in the above example.

Fuzzy queries

Related to wildcard queries are fuzzy queries. Be sure to check out Henrik Lindström’s post on how to fuzzy queries with Find!

Type conditional filtering with EPiServer Find

$
0
0

EPiServer Find’s .NET API allows to filter by .NET/CLR type when searching and querying. For instance, let’s say we’ve indexed a bunch of objects representing animals. We can then find all animals using:

var result = client
    .Search<Animal>()
    .GetResult();

If we only want to find cats we change the type parameter:

var result = client
    .Search<Cat>()
    .GetResult();

But what if we want to find all animals except for dogs?

MatchType and MatchTypeHierarchy

We can then utilize one of two methods in a filter expression – MatchType and MatchTypeHierarchy. The first one matches by exact type match while the second matches any type in the object’s type hierarchy, that is it’s own type plus any class or interface that it inherits. So, to find all animals except for dogs we may add a filter such as this:

var result = client
    .Search<Animal>()
    .Filter(x => !x.MatchType(typeof(Dog)))
    .GetResult();

Another common use case is to add additional requirements to those documents that are of a specific type. For instance, it may be that if a CMS content object is versionable only the latest version should be matched. For our animal example it may be that we only want to find small animals meaning perhaps cats and small dogs. In other words we want to match all animals except for dogs unless the dog is small.

var result = client
    .Search<Animal>()
    .Filter(x => !x.MatchType(typeof(Dog)) | ((Dog)x).IsSmall.Match(true))
    .GetResult();

In the above example we use MatchType to filter by type. If there are subtypes of the Dog class we should instead use MatchTypeHierarchy. That method also comes in handy when filtering by interfaces. For instance, let’s say we have a Fish class which has a bunch of subtypes (Goldfish, Whale etc) and an interface that indicates that an animal is suitable as a pet. We could then exclude all fish excepts those that are good pets.

var result = client
    .Search<Animal>()
    .Filter(x => 
        !x.MatchTypeHierarchy(typeof(Fish)) 
        | x.MatchTypeHierarchy(typeof(IDomesticAnimal)))
    .GetResult();

More use cases

Both methods can be used in all filter expressions, meaning that they can also be used to boost certain objects using the BoostMatching method, in Filter facets, when dynamically building complex filters etc.

Notes and limitations

The methods for matching by type currently only works for the searched type and can't be used for nested properties/fields. That's however technically possible and I hope to see such functionality in the future. Also, the MatchTypeHierarchy method was added in a recent version of Find (1.0.0.224) so if you want to try it out, make sure you have the latest version.

New in EPiServer Find – Unified Search

$
0
0

“Unified search is a new concept in Find’s .NET API that aims to provide the benefits of indexing objects using the “dumbed down” least common denominator approach while still maintaining Find’s original power of indexing more or less full .NET objects. … It allows us to query objects as if they where implementing a common interface without actually having to modify the indexed objects.”

Last week EPiServer released the new version of their CMS, EPiServer CMS 7. While this new version contains a few nice but minor tweaks like a whole new editor UI, revamped API and multi channel mojo there was actually another, much more significant, release last week. The new version of the EPiServer Find .NET API and CMS integration!

Kidding aside the new version, which is available for download from EPiServer World as well as from EPiServer’s NuGet feed, features primarily tweaks and improvements to the CMS integration making Find even more seamlessly integrated with the CMS. It does have one rather interesting brand new feature though, a concept called Unified Search.

Executive summary

This is a long post in which I’ll discuss the Unified Search concept in quite some detail. If you don’t care or don’t have the time for that, here’s what you need to know.

In the new version of Find’s .NET API and CMS integration you can do this:

var result = SearchClient.Instance
    .UnifiedSearchFor("some search term")
    .GetResult();

The above code will search for both PageData objects and files in VPPs (UnifiedFile). Using a couple of lines of code you can also add other types that will be included in the search. The result object will be contain hit objects with a title and an excerpt, both which can be highlighted, as well as some other properties that are commonly used in search result listings.

What it is

EPiServer Find takes a new approach to search with the aim of harnessing the power of search engines for more than just search pages. It does this by allowing developers to index and query objects of existing classes, such as PageData objects, without having to map them to some intermediate type that is then put into the index. It also indexes the objects in such a way that developers can later query them in a number of ways using a fluent API that doesn’t require much in terms of special search engine skills. This is of course very powerful as it allows us to use Find both for free text search and for deterministic querying such as navigation and listings.

Most other search products doesn’t have this functionality but instead indexes some sort of least common denominator. That is, while the actual data being indexed may have widely different characteristics all content is indexed the same way out of the box. That is an article page, a recipe, a product and a user comment may all have title and a content field in the index. If we later want to distinguish between articles and recipes we’ll have to find a way to do so by filtering on parts of the URL or output some meta data that contains type information.

As you might have guessed I think Find’s approach is better as it doesn’t “dumb down” objects/content in order to indexed them allowing us as developers to query them almost as if they where in memory. The approach of using a least common denominator does have a couple of benefits though – it makes it easier to search over different, unrelated, types and it makes it easier build generic functionality for querying and displaying search results (ie helper methods for displaying stuff).

Of course these benefits typically come at a steep price – the unique characteristics of the indexed objects are lost and we have to spend time customizing how they are indexed, often by outputting a bunch of meta data in the markup, to get at least some of them into the index. Unified search is a new concept in Find’s .NET API that aims to provide the benefits of indexing objects using the “dumbed down” least common denominator approach while still maintaining Find’s original power of indexing more or less full .NET objects.

The problem

In order to build generic search functionality using search engines we typically need to index all content or objects with a base set of fields that is the same no matter of their original type. This typically forces us to map objects that should be indexed to an intermediate object that matches that base set of fields/properties.

Imagine we have two classes, A and B and A has a Title property and B has a Headline property and we want to search for instances of both types in both of those fields. We then have to make the search engine understand that Title and Headline is essentially the same. Using a crawler based search engine we’d do that by outputting both properties in a H1 tag. With other search solutions we’d have a third class, let’s call it IndexDocument. When instances of A and B should be indexed we’d provide code that would map instance of A to IndexDocument as well as code that could map instances of B to IndexDocument.

There must be a better way!

The drawback of the conventional way of indexing objects using search engines is that we have to map objects to something that they are not. This can be tedious work and, worse, we loose the ability to query them by type and by that type’s unique characteristics. Clearly imposing such limitations on Find’s rich query API would be a step back. So, how can we eat the cake and have it too? Well, taking a step back from search engines and looking at object oriented programming, how would we do it there? We’d use interfaces!

Using the example of classes A and B we wanted to map both types to the third class, IndexDocument, in order to easily search over both types. But at the same time we wanted to index the objects as their original types (A and B). With object oriented programming we could turn the IndexDocument class into an interface, IIndexDocument, and have both A and B implement that.

Of course Find’s .NET API already supports this as it indexes the full inheritance hierarchy of objects including interfaces. There’s just one problem – what if we aren’t able to modify the classes to or for some other reason don’t want them to implement a common interface? This is where Unified Search comes in. It allows us to query objects as if they where implementing a common interface without actually having to modify the indexed objects.  

Components

The concept of Unified Search consists of four main parts:

  • A common interface declaring properties that we may want to use when building search (not querying) functionality – ISearchContent.
  • An object that maintains a configurable list of types that should be searched when searching for ISearchContent as well as, optional, specific rules for filtering and projecting those types when searching – IUnifiedSearchRegistry which is exposed by the IClient.Conventions.UnfiedSearchRegistry property.
  • Classes for search results that are returned when searching for ISearchContent – UnifiedSearchResults which contains a number of UnifiedSearchHit.
  • Special method for building and executing search queries – UnifiedSearch() and UnifiedSearchFor() and an overloaded GetResult() method.

ISearchContent

The ISearchContent interface resides in the EPiServer.Find.UnifiedSearch namespace and is the least common denominator. It declares a large number of properties, all with names prefixed with “Search”, that we may want to search for. The properties ranges from common ones such as SearchTitle and SearchText to more specialized ones such as SearchGeoLocation and SearchAttachment (for files such as Word documents).

IUnifiedSearchRegistry

IUnifiedSearchRegistry, also residing in EPiServer.Find.UnifiedSearch but typically accessed by fetching it from the client’s conventions, exposes methods for building and configuring a list of types that should be included when searching for ISearchContent. Apart from methods for adding (the Add method) and listing types (the List method) it also declares methods that allow us to add rules for how specific types should be filtered when searching as well as how to project found documents to the common hit type (UnifiedSearchHit).

Unless we want to include some additional type or modify the rules for an already added type we typically don’t have to care about the registry as the CMS integration will automatically add PageData and UnifiedFile to it.

UnifiedSearchResults and UnifiedSearchHit

While ISearchContent provides a decent common denominator for fields to search in it wouldn’t be useful to get back instances of it as the result of a search query. For instance, while we may want to search in the full text in an indexed object we typically only want a small snippet of the text back which we’ll show in the search results listing. Also, it would be technically problematic to get instances of ISearchContent back from the search engine as the matched object doesn’t actually implement that interface, or at least they don’t have to.

Therefor, when we search for ISearchContent and invoke the GetResult method we won’t get back instances of ISearchContent. Instead we’ll get back an instance of the UnifiedSearchResults class which contains a number of UnifiedSearchHit objects. A UnifiedSearchHit object contains a number of properties that we typically would want to show for each search result, such as Title, Url and Excerpt (a snippet of text). It also has a number of other properties such as PublishDate, ImageUri, Section, FileExtension and TypeName.

Methods for building and executing queries

In order to search for ISearchContent we can use the regular Search method, ie client.Search<ISearchContent>(). In that case we’ll be in charge of what fields to search in when building free text search. However, since ISearchContent is a special type that the .NET API knows about, there are a couple of methods that takes care of adding some sensible defaults for us – UnifiedSearch() and UnifiedSearchFor().

More importantly the Unified Search concept also adds a new GetResult method. As this has the same name as the regular method for executing search queries we don’t really have to do anything special to use it. The compiler will choose to use it for us as it has a more specific generic type constraint than the other GetResult methods. But, we should be aware of what it does.

The GetResult method will modify the search query that we have built up so that it won’t just search for objects that implement ISearchContent but also for all types that have been added to the UnifiedSearchRegistry. It will also proceed to add a projection from ISearchContent to UnifiedSearchHit with some nice sensible defaults, along with any type specific projections that have been added to the UnifiedSearchRegistry. Finally, before executing the search query like the regular GetResult method, it will also add any type specific filters that have been added to the UnifiedSearchRegistry.

Once we invoke GetResult the search query will search over types that may not implement ISearchContent, but as we (hopefully) have specified that we should only search in, or filter on, a number of fields that are declared by ISearchContent we’ll only search in fields with those names, even if the objects don’t implement ISearchContent.

The GetResult method has an overload that requires an argument of type HitSpecification. Using this we can control the length of the excerpt, whether titles and excerpts should be highlighted, as well as a number of other things.

How it works

The unified search concept utilizes two key concepts: the fact that the type hierarchy is indexed for objects and the fact that the search engine doesn’t care what type declares a given field (property) as long as it has the expected name and type. Let’s take an example. Imagine we have added two classes A and B to the registry and have the below code.

SearchClient.Instance
    .UnifiedSearch()
    .Filter(x => x.SearchTitle.Prefix("A"))
    .GetResult();

This code will search for search the index for objects that either implement ISearchContent or which are of types A or B. It will then filter those requiring that they have a field named SearchTitle with a value that starts with “A”. Objects that implement ISearchContent will have such a field but A and B may not. If they don’t, the filter won’t match and instances of A and B won’t be returned. However, if they do have such a field, the search engine doesn’t care about why they have it. That is, it won’t care about the fact that they don’t have ISearchContent.SearchTitle. The type filtering is something separate and as long as they have a string field named SearchTitle it can filter on it.

Of course, in order for A or B objects to be included in the result of the above query we must add a SearchTitle field to them, but we don’t have to make the implement the full ISearchContent interface. Also, since the .NET API and the search engine doesn’t distinguish between properties and methods we can create an extension method for A or B and configure the client’s convention to include it when indexing instances of those types, thereby adding the SearchTitle field without modifying the classes.

In other words, we can think of unified search as something similar to mixins in object oriented programming. By adding types to the UnifiedSearchRegistry we can “mix in” that they should be included when searching for ISearchContent and by adding properties to the types and/or extension methods to them we can “mix in” some, or all, of the members of ISearchContent.

How to use it

Using unified search to search for CMS content, both pages and uploaded files, is easy. Simply create a search query using either the UnifiedSearch or UnifiedSearchFor methods invoked on the SearchClient. Execute the query using GetResult and do what you want with the result, typically iterate over each hit in a view.

using EPiServer.Find;
using EPiServer.Find.Framework;
using EPiServer.Find.Cms;
using EPiServer.Find.UnifiedSearch;

var result = SearchClient.Instance
    .UnifiedSearchFor(Query)
    .GetResult();

foreach (UnifiedSearchHit hit in result)
{
    Response.Write(hit.Url);
    Response.Write(hit.Title);
    Response.Write(hit.Excerpt);
}

If you want to customize what is indexed and/or returned for your CMS content you can either add a property with the same type and name as one of the properties in ISearchContent or create and include an extension method matching such a property. For instance, the CMS integration includes a default SearchSection method to PageData objects. If we don’t like that one, or want to modify it in some cases, we can add a property named SearchSection to our page type class.

public abstract class SitePageData : PageData
{
  [Ignore]
  public virtual string SearchSection
  {
    get
    {
      var section = this.SearchSection();
      if (!string.IsNullOrWhiteSpace(section))
      {
        return section;
      }

      if (ParentLink.CompareToIgnoreWorkID(
            ContentReference.StartPage))
      {
        return PageName;
      }

      return null;
    }

    //Other properties
}

The same goes for SearchText, the CMS integration includes a default method which we can override by adding our own property:

public class NewsPage : StandardPage
{
  [Ignore]
  public string SearchText
  {
    get
    {
      return MainBody.ToHtmlString(
        PrincipalInfo.AnonymousPrincipal);
    }
  }
  //Other properties
}

Type conditional filtering

So we’re able to search for objects that don’t implement ISearchContent as if they were. If they happen to have properties or included methods with matching names as properties declared in ISearchContent they’ll also be returned if we filter by them. But keep in mind that what’s stored in the object is still the full objects. This, and the fact that we can filter by type using Find, means that we can apply additional criteria to objects of some types using type conditional filtering.

For instance, let’s say we want to search for everything that is included as ISearchContent (typically PageData and UnifiedFile) and apply a filter. For PageData objects we also want to add an additional criteria. We can then do things like this:

SearchClient.Instance
  .UnifiedSearch()
  .Filter(x => 
    x.SearchTitle.Prefix("A")& (!x.MatchTypeHierarchy(typeof(PageData))
       | ((PageData)x).CreatedBy.Match("Joel"))
    ).GetResult();

In the above code we first add a filter that requires objects to have a SearchTitle field with a value starting with “A”. We then also require the objects to either NOT be of type PageData OR, if they are, be created by someone named Joel.

Looks a bit complex? Yes. Something we’d do every day? Probably not. Powerful? I think so.

When to use it

The Unified Search concept is generally useful when you:

  • Build standard search pages that don’t require you to filter on type specific properties.
  • Build generic functionality and don’t know what types it will be used for.

The regular Find query API is better for:

  • Most querying scenarios. That is, content retrieval/navigations/listings that doesn’t involve free text search.
  • When you want to do fine grained and type specific filtering

Do I have to use it?

No. The regular, type specific, fluent, strongly typed querying API is still there and better than ever. Unified Search is just sugar on top.

WTF are you trying to say, I don’t get this mumbo jumbo!

That’s cool. There will be more hands on posts in the future about how to actually use Unified Search. In practice, Unified Search makes it even easier to use Find in some scenarios while this post looks under the covers and explains how it’s implemented.

Building a search page for an EPiServer 7 site with EPiServer Find

$
0
0

Although EPiServer Find can be used for many things on an EPiServer 7 site, the most common scenario is probably free text search pages and while there’s plenty of documentation out there there’s (so far) no ready-to-use search page. In this post you’ll find source code for such a ready to use search page for the new Alloy Tech templates that ship with EPiServer 7 using Find.

While the purpose of this post, and the code, is to show how to build a better search page for the Alloy templates most of the code and concepts are generic and can be reused on just about any EPiServer 7 site. The search page discussed in this post reuses CSS classes from Bootstrap and some from the Alloy templates and looks similar to the one that exists out-of-the-box in the Alloy templates. However, since it uses Find it offers better free text search and best bets. It also brings searching in VPP files, highlighting of keywords, a facet with filters for section on the site and paging. Using a number of properties on the search page’s page type it also allows editors to control some of it’s functionality. Below is an example of how it looks.

EPiServer-Alloy-Find-Search-Page-Example

Code walk through

The search page uses the new Unified Search concept meaning that the code for the basic search functionality is very short. I have however also included some customization of the out-of-the-box functionality as well as made it possible for editors to control some of the search page’s behavior.

Instead of cluttering up this post with extracts from the source code and a walk through of them I’ve made the full, annotated, source code available here. Note that you can switch between files using the “Jump to” menu in the top right corner.

Jump-to-menu

Most important is the markup for the template, FindTemplate.aspx and its code behind file, FindTemplate.aspx.cs, but I’d recommend looking through all of the files, especially the initialization module in which some customization of the default indexing as well as projection of image URLs is made.

Downloadable source code

You’re also most welcome to download the source code and use it yourself. In the zip file you’ll find a directory structure that matches that of the Alloy templates meaning that you can simply paste all of the folders into the root folder of an Alloy Tech site. Just remember to paste them through Visual Studio or manually include the files in your project.

In order to use the search page in a new Alloy Tech site after pasting the downloaded source code here’s what you’ll need to do:

  1. Register a user on find.episerver.com. Note that this requires e-mail activation before you can proceed.
  2. Create a development index. Pick English as the only language.
  3. Once you've created the index you can see the configuration you need ready to cut-n-paste. Copy everything except for the first and last line of the config into the file /[Configuration]/Common/Web.Common.config.
  4. Optional: Configure Find's shell module by copying the config described here into the episerver.shell section in /[Configuration]/EPiServer/web.config. This config is added to the web.config file in the web root by Find’s NuGet package but will get overridden when compiling due to the config handling in the Alloy templates.
  5. Install the Find CMS integration using NuGet. (Add EPiServer's feed and get this package). Alternatively, download it from EPiServer World and add all of the DLLs as reference.
  6. Locate the scheduled job "EPiServer Find CMS Indexing Job" in admin mode and run it manually. This will index all existing pages. Future changes to pages will be automatically indexed.
  7. Create a page of the new page type (FindSearchPage). On the start page change Search Page property (on the Site Settings tab) to point to the newly created page.
  8. Search using the quick search box in the site's header.

After creating the search page you can play with it’s different properties to control how it works, for instance to control whether matching keywords should be highlighted. You may also want to upload a PDF or Word document to Global Files and search for a word in it.

Search statistics

Apart from a solution for free text search and querying for content Find also offers search statistics. I haven’t implemented that on this search page as I wanted to keep the code focused on showing how to build the actual search functionality. Not using the search statistics functionality on a real site would however be a shame and I’ll get back to how to add that in a future post.

EPiServer 7 and MVC – How to customize rendering of properties

$
0
0

When building a site with EPiServer CMS 7 and ASP.NET MVC the standard way of rendering a property is to use the PropertyFor helper method. Here’s an example of a simple view that renders a XhtmlString property and a LinkItemCollection property.

@model Pigeon.Models.Pages.StandardPage

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

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

In both of the above cases the PropertyFor method will eventually call the ASP.NET MVC helper method DisplayFor to render the properties. The DisplayFor method uses conventions to locate a partial view depending of the type of object being rendered. However, in an empty EPiServer 7 MVC site no such partial view exists for XhtmlString, LinkItemCollection or any other built in property type. Yet, when viewing a page using the above view they are displayed.

EPiServer-7-MVC-properties-displayed

Clearly, something is written to the output. Inspecting the elements we see that the MainBody XhtmlString is outputted as is and the LinkItemCollection is rendered as an ul/li list.

EPiServer-7-MVC-properties-markup

Although no template for built in properties exists in our project the PropertyFor and DisplayFor methods can render them anyway. That’s nice, but what’s going on here? In fact a display template for built in property types such as LinkItemCollection and XhtmlString does exist on our site, just not in our project.

Default display templates for built in property types

While the ASP.NET MVC infrastructure by convention looks in /Views/Shared/DisplayTemplates to find display templates EPiServer has registered another path for resolving views, [CMS_Installation_Directory]/Application/Util/Views/. Looking in this folder we’ll find display templates for a bunch of property types:

EPiServer-7-MVC-display-templates-folder

Opening LinkItemCollection.ascx reveals this:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<EPiServer.SpecializedProperties.LinkItemCollection>" %><%@ Import Namespace="EPiServer.Web.Mvc.Html" %><ul><% foreach(var linkItem in Model ?? Enumerable.Empty<EPiServer.SpecializedProperties.LinkItem>()) %><% { %><li><%: Html.PageLink(linkItem)%></li><% } %></ul>

What if we want to change how a certain type of property is rendered? Technically we could make changes in these default display templates, but that would effect all sites on the same computer and would break after an EPiServer upgrade. In other words – don’t do that.

Using custom display templates for built in property types

If we want to change how a property type is rendered using the PropertyFor or DisplayFor methods we simply create a display template with the name of the type which we put in the folder ASP.NET MVC will look for by convention, by default ~/Views/Shared/DisplayTemplates. For instance, to change the rendering of LinkItemCollection we can create a view named LinkItemCollection.

Creating-display-template-for-LinkItemCollection

LinkItemCollection-display-template-in-Solution-ExplorerBy putting this in the “local” display templates folder this view will take priority and will be used instead of EPiServer’s default one. In our own display template we’re free to render LinkItemCollections however we want. We could for instance render them using ol lists instead of ul lists:

@using EPiServer.SpecializedProperties
@model LinkItemCollection<ol>
    @foreach(var linkItem in Model ?? Enumerable.Empty<LinkItem>())
    {<li>@Html.PageLink(linkItem)</li>
    }</ol>

Custom-rendering-of-LinkItemCollection

Passing arguments to display templates

In addition to being able to completely change the default rendering of properties using custom display templates we can also reap another, perhaps more useful, benefits from creating them. Looking at the default templates in EPiServer’s installation directory we can see that they aren’t very customizable. For instance, let’s say we’re happy with rendering LinkItemCollections using ul/li lists but in some places we would like to pass a CSS class or two to the PropertyFor method which should be added to the list. That is, we’d like to do something like this:

@Html.PropertyFor(x => x.Links, new { @class="nav nav-tabs"})

Without customizing how LinkItemCollections are rendered passing the additional view data like above won’t have any effect. But we can change that using our own display template.

@using EPiServer.SpecializedProperties
@model LinkItemCollection<ul class="@ViewData["class"]">
    @foreach(var linkItem in Model ?? Enumerable.Empty<LinkItem>())
    {<li class="@ViewData["itemClass"]">@Html.PageLink(linkItem)</li>
    }</ul>

In the above display template for LinkItemCollections we look for a “class” value in the view data which we add to the ul element. We also look for a “itemClass” value which we add to each li element. Now passing a CSS class in the additionalViewData argument to the PropertyFor method has an effect.

Rendering-EPiServer-property-with-custom-css-class

EPiServer 7 and MVC – Getting the URL for a page

$
0
0

Pretty much since the dawn of time, or at least since the Finnish heavy metal band Lordi won the Eurovision Song Contest with the song Hard Rock Hallelujah, we have been able to get URL for a page in EPiServer CMS using the LinkURL property on the PageData objects. With EPiServer 7 that changes. Although the LinkURL property still exists and hasn’t changed the recommended approach now is to use routing.

When using Web Forms the LinkURL property still produces the expected result, but when used in an ASP.NET MVC view it will be outputted as the internal URL (/link/some_weird_guid.aspx?id=42… etc). Luckily EPiServer provides a couple of ways to get the URL for a page when building sites with MVC.

Url.PageUrl

One way of getting the URL for a page when using MVC is to use an extension method for the UrlHelper class named PageUrl. This method resides in a class in the EPiServer.Web.Mvc.Html namespace and has a single parameter of type string named classicalUrl. Feed it the LinkURL property of a PageData object and the relative URL for the page is returned. In a view it may look like this:

@model MySite.Models.Pages.StandardPage

@Url.PageUrl(Model.LinkURL)

UrlResolver.GetVirtualPath

The PageUrl extension method is nice and simple but in some cases we don’t have access to an UrlHelper instance. In other cases we may want to control what language version of the page we want the URL to be to. Or, we may want the URL for a different action than Index. In those cases we can use the UrlResolver class which is located in the EPiServer.Web.Routing namespace.

The UrlResolver class has a method named GetVirtualPath with two overloads. In it’s simplest form it requires a single argument, a ContentReference. Other overloads allow us to pass it a language and/or additional route values and a RequestContext. Get a hold of an instance of UrlResolver and invoke the method with a ContentReference and, just like with the PageUrl method, the relative URL for the page is returned.

//using EPiServer.Web.Routing;

var urlResolver = ServiceLocator.Current.GetInstance<UrlResolver>();
var pageUrl = urlResolver.GetVirtualPath(currentPage.ContentLink);

EPiServer 7 – How to check if the page is in edit mode

$
0
0

After trying a number of ways to determine if a page or block is rendered within EPiServer CMS’ edit mode or not I stumbled upon a nice little helper class in the EPiServer API named PageEditing. Using this we can easily check if the current request is for a content object in edit mode in templates and preview pages:

//using EPiServer.Editor;

bool inEditMode = PageEditing.PageIsInEditMode;

I tried this for both pages and block in Web Forms as well as with MVC and it seems to work everywhere.

The PageEditing class also has a couple of other nice members, such as the method GetEditUrl for retrieving the URL for editing a content object  and GetChannel which returns the current channel, if any.


EPiServer 7 and MVC – Custom tags and CSS classes when rendering properties

$
0
0

The standard way of rendering properties when building a site using EPiServer 7 and ASP.NET MVC is the PropertyFor HTML helper extension method. The Web Forms equivalent is the EPiServer:Property control which can contain a nested RenderSettings control with which we can control some aspects of how the property is rendered. When using the PropertyFor method it’s possible to do the same by passing an object with one or several of those settings as properties. However, the names of these settings aren’t obvious and when using PropertyFor IntelliSense doesn’t help as they are passed as members of an anonymous object.

Therefore, for my own sanity's sense, here’s a quick reference. Please let me know if I’ve missed any setting!

Quick reference

  • CustomTag
  • CssClass
  • ChildrenCustomTagName
  • ChildrenCssClass
  • EditContainerClass

Custom tag

Property name: CustomTag. Example:

@Html.PropertyFor(x => x.PageName, new { CustomTag = "span"})

Note that the CustomTag primarily applies to edit mode when using MVC, with some notable exceptions such as content areas. For instance for a string property no tag is rendered when a page is displayed in view mode. For such types EPiServer will however add a wrapper for the text when displaying it in edit mode and the CustomTag can be used to control what type of element EPiServer wraps it in.

For some property types, such as LinkItemCollection, almost no settings have any effect when using the default rendering. It’s however possible to work around that using custom display templates.

Custom CSS class

Property name: CssClass. Example:

@Html.PropertyFor(x => x.Teasers, new { CssClass = "row" })

Passing a custom CSS class only affects those properties for which a HTML element is created when using PropertyFor, such as content areas. In other words, in the below example the CssClass setting won’t actually do anything at all as no element is created when rendering string properties.

//The CSS class won't be added as no element is created
@Html.PropertyFor(x => x.PageName, new { CssClass = "big" })

Customizing wrapping elements for blocks in content areas

When rendering content area properties a wrapping element will be inserted for each block or other type of content in the content area. The tag can be customized using ChildrenCustomTagName. It’s also possible to add CSS classes to these elements using ChildrenCssClass. Example:

@Html.PropertyFor(x => x.Teasers, 
    new
        {
            ChildrenCustomTagName ="span", 
            ChildrenCssClass = "block"
        })

Customizing wrapping elements in edit mode

When properties are rendered in edit mode EPiServer inserts a wrapping element around the property’s value. By default this wrapping element is a div with a CSS class named epi-editContainer which forces the element to have a minimum width and height. It’s possible to change that CSS class to a custom class using EditContainerClass. By utilizing this we can customize the size and other characteristics of the wrapping element which I’ve personally found very useful in some specific cases. Example:

@Html.PropertyFor(x => x.PageName, 
    new { EditContainerClass = "inline" })

Building a PDF Channel for EPiServer 7

$
0
0

Channels in EPiServer 7 is an exciting new feature that can be used for a variety of purposes. While it may not be the first use case that comes to mind we can use channels to enable rendering of pages on a site as PDF documents.

Last Thursday at the Stockholm EPiServer Meetup I demoed a quick and easy way to create a PDF channel for an EPiServer 7 site using the open source library Rotativa. While it may not be sexiest use case for channels it may actually be useful for some sites. Not to mention that it’s fun to be able to surf around on the site in PDF format while in edit mode. Here’s how to build it.

The channel

Channels in EPiServer 7 couldn’t be much easier to define. We simply create a class inheriting from DisplayChannel and implement two abstract methods.

//using System.Web;
//using EPiServer.Web;

public class PdfChannel : DisplayChannel
{
    public override bool IsActive(HttpContextBase context)
    {
        return context.Request.QueryString["pdf"] == 1.ToString();
    }

    public override string ChannelName
    {
        get { return "PDF"; }
    }
}

The above class will add a new channel to a site which will be selectable in edit mode. It will also be active if there’s a query string parameter named pdf with a value of 1 in the URL. That is, no matter which page is being requested it will be active if the query string parameter is there.

The controller

Rotativa is a nice little library which wraps the larger wkhtmltopdf project which can be used to generate PDF documents from HTML markup through the use of WebKit. Rotativa is easily downloadable from NuGet.

As Rotativa is built for being used with ASP.NET MVC we’ll add a renderer for the PDF channel in the form of an MVC controller. With EPiServer 7 doing so will work no matter if the rest of the site is built using MVC or Web Forms.

//using System.Web.Mvc;
//using EPiServer.Core;
//using EPiServer.Framework.DataAnnotations;
//using EPiServer.ServiceLocation;
//using EPiServer.Web.Mvc;
//using EPiServer.Web.Routing;
//using Rotativa;

[TemplateDescriptor(Inherited = true, Tags = new [] { "pdf" })]
public class PdfController : PageController<PageData>
{
    public ActionResult Index(PageData currentPage)
    {
        var url = ServiceLocator.Current
            .GetInstance<UrlResolver>()
            .GetVirtualPath(currentPage.ContentLink);

        return new UrlAsPdf(url)
        {
            FormsAuthenticationCookieName = ".EPiServerLogin"
        };
    }
}

The above controller simply receives the current page, figures out its URL in an MVC and Web Forms agnostic way and returns a UrlAsPdf result which is an action result type added by Rotativa. To ensure that the PDF generator will be able to reach the page even in edit mode when using ASP.NET MVC we alse ensure that it can use the correct authentication cookie.

The result – any page as PDF

With the channel and renderer in place we’re now able to select the PDF channel in edit mode to preview any page as a PDF document. Public visitors are also able to get any page as PDF by adding pdf=1 to the query string. Of course, in a real world scenario we’d still have the bulk of work in front of us if we wanted the channel to be useful as we’d probably want to use a separate stylesheet. Nevertheless, here’s how it may look in edit mode.

pdf-channel

Limiting content and page reference properties to values of a specific type in EPiServer CMS

$
0
0

By limiting available options to only valid values for PageReference and ContentReference properties in EPiServer 7 we can improve the user experience for editors. By doing so we can also protect our sites from invalid and potentially harmful editorial settings.

A fairly common scenario when building EPiServer CMS sites is that we as developers add a PageReference property to a page type whose value should be a reference to a page of a specific page type. We may for instance want editors to select a product page, a search page or a person page. Simply adding a PageReference property works but then we can’t be sure that the editor will actually set it to a page of the type we expect. Also, for editors, having to sort through the entire page tree to locate a page of a limited number of pages of that types is tedious.

A filtered page reference property

In previous versions of EPiServer it’s been tricky to achieve a solution that both validated that the selected page was of the correct type and enable editors to only have to choose amongst relevant pages. In the Alloy templates for EPiServer 7 however an elegant solution to this problem is illustrated. The ContactBlock class has a property named ContactPageLink which when rendered in edit mode looks like this:

contactpageselector

As you can see, editors are only able to select pages of a specific type and they are also able to do so by using a drop down with all those pages rather than having to sort through the entire page tree. Me like!

The code for the ContactPageLink property looks like this:

[Display(
    GroupName = SystemTabNames.Content,
    Order = 3)]
[UIHint(Global.SiteUIHints.Contact)]
public virtual PageReference ContactPageLink { get; set; }

The magic ingredient here is the UIHint. Using that the property is mapped to an “editor descriptor” named ContactPageSelector. The code for that looks like this:

[EditorDescriptorRegistration(
    TargetType = typeof(PageReference), 
    UIHint = Global.SiteUIHints.Contact)]
public class ContactPageSelector : EditorDescriptor
{
    public override void ModifyMetadata(
        ExtendedMetadata metadata, 
        IEnumerable<Attribute> attributes)
    {
        SelectionFactoryType = typeof(ContactPageSelectionFactory);
        ClientEditingClass = "epi.cms.contentediting.editors.SelectionEditor";
        base.ModifyMetadata(metadata, attributes);
    }
}

The editor descriptor instructs the edit mode to render the property as a dropdown by setting the ClientEditingClass. It also hooks up the dropdown to a data source, a custom selection factory named ContactPageSelectionFactory. The code for the selection factory looks like this:

public class ContactPageSelectionFactory : ISelectionFactory
{
    public IEnumerable<ISelectItem> GetSelections(
        ExtendedMetadata metadata)
    {
        var contactPages = SiteDataFactory.Instance
            .GetContactPages();

        return new List<SelectItem>(contactPages
            .Select(c => new SelectItem
                {
                    Value = c.PageLink, 
                    Text = c.Name
                }));
    }
}

The selection factory uses more custom code in the Alloy templates to locate all pages of type ContactPage and then creates a SelectItem for each of them. These SelectItem objects are what’s used to populate the dropdown while the selected value is stored as the property’s value.

A generic approach

I really like what we’re achieving using the above code! As a developer I can ensure that editors can only insert valid values into the property. Also, given that the number of pages of the desired type is fairly small, the user experience for editors is greatly improved.

There’s just one problem. If we want to use the same approach for other properties where editors should be able to choose pages, or other types of content such as blocks, of a different type we can’t reuse any of the existing code but instead have to come up with another UI hint string, add another editor descriptor as well as another selection factory.

Instead of having to do all that tedious and error prone work I’d like a generic solution with which all we’d have to do to limit the possible selections to content of a specific type would be to add an attribute. Like this:

[ContentSelection(typeof(ProductPage))]
public virtual PageReference Product { get; set; }

[ContentSelection(typeof(ContactPage))]
public virtual PageReference ContactPage { get; set; }

[ContentSelection(typeof(TeaserBlock))]
public virtual ContentReference Teaser { get; set; }

Looks pretty nice, right? Let’s do it!

Solution

First of all we need an attribute which could only be simpler if attributes could have generic type parameters.

[AttributeUsage(
    AttributeTargets.Property, 
    AllowMultiple = false)]
public class ContentSelectionAttribute : Attribute
{
    public ContentSelectionAttribute(Type contentType)
    {
        ContentType = contentType;
    }

    public Type ContentType { get; set; }
}

The editor descriptor

With the attribute in place we can add it to properties but it won’t yet have any effect. To make the attribute matter we’ll need to create an editor descriptor.

//using System;
//using System.Collections.Generic;
//using System.Linq;
//using EPiServer.Core;
//using EPiServer.Shell.ObjectEditing;
//using EPiServer.Shell.ObjectEditing.EditorDescriptors;

[EditorDescriptorRegistration(
    TargetType = typeof(ContentReference))]
[EditorDescriptorRegistration(
    TargetType = typeof(PageReference))]
public class ContentSelector : EditorDescriptor
{
    public override void ModifyMetadata(
        ExtendedMetadata metadata, 
        IEnumerable<Attribute> attributes)
    {
        var contentSelectionAttribute = metadata.Attributes
            .OfType<ContentSelectionAttribute>()
            .SingleOrDefault();

        if(contentSelectionAttribute != null)
        {
            SelectionFactoryType =  
                typeof (ContentSelectionFactory<>)
                    .MakeGenericType(
                        contentSelectionAttribute.ContentType);

            ClientEditingClass = 
                "epi.cms.contentediting.editors.SelectionEditor";
        }

        base.ModifyMetadata(metadata, attributes);
    }
}

As opposed to the Alloy templates we register our editor descriptor for both PageReference and ContentReference and omit the UIHint. This means that it will be used for all properties of type PageReference as well as ContentReference.

In the ModifyMetadata method we check if the given property is annotated with our ContentSelection attribute. If it is we hook it up to a custom selection factory with the type specified in the attribute as type argument.

A generic selection factory

Speaking of the custom selection factory, that’s our next and final step.

//using System.Collections.Generic;
//using System.Linq;
//using EPiServer.Core;
//using EPiServer.DataAbstraction;
//using EPiServer.ServiceLocation;
//using EPiServer.Shell.ObjectEditing;

public class ContentSelectionFactory<T> : ISelectionFactory
    where T : IContentData
{
    private Injected<IContentTypeRepository> 
        ContentTypeRepository { get; set; }
    private Injected<IContentModelUsage> 
        ContentModelUsage { get; set; }
    private Injected<IContentLoader> 
        ContentLoader { get; set; }

    public IEnumerable<ISelectItem> GetSelections(
        ExtendedMetadata metadata)
    {
        var contentType = ContentTypeRepository.Service
            .Load<T>();
        if(contentType == null)
        {
            return Enumerable.Empty<SelectItem>();
        }

        var selectItems = 
            ContentModelUsage.Service
            .ListContentOfContentType(contentType)
            .Select(x => 
                x.ContentLink.CreateReferenceWithoutVersion())
            .Distinct()
            .Select(x => ContentLoader.Service.Get<T>(x))
            .OfType<IContent>()
            .Select(x => new SelectItem
                {
                    Text = x.Name,
                    Value = x.ContentLink
                })
            .OrderBy(x => x.Text)
            .ToList();
        selectItems.Insert(0, new SelectItem());
        return selectItems;
    }
}

The above code is quite the mouth full. Sorry about that. Contrary to it’s appearance however the essence of what it does is straight forward – it returns a SelectItem for each page on the site of the type specified in the type parameter. It also adds a an empty item first in the list before returning it. If it hadn’t properties with the attribute would appear prepopulated when editors create a new page and editors wouldn’t be able to set their values to null (we can always add a Required attribute if they shouldn’t be able to do that.

The desired functionality of the selection factory may vary from site to site. For instance, in a multisite scenario we’d might like to filter out content from other sites. As for the technical implementation there are several ways to achieve the same result but that’s a different blog post.

Result

With the above three classes in place we now have a generic way of creating filtered page and content reference properties whose selectable values are limited to a specific content type. As an example, the code I wanted to be able to write…

[ContentSelection(typeof(ProductPage))]
public virtual PageReference Product { get; set; }

[ContentSelection(typeof(ContactPage))]
public virtual PageReference ContactPage { get; set; }

[ContentSelection(typeof(TeaserBlock))]
public virtual ContentReference Teaser { get; set; }

… gives us this in edit mode …

multiple-content-selectors

multiple-content-selectors-expanded-1

multiple-content-selectors-expanded-2

EPiServer Find Training

$
0
0

I occasionally work as a technical trainer for EPiServer Training. I enjoy that as I get to meet people from different companies as well as people from different parts of the country, and sometimes the world. As one may also guess given my background with 200OK and Truffler, the product now known as EPiServer Find, I feel strongly about EPiServers coolest product, Find ;-)

EpiServerLogo_FINDTherefore I’m really happy to announce that I’ll be giving a course in EPiServer Find starting with a first course date in Stockholm set to February 1st. So, if you work with EPiServer products, or if you’re curious about the possibilities that an inverted index offers both in terms of performance and functionality, and want to learn how to build awesome things with Find, be sure to check it out!

Shameless promotional plug done.

Upgrading a site from EPiServer CMS 6 to EPiServer 7

$
0
0

I’ve been investigating the upgrade experience from EPiServer CMS version 6 R2 to version 7 lately. Those that have ever upgraded an EPiServer 4 site to version 5 probably know that that’s no easy feat. In fact, I typically recommend my customers to build a new site rather than upgrading. Upgrading from version 5 to 6 on the other hand wasn’t a big deal.

EPiServer 7 features a totally new edit mode and quite a lot of API changes. On the other hand the developers at EPiServer have worked hard to maintain backwards compatibility in a way that should enable us to gradually start using new API features rather than having to rewrite everything just to get an upgraded site to work.

Also, while EPiServer 7 requires .NET 4 instead of .NET 3.5 there hasn’t been any major changes in the underlying platform which EPiServer builds upon, as opposed to the version 4 to 5 upgrade where EPiServer threw out a bunch of custom stuff in favor of standardized components introduced in .NET 2.0 and ASP.NET 2.0.

So, in theory upgrading from 6 R2 to 7 should be a smooth ride. We should expect custom properties to work but not fit into the new design of the edit UI and of course customizations of the old edit UI would have to be replaced. Other than that though, there shouldn’t be any major issues.

Of course we all know that theory and reality rarely meet when dealing with upgrades. Therefor I decided to get my hands dirty and do some upgrading for real. As a first step I decided to set up the standard sample site for EPiServer 6, AlloyTech, and upgrade that to EPiServer 7. Here’s how it went.

Deployment Center and .NET 4

Upgrading through EPiServer’s Deployment Center proved to be a simple next-next-next experience. We simply have to select “Upgrade site with SQL Server database” in the All Actions tab and then select a site to upgrade.

DeploymentCenter

Although I’m sure some will run in to edge cases where there will be problems performing the upgrade I had no problems at all. The only warning that I got was that the “project will have to be compiled after upgrading” which I guess was actually more of a friendly reminder than an actual warning.

After having done the upgrade with Deployment Center I found that it had created a new application pool running .NET 4 in IIS and changed the site’s configuration to use that. Proceeding to open up the project in Visual Studio I ran into a funny little oddity.

Before I even got to open the project though, I was faced with a prompt where I had to choose whether to change the site back to .NET 2.0 or not as the project was still targeting .NET 3.5.

Dialog

Of course I didn’t want that so I clicked no.

Now, remember that EPiServer 7 requires .NET 4 and that Deployment Center had told me I needed to recompile the project. The old Alloy templates are by default configured to target .NET 3.5 and neither myself nor Deployment Center’s upgrade had changed that. Deployment Center had however changed the EPiServer references to their EPiServer 7 versions. Clearly the project shouldn’t compile as it was still targeting an older .NET framework than the referenced EPiServer assemblies.

It did however compile! Of course it didn’t actually, but since I hadn’t changed a single source file Visual Studio didn’t think it actually needed to do anything and reported a successful compilation. Of course, when browsing the site I was met with an angry yellow screen of death, saying “Method not found: 'Boolean EPiServer.Core.PageReference.CompareToIgnoreWorkID(EPiServer.Core.PageReference)'.”.

So, I went ahead and changed the Target Framework to .NET 4.

Fiftyseven compilation errors

Changing target framework made Visual Studio recognize that it actually needed to do some work when I asked it to compile the next time. Now, however, when one could almost expect a successful compilation I was faced with 57 angry errors.

CompilationErrors

Didn’t I say something about EPiServer’s developer having worked hard to maintain a level of backward compatibility with which we should be able to adjust to API changes gradually? As it turned out a lot of the compilation errors were actually warnings about using deprecated methods. Changing the project to not treat warnings as errors brought the compilation error list down to 14 errors.

WarningsAsErrors

The remaining 14 “real” errors could be grouped into three categories.

First of all there were a number of errors due to EPiServer having marked types and methods as obsolete in a way that makes the compiler treats usages of them as errors. In most of those cases fixing the error was easy as pie as EPiServer had also provided a good workaround suggestion in the obsolete attributes.

In one case it wasn’t quite as easy though. As EPiServer 7 supports “any content” in the form of IContent rather than just pages in the form of PageData objects a lot of methods and properties that used to work with PageReferences now expect or return ContentReferences instead. In the AlloyTech code there is a method that looks like this:

SoftLink softLink = (SoftLink)link;
return String.Format("{0} [{1}]", 
    DataFactory.Instance.GetPage(softLink.OwnerPageLink).PageName, 
    softLink.OwnerPageLink.ID);

This little bugger proved slightly more problematic to fix than the other compilation errors due to things having been marked as obsolete. The problem is that SoftLink.PageLink is obsolete and we’re instructed to instead use SoftLink.ContentLink which returns a ContentReference instead of a PageReference. Only, the GetPage method, which I guess one could argue is semi obsolete, requires a PageReference.

Luckily, there’s an easy solution to this. We simply use Get<PageData>() instead of GetPage() as the former is satisfied with a ContentReference as argument. Alternatively we could have constructed a PageReference as the ContentReference has all the required data for doing so, but why bother when we should prefer the new and shiny Get<T>() over the old and dusty GetPage() anyway.

Four compilation errors

Having mostly just followed the instructions from EPiServer, replacing usages of obsolete types and methods with the suggested equivalents I was down to four compilation errors.

Two of those were caused by some code which disabled On Page Edit for a couple of templates. As there isn’t any such thing as (the old) On Page Edit (or DOPE as it’s really called) fixing this simply involved removing the code that didn’t compile.

Finally the last two errors were due to code that instantiated the SiteConfigDB class which no longer has a parameter less constructor and instead requires an IDatabaseHandler as argument. While I can’t say that I’m very familiar with either the SiteConfigDB class or the IDatabaseHandler interface it was quite easy to guess how to fix that – by using the ServiceLocator.

//Before
var siteConfigDB = new SiteConfigDB();

//After
var databaseHandler = ServiceLocator.Current.GetInstance<IDatabaseHandler>();
var siteConfigDB = new SiteConfigDB(databaseHandler);

Build succeeded!

An URL isn't no string no more

Having fixed the last compilation errors I was happy to see that the site’s start page loaded without runtime errors. However, it looked a bit bare as it lacked a logotype. Also, when loading some pages I got a yellow screen of death saying “Unable to cast object of type 'EPiServer.Url' to type 'System.String'”.

In both cases the problem was code that retrieved a URL property’s value from a PageData object using indexer syntax and then casted the value to a string. That worked fine in EPiServer 6 but in 7 the value type for an URL property isn’t a simple string anymore. Instead it’s a Url, as in EPiServer.Url.

So, to get back the logotype and fix runtime errors due to type conversion errors I had to modify such code. For instance, here’s what I did to fix the logotype:

//Before
Logotype.ImageUrl = StartPageData["Logotype"] as string ?? string.Empty;

//After
var logoUrl = StartPageData["Logotype"] as Url;
if (logoUrl != null && !logoUrl.IsEmpty())
{
    Logotype.ImageUrl = logoUrl.ToString();
}

Personally I can’t see any reason why EPiServer couldn’t have added an implicit operator from Url to string which might have made life easier for us. Either way, fixing these issues was straight forward. I could however imagine quite a few projects where developers have written “safe” code that handles when a property’s value isn’t of a specific type. Tracking down bugs due to the changes of value types for properties such as PropertyUrl may prove tiresome in such projects.

Custom properties

According to what I’ve heard and understood custom properties *should* continue to function as before after an upgrade, albeit with custom editing controls displayed in an IFrame. Curious about this, one of the first things I did after successfully upgrading the site was to head over to a Blog item page as I knew that those featured a custom property named BlogTags. Here’s how such a property looked in EPiServer 6’s edit mode:

CustomProperty

Disappointingly I found the property displayed as a regular string in 7’s forms edit mode.

Custom_property_in_7

Analyzing the custom property I realized that it was, given that I’ve worked quite a bit with EPiServer 7, fairly obvious why the custom property control wasn’t used. The old way of connecting a property (as in class inheriting PropertyData or one of its sub types) was to override the CreatePropertyControl method and return an instance of the control that should be used. That’s exactly what was done in this custom property:

public override IPropertyControl CreatePropertyControl()
{
    return new PropertyBlogTagsControl();
}

In EPiServer 7, that doesn’t do anything. Instead I needed to connect the control to the property type in some other way. I did so by adding an EditorHint attribute to both the property class and the control.

[EditorHint("BlogTags")]
public class PropertyBlogTagsControl 
    : Web.PropertyControls.PropertyTextBoxControlBase
//Class definition

[PageDefinitionTypePlugIn]
[EditorHint("BlogTags")]
public class PropertyBlogTags : PropertyString
//Class definition

Et voilà! The property could be edited using a “legacy editor”.

custom_property_with_legacy_editor

Conclusion

After having upgraded the EPiServer 6 version of the Alloy site, twice, I’m surprisingly unsurprised. The experience was pretty much what I had expected as the upgrade did involve fixing some, but not that many, compilation errors. The custom property still worked, albeit after a little adjustment and not exactly looking superb in the context of the sleek new edit UI.

Based on my experiences so far I’d say that upgrading from version 6 to 7 is neither exactly easy nor is it a hellish experience. If one has some experience with EPiServer 7 and the site doesn’t rely on major customizations of the old edit mode I’d account for maybe a days work to perform the initial upgrade of a site.

Reaping the benefits of EPiServer 7’s new edit UI and live preview functionality may however require far more time, much depending on how the site was originally built. If for instance a lot of properties wasn’t rendered using the EPiServer:Property control the site would work but we’d have a fair amount of work in front of us before editors would be able to work outside of forms edit mode.

Likewise, what I’ve described here is the initial steps of the technical upgrade. I still have 43 compiler warnings that should be tended to, configuration files to go through and new coding practices to adopt in the sites code base. Not to mention deployment.

Knowledge about EPiServer 7 is key

I should add that if I hadn’t had worked a lot with EPiServer 7 prior to the upgrade I probably would have had to spend quite a lot more time on the initial upgrade. So, before you think about upgrading anything beyond the simplest of sites, be sure that you have some knowledge and experience with the new version. With that, the basic upgrade shouldn’t be too hard and you can focus on details and making the site nice to work with for editors rather than having to get into a boxing fight with the compiler.

Result

I leave you with this, a screenshot of the good old (well… old at least ;-)) AlloyTech site in EPiServer 7’s new edit UI, Sparrowhawk.

Old_Alloy_in_Sparrohawk

Viewing all 78 articles
Browse latest View live