Wednesday, 24 February 2016

Pipelines - Sitecore development basics

Pipelines are very important mechanism in Sitecore. They organize processed tasks in a very clear, classified and highly customizable way.

Pipelines contain processors, which execute specified actions, one after one. Each processor can abort whole pipeline, so subsequent processors won't be executed.

Processor must contain Process() method that returns void and accepts single argument based on class Sitecore.Pipelines.PipelineArgs or its derivative.

public class CustomPipelineProcessor
{
    public void Process(Sitecore.Pipelines.PipelineArgs args)
    {
        // Your logic
    }
}

To make your processor work, you need to bind it to pipeline in configuration file. Let's look at the <httpRequestBegin> pipeline configuration (Sitecore 8.1):

<httpRequestBegin>
  <processor type="Sitecore.Pipelines.PreprocessRequest.CheckIgnoreFlag, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.EnsureServerUrl, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.StartMeasurements, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.IgnoreList, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.SiteResolver, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.UserResolver, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.DatabaseResolver, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.BeginDiagnostics, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.DeviceResolver, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.LanguageResolver, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.CustomHandlers, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.FilterUrlExtensions, Sitecore.Kernel">
    <param desc="Allowed extensions (comma separated)">aspx</param>
    <param desc="Blocked extensions (comma separated)">*</param>
    <param desc="Blocked extensions that stream files (comma separated)">css,js</param>
    <param desc="Blocked extensions that do not stream files (comma separated)">*</param>
  </processor>
  <processor type="Sitecore.Pipelines.HttpRequest.QueryStringResolver, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.DynamicLinkResolver, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.AliasResolver, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.DefaultResolver, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.FileResolver, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.DeviceSimulatorResolver, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.LayoutResolver, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.HttpRequest.ExecuteRequest, Sitecore.Kernel" />
</httpRequestBegin>

As you can see, there are a list of processors in pipeline. Names are meaningful, there are processors which resolve current site, user, database, etc.

So when we want to put our processor to pipeline, we create configuration patch file:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <httpRequestBegin>
        <processor type="SitecoreExperiments.Common.Pipelines.CustomPipelineProcessor, SitecoreExperiments.Common" 
                   patch:after="*[@type='Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel']" />
      </httpRequestBegin>
    </pipelines>
  </sitecore>
</configuration>

In this example patch code, new processor is added to httpRequestBegin pipeline. It is added just after ItemResolver processor, beacuse I set it by this attribute:

patch:after="*[@type='Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel']"
Setting this is not necessary, I just wanted to show that it can be achieved.

I made sure that my config patch is loaded last, so I named it SitecoreExperiments.Common.Pipelines.config and I put it in directory named Z_SitecoreExperiments in \Website\App_Config\Include. After checking configuration at /sitecore/admin/showconfig.aspx I am sure that my patch works as expected:


Object that is passed to function can be modified by each processor, so subsequent processors can rely on data that is added or resolved previously. We can create our own class that inherits from Sitecore.Pipelines.PipelineArgs and adds custom fields or, for example, use CustomData SafeDictionary property:

public void Process(Sitecore.Pipelines.PipelineArgs args)
{
    args.CustomData.Add("CustomPipelineProcessor""Hello, is it pipeline I've been looking for?");
}

Pipelines can be run from code using method CorePipeline.Run()

var args = new Sitecore.Pipelines.PipelineArgs();
Sitecore.Pipelines.CorePipeline.Run("customPipeline", args);
 
// - result will be "Hello, is it pipeline I've been looking for?"
String result = args.CustomData["CustomPipelineProcessor"].ToString();

Pipeline Profiler

There is also useful tool to diagnose pipelines that run in our solution. It is disabled by default, to enable it rename configuration include file Sitecore.PipelineProfiling.config.disabled to Sitecore.PipelineProfiling.config. This config has two settings:

<setting name="Pipelines.Profiling.Enabled" set:value="true" />
This enables whole profiling.

<setting name="Pipelines.Profiling.MeasureCpuTime" set:value="true" />
This enables CPU time measurement. By default this is disabled.

 It can be accessed by url /sitecore/admin/pipelines.aspx. 




Sunday, 21 February 2016

Changing default page title for Sitecore Desktop, Launchpad and Content Editor

My client really wanted to change default page title for Sitecore Desktop view. I have found that I can change it for Launchpad - by Item /sitecore/client/Applications/Launchpad and field "BrowserTitle" in Core database:



But to change default Desktop name in page title in Sitecore 8 it is more tricky. Using only Sitecore, without changing application code, you can make it by changing /sitecore/system/Dictionary/D/Desktop on Core database. It will change browser title of website/sitecore/shell/default.aspx. I have tested that, and it works but for me it is very strange solution - it changes the proper translation of "Desktop" word!

As an alternative you can change the Title attribute of the FormPage node in the following file:
\sitecore\shell\Applications\Shell.xml - hightlighted as red:

