Tuesday, May 31, 2016

Sitecore error when rebuilding search indexes

In a scaled Sitecore environment, I was getting a combination of the following errors in conjunction with search (Lucene based) not working as expected:
Exception: System.InvalidOperationException

Message: Could not find configuration node: contentSearch/indexConfigurations/indexUpdateStrategies/syncMaster

ERROR One or more exceptions occurred while processing the subscribers to the 'indexing:end:remote' event.
This was because one of the Sitecore indexes which relies on the master was not being removed by the switch master to web configuration patch file. The following index items need to be deleted on CD servers:
<contentSearch>
  <configuration>
 <indexes>
   <index id="sitecore_master_index">
  <patch:delete />
   </index>
   <index id="sitecore_marketing_asset_index_master">
  <patch:delete/>
   </index>
   <index id="sitecore_testing_index">
  <patch:delete/>
   </index>
   <index id="sitecore_suggested_test_index">
  <patch:delete/>
   </index>
   <index id="sitecore_fxm_master_index">
  <patch:delete />
   </index>
   <index id="social_messages_master">
  <patch:delete/>
   </index>
   <index id="sitecore_list_index">
  <patch:delete/>
   </index>
 </indexes>
  </configuration>
  <indexConfigurations>
 <indexUpdateStrategies>
   <syncMaster>
  <patch:delete />
   </syncMaster>
   <intervalAsyncMaster>
  <patch:delete />
   </intervalAsyncMaster>
 </indexUpdateStrategies>
  </indexConfigurations>
</contentSearch>
In particular, my config file was not patching sitecore_list_index out. After this was removed, all was good again.

301 redirects in a Sitecore item resolver

When migrating to Sitecore from an existing web site, one of the biggest concerns is a change in URLs - especially when it comes to search engine optimization. The common solution to this is a mix of a URL redirect module and sitemaps. However when there are a large chunk of content pages where the URL structure is changing in a common way - news articles, or blog posts for example. A Sitecore item resolver can be a good way to handle this, and by using 301 (moved permanently) is an added bonus to SEO.
using Sitecore.Configuration;
using Sitecore.Diagnostics;
using Sitecore.Links;
using Sitecore.Pipelines.HttpRequest;
using System;
using System.Web;

namespace MyProject.ItemResolvers
{
    public class MyItemResolver : HttpRequestProcessor
    {
        public override void Process(HttpRequestArgs args)
        {
            // Return if Sitecore has found the item
            if (Context.Item != null || Context.Database == null || args.Url.ItemPath.Length == 0) return;

            try
   {
    var itemPath = ""; // Business logic here to find the item based on the Context passed through
    var context = Factory.GetDatabase("web");
    var item = context.GetItem(itemPath);

    if (item != null)
    {
     var urlOptions = new UrlOptions
     {
      AlwaysIncludeServerUrl = true
     };

     var itemUrl = Sitecore.Links.LinkManager.GetItemUrl(item, urlOptions);

     HttpResponse Response = HttpContext.Current.Response;
     Response.StatusCode = 301;
     Response.StatusDescription = "Moved Permanently";
     Response.RedirectLocation = itemUrl;
     Response.Flush();
    }
   }
   catch (Exception e)
   {
    Log.Error(e.ToString(), "");
   }
        }
    }
}

Then we simply place this resolver after the default Sitecore one, which means that it will only be called if Sitecore could not find the item.
<processor type="Sitecore.Pipelines.HttpRequest.FileResolver, Sitecore.Kernel"/>
<processor type="MyProject.ItemResolvers.MyItemResolver, MyProject"/>

The only hard part is creating business logic to resolve what the item should be based on the invalid URL. Once this is done and you have found the new item, you get a legitimate 301 redirect to it.

Monday, May 30, 2016

IHttpHandler response redirect shows Object moved to here

While working on a HTTP Handler for PDF files, I found that redirecting to a new URL would display:
Object moved to here.
 This was using the following code:

context.Response.Redirect(pdfUrl, false);
context.Response.StatusCode = 301;
context.Response.End();

This could have been related to the redirect begin inside a try catch block, so the following code works better by flushing a redirect header.

HttpResponse Response = HttpContext.Current.Response;
Response.StatusCode = 301;
Response.StatusDescription = "Moved Permanently";
Response.RedirectLocation = pdfUrl;
Response.Flush();

This then worked as expected.

Thursday, May 19, 2016

Sitecore snippets are great for reusable HTML blocks

During a migration from an older system to Sitecore, I ran into an issue where there were large chunks of HTML being used to render content. These chunks needed to be reusable and inserted by content editors as needed. The solution was using snippets in the rich text editor:

Sitecore rich text editor - insert snippet

