Pragmatically add request tracking for an item in Sitecore

Sitecore’s Engagement Analytic’s engine automatically tracks all page requests. When you assign profile cards to items this also triggers data about a persons interests to start being built up against their visit record, which can then be used to create a personalized site experience.

However what if you want items that are not pages to also contribute to a users profile?

This scenario came about with a recent site we took over that the client wanted to add personalisation too. The site (for unknown reasons) had been built with one product page which looked at a querystring parameter to determine the product information to be displayed (the querystring format was hidden from end users through the use of a URL rewrite). Product data was being stored as Sitecore items allowing them to have profile cards assigned, but as the item was never visited the profile cards values were never applied to the users profile.

After a bit of searching through the Sitecore.Analytics.dll I stumbled across the TrackingFieldProcessor class. This class contains a process function that takes an item parameter an in turn triggers all the functionality for processing campaigns, profiles and events related to the item.

To use it your code would look like this:

Sitecore.Data.Database db = Sitecore.Configuration.Factory.GetDatabase("web");
(new TrackingFieldProcessor()).Process(db.GetItem(new ID("395BDEF7-16CB-4C94-B9B6-A6EAC148401F")));

This will cause the profile key values to be updated but in the visitor history it still looks like the visitor was only looking at the one page. To change those values we can do this:

VisitorDataSet.PagesRow rawUrl = Tracker.CurrentVisit.GetOrCreateCurrentPage();
rawUrl.Url = "new url value";
rawUrl.UrlText = "new url value";

Note: I was unable to find any documentation on these functions, or any official way of doing this. Use at your own risk!

Advertisements

Updating the response headers on your 404 Page in Sitecore

A few weeks ago I blogged about how to create a custom 404 Page in Sitecore. Following on from that, one thing you may notice in the response header of your 404 Page is the status code is 200 Ok, rather than 404 Page not found.

When Sitecore can’t find a page what actually happens is a 302 redirect is issued to the page not found page, which as its an ordinary page will return a 200 Ok. Thankfully Google is actually quite good at detecting pages a being 404’s even when they return the wrong status code, but it would be better if our sites issues the correct headers.

Method 1

The simplest solution is to create a view rendering with the following logic and place it somewhere on your page not found page. This will update the response headers with the correct values.

@{
      Response.TrySkipIisCustomErrors = true;
      Response.StatusCode = 404;
      Response.StatusDescription = "Page not found";
}

However personally I don’t think this a particularly neat solution. The contents of a view should really be left for what’s going in a page rather than interfering with its headers, even if it does have access to the Response object.

Method 2

Rather than using a view my solution is to add some code to the httpRequestEnd pipeline that will check the context items Id against a setting where we will store the Id of the 404 page item in Sitecore and if the two match then update the response header.

The solution will look like this

Pipeline logic

using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Pipelines.HttpRequest;

namespace Pipelines.HttpRequest
{
    public class PageNotFoundResponseHeader : HttpRequestProcessor
    {
        private static readonly string PageNotFoundID = Settings.GetSetting("PageNotFound");

        public override void Process(HttpRequestArgs args)
        {
            if (Sitecore.Context.Item != null && Sitecore.Context.Item.ID == new ID(PageNotFoundID))
            {
                args.Context.Response.TrySkipIisCustomErrors = true;
                args.Context.Response.StatusCode = 404;
                args.Context.Response.StatusDescription = "Page not found";
            }
        }
    }
}

Patch config file

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>    
    <pipelines>
      <httpRequestEnd>
        <processor
          patch:after="processor[@type='Sitecore.Pipelines.PreprocessRequest.CheckIgnoreFlag, Sitecore.Kernel']"
          type="Pipelines.HttpRequest.PageNotFoundResponseHeader, MyProjectName"  />
      </httpRequestEnd> 
    </pipelines>
    <settings>
      <!-- Page Not Found Item Id -->
      <setting name="PageNotFound" value="ID of 404 Page" />
    </settings>
  </sitecore>   
</configuration>

What’s the TrySkipIisCustomErrors property

Quite simply this stops a scenario where you end up on IIS’s 404 page rather than your own. If you don’t set this, when you update the header status code to 404, IIS likes to return the page from it’s settings rather than continuing with your own.

Sitecore Search and Indexing: Creating a simple search

With Sitecore 7, Sitecore introduced the new Sitecore.ContentSearch API which out of the box can query Lucene and SOLR based index’s.

Searching the index’s has been made easier through Linq to Sitecore that allows you to construct a query using Linq, the same as you would use with things like Entity Framework or Linq to SQL.

To do a query you first need a search context. Here I’m getting the a context on one of the default index’s:

using (var context = ContentSearchManager.GetIndex("sitecore_web_index").CreateSearchContext())
{
    ...
}

Next a simple query would look like this. Here I’m doing a where parameter on the “body” field:

using (var context = ContentSearchManager.GetIndex("sitecore_web_index").CreateSearchContext())
{
    IQueryable<SearchResultItem> searchQuery = context.GetQueryable<SearchResultItem>().Where(item => item["body"] == “Sitecore”)
}

But what if you want to add a search to your site. Typically you would want to filter on more than one field, what the user enters may be a collection of words rather than an exact phrase and you’d also like some intelligent ordering to your results.

Here I am splitting the search term on spaces and then building a predicate that has an “or” between each of its conditions. For each condition rather than doing a .Contains on a specific field, I’m doing it on a content field that will contain data for all fields in the item.

using (var context = ContentSearchManager.GetIndex("sitecore_web_index").CreateSearchContext())
{
    IQueryable<SearchResultItem> query = context.GetQueryable<SearchResultItem>();

    var predicate = PredicateBuilder.True<SearchResultItem>();

    foreach (string term in criteria.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries))
    {
        predicate = predicate.Or(p => p.Content.Contains(searchTerm.Trim()));
    }

    SearchResults<SearchResultItem> searchResults = query.Where(predicate).GetResults();

    results = (from hit in searchResults.Hits
                        select hit.Document).ToList();
}

The intelligent ordering of results you will get for free based on what was search for.