<?xml version="1.0" encoding="utf-8" ?> 
<control xmlns:def="Definition" xmlns="http://schemas.sitecore.net/Visual-Studio-Intellisense">
  <Shell>
    <Favicon Src="/sitecore/images/favicon.ico" />
    <Stylesheet Src="Shell.css" DeviceDependant="true"/>
    <Stylesheet Src="Startbar.css" DeviceDependant="true"/>
    <Script type="text/JavaScript" src="/sitecore/shell/controls/SitecoreObjects.js" />
    <Script type="text/JavaScript" src="/sitecore/shell/controls/SitecoreWindowManager.js" />
    <Script type="text/JavaScript" src="/sitecore/shell/controls/lib/scriptaculous/scriptaculous.js?load=effects" />
 
    <Script type="text/JavaScript" language="javascript" key="TrackModified">
      scSitecore.prototype.setModified = function(value) {
        this.modified = false;
      }
    </Script>
 
    <FormPage Submittable="false" TrackModified="false" ForceGlobalHeader="true" Title="My Custom Desktop Title">
      <CodeBeside Type="Sitecore.Shell.Applications.ShellForm,Sitecore.Client" FrameName="Shell"/>
      <img ID="Wallpaper" src="" alt=""/>
      
      <Border KeyDown="ShowStartMenu" KeyFilter="c91">
        <Border Class="DesktopArea" ID="Desktop" ContextMenu="ShowContextMenu">
          <Border ID="Links" Click="ClosePopups" DblClick="Launch"/>
        </Border>
        <div class="scStartbar">
          <Startbar />
        </div>
      </Border>
    </FormPage>
  </Shell>
</control>

For Content Editor application you can do similar, in the file \sitecore\shell\Applications\Content Manager.aspx change highlighted string:
<title><%= Translate.Text(WebUtil.GetQueryString("he""My Website Content Editor")) %></title>
I hope that in next versions it will be more simple to customize, just like Launchpad, because some clients are very alerted to such details as title in the browser :)

Wednesday, 17 February 2016

Sitecore Module - Quick Package

Creating packages in Sitecore is quite simple, but sometimes you need to quickly create simple package with one item. For this purpose I have created Sitecore Marketplace package with my new module Quick Package.

This module provides rapid package creation, with just few clicks. It doesn't replace Sitecore Package Designer just lets simple actions be done faster. Module adds context menu item for all Sitecore items:

There is also new button on Developer tab:

By clicking on these elements you will see a popup, where you can set name of created package. By default it is item name which is currently selected and current date and time. You can also uncheck 'Include descendants' checkbox if you want only selected item in package - not whole tree beneath.


I have published it on Sitecore Marketplace: https://marketplace.sitecore.net/en/Modules/Q/Quick_Package.aspx
Full source code is available on github: https://github.com/ReoKzK/Sitecore.SharedSource.QuickPackage


Wednesday, 27 January 2016

Sitecore 8.1 - Creating custom search index

Sitecore provides availability to configure and use custom search indexes. Starting from version 8.1 they are slightly different in configuration. In this article I will provide you with some advices to create your own Sitecore search index.

Differences from Sitecore 8.0

Let's take a quick look at the differences from version 8.0. When you open Sitecore.ContentSearch.Lucene.DefaultIndexConfiguration.config or Sitecore.ContentSearch.Solr.DefaultIndexConfiguration.config, you can see that sections where we define included and excluded templates, fields etc. are now wrapped with documentOptions tag:

<documentOptions type="Sitecore.ContentSearch.LuceneProvider.LuceneDocumentBuilderOptions, Sitecore.ContentSearch.LuceneProvider">
Also, names of the methods that includes/excludes specified elements now are different, for example,

<exclude hint="list:ExcludeTemplate">

is now replaced with

<exclude hint="list:AddExcludedTemplate">

That are the things I've conquered while upgrading from older Sitecore versions. Here's the list of changed names:

8.0 and before 8.1 and after
IncludeTemplate AddIncludedTemplate
ExcludeTemplate AddExcludedTemplate
IncludeField AddIncludedField
ExcludeField AddExcludedField
RemoveSpecialFields AddExcludedSpecialField

Creating custom search index

To define our own custom Sitecore custom index we will have to create config patch.

Here is version for Lucene

Here is version for Solr


Let's take a closer look now.

Extending default index configurations

Sitecore comes with default index configurations we can use to build our own index. There are versions for Lucene and Solr as well. To use it we are declaring ref attribute on <configuration> element:

<configuration ref="contentSearch/indexConfigurations/defaultLuceneIndexConfiguration">

<configuration ref="contentSearch/indexConfigurations/defaultSolrIndexConfiguration">

Indexing fields

With this setting:
<indexAllFields>true</indexAllFields> 
we declare that all fields will be indexed. This is by the way default setting in default Lucene index configuration.