These snippets are defined under rich text editor profiles - which are found in the core database under: /sitecore/system/Settings/Html Editor Profiles.

An example snippet is available under the rich text full profile (/sitecore/system/Settings/Html Editor Profiles/Rich Text Full). If you look under this item, there will be a folder called snippets, and in there a sample snippet.

Location of Sitecore sample snippet

This item is of the template HTML Editor Snippet (/sitecore/templates/System/Html Editor Profiles/Html Editor Snippet) and contains 2 fields:

  • Header: What displays in the rich text editor
  • Value: The HTML block to be inserted
Sample Sitecore snippet
An example with HTML is:
Sitecore snippet with HTML
My previous post on rich text editor profiles explains how these profiles can be set by default on al rich text fields or on individual instances of a field. In one multi site instance of Sitecore, I set a different profile on the base content item for each site - this allowed them to have their own custom snippets.

Sitecore setting the rich text editor profile

The rich text editor is Sitecore is completely configurable, meaning that you can control down to each button what does and does not appear. Sitecore by default has several profiles setup for use:

  • Rich text default
  • Rich text full
  • Rich text medium
  • Rich txt IDE
The profiles are administered in the core database under: /sitecore/system/Settings/Html Editor Profiles.


By default you can set a default profile to use for an entire Sitecore instance, via the HtmlEditor.DefaultProfile setting. The following patch file will allow you to set the default to Rich Text Medium.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <settings>
      <setting name="HtmlEditor.DefaultProfile">
        <patch:attribute name="value">/sitecore/system/Settings/Html Editor Profiles/Rich Text Medium</patch:attribute>
      </setting>
    </settings>
  </sitecore>
</configuration>

You can also set the profile on the given instance of a rich text field (and this will overwrite the default setting from above). Simply set the source field of a rich text field (on an item template) to the path of the rich text field you wish to use:


You can also duplicate and modify the default rich text profiles to suit your needs (perhaps you need a hybrid of the rich text full and rich text medium). It's as simple as adding/deleting items in the tree under the given profile (found in the core database).

Tuesday, May 17, 2016

Sitecore GetItemUrl method is leaving off the http or https

I had a Lucene computed index field which was indexing item URLs on a multi site environment. The URLs being returned by LinkManager.GetItemUrl were coming back in an unexpected format: ://myUrl instead of http://myUrl. This was because the scheme element was missing off of the site definiton:
<site hostName="MySite" name="mysite" scheme="http" ... />
Aka the scheme element needs to be present otherwise the link manager will not include the protocol.

Incorrect date format in the content editor for date fields in Sitecore

An issue that comes up with non-US Sitecore implementations of Sitecore is the default format of the dates in the Sitecore content editor. By default they will appear as MM/dd/yyyy where we would likely want them in the dd-MM-yyyy format.

US date format
This actually comes back to the backend language/localization of the Sitecore web site - which by default is set to EN (US language). To get the correct format showing, you will need to edit the DefaultRegionalIsoCode setting in Sitecore configuration to en-GB or another supported language.

GB date format
A patch file to change this setting would be:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <settings>
      <setting name="DefaultRegionalIsoCode">
        <patch:attribute name="value">en-GB</patch:attribute>
      </setting>
    </settings>
  </sitecore>
</configuration>

Monday, May 16, 2016

Sitecore preview and experience editor error

When using the preview mode or experience editor, the following error may occur:
The Controls collection cannot be modified because the control contains code blocks (i.e. <% ... %>).
This is because the page contains code rendering blocks (the <%= tag) which should always be surrounded by a runat="server" control. This is stated in Sitecore documentation as:
When necessary, use code rendering blocks only within server controls. For example, the following code contains a code rendering block (the content between <%= and %>):
Therefore a solution would be to use a placeholder for example:

<asp:PlaceHolder runat="server">
    <%=TextyText%>
</asp:PlaceHolder>

Tuesday, May 10, 2016

Accessing items from a Sitecore RSS feed in code

Creating RSS feeds of your Sitecore content is a great way to expose content to web site users, however it's also another way which you can access the data in code. Take news content for example, instead of accessing Sitecore items to display a news feed on a home page, the RSS feed could be used as the datasource.

This is done using System.ServiceModel.Syndication and allows you to specify a Sitecore feed URL and then access the items contained within. The code for that is relatively simple:

XmlReader reader = XmlReader.Create("http://test/feed");
SyndicationFeed sFeed = SyndicationFeed.Load(reader);
reader.Close();

foreach (SyndicationItem item in sFeed.Items)
{
    var title = item.Title.Text;
    var summary = item.Summary.Text;
    var link = item.Links.FirstOrDefault();
}

It's worth noting that the URL to the feed needs to include the server URL and not be relative.