We can set it to index only specific fields - indexAllFields should be set to false
<indexAllFields>false</indexAllFields> 
And then we should set our fields explicitly
<include hint="list:AddIncludedField">
  <Title>{96970EAB-F8C6-4DE2-A124-CDAC2DFD0C90}</Title> <!-- Title -->
  <Content>{EC539DFA-07C1-49A4-A094-87EACF5ED9BB}</Content> <!-- Content -->
</include>

Our index will also contain computed fields that come from default lucene index configuration. If we don't want them, then we should base our index on edited lucene index.

Including templates

The most important setting is where we define included templates. Only these templates will be indexed:
<include hint="list:AddIncludedTemplate">
    <Article>{C2198DA4-C89F-45D9-AF1A-E24C97F83398}</Article>
    <News>{24EBFFC0-27F0-4F18-A425-74D62F21609E}</News>
    <About>{804B99A2-B18E-4B86-A862-4D0270D4DEAB}</About>
</include>

I have included three example templates, they are declared with:
<templateName>Template ID</templateName>

It is also important to set up root path for the crawler:
<crawler type="Sitecore.ContentSearch.SitecoreItemCrawler, Sitecore.ContentSearch">
  <Database>web</Database>
  <Root>/sitecore/content/home</Root>
</crawler>

As we have complete index configuration lets save it in /App_Config/Include/OUR_CUSTOM_CONFIGS_FOLDER folder with file name format Sitecore.ContentSearch.Lucene.Index.OUR_INDEX_NAME.config. After that let's rebuild our new index.

Previewing indexes

After our index is built we can preview it. For Lucene indexes we can preview it with external tool named Luke (https://code.google.com/archive/p/luke/). It opens folder with index, usually located at Data/indexes folder:

Luke

For Solr indexes we basically open Solr Admin panel, by default set up on port 8983. We can access it by going to url http://localhost:8983/solr/

Solr admin panel


Querying index

As we have index built, we can run queries to get desired elements from index. Sitecore provides ContentSearch library and it is the proper way of searching index in Sitecore. It's completely independent from the implementation so switching index provider won't require code changes (in most cases of course ;)).

Here's a sample code that reads our custom index and searches for specified phrase:

var index = ContentSearchManager.GetIndex("SitecoreExperiments_index");
string phrase = "search phrase";
 
using (var context = index.CreateSearchContext())
{
    var searchQuery = context.GetQueryable<SearchResultItem>()
        .Where(x => x.Language.Equals(Sitecore.Context.Language.Name))
        .Where(x => x.Content.Contains(phrase));
 
    var resultItems = searchQuery.ToList();
    var totalCount = searchQuery.Count();
}

Sample project that provides custom search index and search service is available on my Helix project at GitHub - https://github.com/ReoKzK/SitecoreCoffee/tree/master/src/Foundation/Search/code

Sunday, 29 November 2015

Keeping your Sitecore configuration files clean

Keeping your Sitecore configuration files clean is very important thing for further upgrades and maintenance. I had a lot experience with projects where configuration files was edited and maintained poorly. This article will provide you with some points you should observe to keep your Sitecore configuration clean and transparent. 

1)      You should definitely avoid editing Web.config file. Rather than that you should use Sitecore patch files mechanism to change Sitecore configuration. In this mechanism, Sitecore processes all .config files in App_Config\Include directory and applies patches to Web.config file.
E.g when you want to replace pipeline processor with your custom one, you could simply edit Web.config. You could delete or comment line with processor you want to replace and write new one. This is the fastest way but the worst. The best way is to create custom patch file:

<!--
 
  Events configuration
 
-->
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <events>
 
      <event name="item:creating">
        <handler type="SitecoreExperiments.Common.Events.PreventDuplicates, SitecoreExperiments.Common" method="OnItemCreating" />
      </event>
 
      <event name="item:saved">
        <handler type="SitecoreExperiments.Common.Handlers.CustomSaveHandler, SitecoreExperiments.Common" method="OnItemSaved" />
      </event>
 
    </events>
  </sitecore>
</configuration>

For more informations about configuration patch files see John West post: http://www.sitecore.net/learn/blogs/technical-blogs/john-west-sitecore-blog/posts/2011/05/all-about-web-config-include-files-with-the-sitecore-aspnet-cms.aspx

You can check how patches applied to Web.config file using special url /sitecore/admin/showconfig.aspx.

Unfortunately Sitecore patches concern only /sitecore branch in Web.config file.

2)      Group Sitecore patches into files that matches specified type of Sitecore region e.g pipelines, scheduling tasks, events. Also make sure that your patches are loaded last – put them in your folder in App_Config\Include and name it with prefix ‘Z_’.

3)      Make developer-specified patches ignored in code repository. Let developers use commited example file (with name e.g FileName.config.example) to create their custom one.

4)      Don’t make Web.config file ignored in code repository. Web.config should be commitable.

5)     Make sure that all Sitecore application configs are in solution. Leaving some of them just in webroot after some installations, leads to misconfiguration and nasty bugs.