This is also a great way to expose Sitecore content to third party systems (perhaps a SharePoint web part which displays the latest news on a web site for example). You can also get creative by having web forms for marketers create items in a location (that has an RSS feed) and then have a third party system read that feed...

Getting Sitecore links with the server URL

I was working on a sublayout that needed to use an internal RSS feed as a datasource. The code which parsed the RSS feed for items however did not like using relative URLs. The solution here was to query Sitecore for the link with options to include the server URL.

var urlOptions = new UrlOptions { AlwaysIncludeServerUrl = true };
Sitecore.Links.LinkManager.GetItemUrl(MyItem, urlOptions)

This can also be done for media items:

var urlOptions = new MediaUrlOptions { AlwaysIncludeServerUrl = true };
Sitecore.Resources.Media.MediaManager.GetMediaUrl(MyMediaItem, urlOptions)

Most of us are familiar with the classic function available on Stack Overflow which takes in a Sitecore linkfield and returns the correct URL for each possible type of link (internal, media, external and so on). This can also be modified to output the URLs with server URL if required:

public static string GetLinkFieldUrl(LinkField lf, bool includeServerUrl = false)
{
    switch (lf.LinkType.ToLower())
    {
        case "internal":
            // Use LinkMananger for internal links, if link is not empty
            if (includeServerUrl)
            {
                var urlOptions = new UrlOptions { AlwaysIncludeServerUrl = true };
                return lf.TargetItem != null ? Sitecore.Links.LinkManager.GetItemUrl(lf.TargetItem, urlOptions) : string.Empty;
            }
            else
            {
                return lf.TargetItem != null ? Sitecore.Links.LinkManager.GetItemUrl(lf.TargetItem) : string.Empty;
            } 
        case "media":
            // Use MediaManager for media links, if link is not empty
            if (includeServerUrl)
            {
                var urlOptions = new MediaUrlOptions { AlwaysIncludeServerUrl = true };
                return lf.TargetItem != null ? Sitecore.Resources.Media.MediaManager.GetMediaUrl(lf.TargetItem, urlOptions) : string.Empty;
            }
            else
            {
                return lf.TargetItem != null ? Sitecore.Resources.Media.MediaManager.GetMediaUrl(lf.TargetItem) : string.Empty;
            }
                    
        case "external":
            // Just return external links
            return lf.Url;
        case "anchor":
            // Prefix anchor link with # if link if not empty
            return !string.IsNullOrEmpty(lf.Anchor) ? "#" + lf.Anchor : string.Empty;
        case "mailto":
            // Just return mailto link
            return lf.Url;
        case "javascript":
            // Just return javascript
            return lf.Url;
        default:
            // Just please the compiler, this
            // condition will never be met
            return lf.Url;
    }
}

Monday, May 9, 2016

Sitecore search and the treelist field

When Lucene indexes a treelist field in Sitecore, the IDs of the selected items are stored in the search index, separated by the pipe ("|") character. An example would be:
320f408380b246cdb394fdf350f063b7|bc7c2c78e5b14959a7304e6e172159a8|cc9e97d12fe54d38aa60f2bb8ba5dc7d|bd5a6922f41746e89b93f9299e458221
You might also notice that the IDs are not stored in the expected GUID format and instead are in a normalized form. To convert a standard Guid ID to this form:
var myNormalizedGuid = Sitecore.ContentSearch.Utilities.IdHelper.NormalizeGuid(MyGuid);
This means that the treelist field can be queried by searching if the field contains a normalized GUID:
var searchResults = searchContext.GetQueryable<MyResultItem>().Where(x => x.Categories.Contains(myNormalizedGuid));

Wednesday, May 4, 2016

Sitecore treelist dynamic/relative datasource path

I was setting up a re-usable blog branch in Sitecore that contained the following structure:

  • Blog Home
    • 2016
      • 1
      • 2
        • Blog post
    • Categories
      • Category A
      • Category B
The blog post template had a treelist field which needed to have it's datasource connected to the categories element. Being a branch this needed to be dynamic to the branch itself, rather than hard coded to a specific path. This was achieved by setting the datasource to:
query:ancestor-or-self:: *[@@templateid = '{2D516207-AD30-4B71-9394-9A9E451C26D0}']/*[@@templateid = '{BC593CC9-213E-4466-AFB6-FDB089A449BA}']/*[@@templateid = '{8603348F-A6D4-45A4-9007-068E2E1C426B}']
Where:
  • {2D516207-AD30-4B71-9394-9A9E451C26D1} = Blog Home template
  • {BC593CC9-213E-4466-AFB6-FDB089A449BB} = Categories template
  • {8603348F-A6D4-45A4-9007-068E2E1C426B} = Category template
The end result was a tree list where the blog categories (category A and B) where able to be selected.