Contexts

One of the most remarkable aspects of the Sitecore system is that it is “written on itself”. Sitecore uses its own databases, pipelines, event handlers, and settings to drive not only the customer’s site, but the Sitecore interfaces themselves, including the Launchpad, Desktop, Content Editor, and Experience Editor.

However… This lack of a firewall between content and system can cause problems, particularly for developers that mean well, but are not aware of the side effects of adding customizations to Sitecore’s many pipelines. A clever URL rewriter in the HttpRequestBegin pipeline can quickly render Sitecore’s editing interfaces unusable, or worse, corrupt inbound data.

The Constellation.Foundation.Contexts library is designed to fix this glaring problem by allowing developers to specify (normally via the configuration files) exactly under which conditions their code should run.

Pipeline Processor Example

Consider this example from Verndale, a clever bit of code that ensures that the requested URL is in a language supported by the target site.

Before

<pipelines>
  <httpRequestBegin>
  <!-- 
  DEFAULT SITE LANGUAGE RESOLVER			
  Custom pipeline processor for boundary language conditions.
						 
  1. If the URL contains a language that is not explicitly supported by the site (SiteInfo.GetSupportedLanguages()), 
  sets the context language to the current site's default language and redirects to a 404 page.
  2. If the URL does not contain a language, changes the language to the current site's default language. (SiteInfo.Language)
							 
  This processor must immediately follow Sitecore's Language Resolver.
  -->
  <processor 
    type="Verndale.Feature.LanguageFallback.Pipelines.HttpRequest.DefaultSiteLanguageResolver, Verndale.Feature.LanguageFallback"
    patch:after="*[@type='Sitecore.Pipelines.HttpRequest.LanguageResolver, Sitecore.Kernel']"
    />
</httpRequestBegin>
</pipelines>

What happens if someone loads the Content Editor and changes the editing language from “en” to “da”.

A new request is sent, and the new URL forces a change to the sc_lang querystring parameter, which is recognized by the “stock” LanguageResolver pipeline processor.

Verndale’s processor then runs, but it needs a specialized setting attribute on the <site/> definition to detect allowed languages, and the “shell” website (which is what Sitecore calls the Content Editor’s site) does not have that definition.

Most likely, the result will be a simple Yellow Screen of Death, however, it could just as likely result in the surprised content author seeing a 404 page. In some cases, the result could be much more subtle, like a button in the Sitecore UI failing to respond.

We need to fix this by ensuring that the extra Language Resolver only runs for custom websites, and not against any of the Sitecore system ones. We do that by having the new LanguageResolver derive from 

Constellation.Foundation.Context.Pipelines.ContextSensitiveHttpRequestProcessor

This gives us the ability to add some details to the pipeline configuration which will ensure our processor only runs when we’re expecting it to.

After

<pipelines>
  <httpRequestBegin>
  <!-- 
  DEFAULT SITE LANGUAGE RESOLVER			
  Custom pipeline processor for boundary language conditions.
						 
  1. If the URL contains a language that is not explicitly supported by the site (SiteInfo.GetSupportedLanguages()), 
  sets the context language to the current site's default language and redirects to a 404 page.
  2. If the URL does not contain a language, changes the language to the current site's default language. (SiteInfo.Language)
							 
  This processor must immediately follow Sitecore's Language Resolver. The patch:after below is a "safe bet". Your configuration may need to differ.
  Note that this processor should only run for custom websites and should explicitly not run for Sitecore system sites.
  The current config specifies which sites are ignored. Your configuration may differ.
  -->
  <processor 
    type="Verndale.Feature.LanguageFallback.Pipelines.HttpRequest.DefaultSiteLanguageResolver, Verndale.Feature.LanguageFallback">
    patch:after="*[@type='Sitecore.Pipelines.HttpRequest.LanguageResolver, Sitecore.Kernel']"
  >
    <databasesToIgnore>core,master</databasesToIgnore>
    <sitesToIgnore>website,shell,login,admin,service,modules_shell,modules_website,scheduler,system,publisher</sitesToIgnore>
  </processor>
</httpRequestBegin>
</pipelines>

Now, our handler will only run when the Sitecore.Context.Site is not one of Sitecore’s stock websites, and it will only run if the Sitecore.Context.Database is not “core” or “master”.

Note that in <databasesToIgnore/> above we’ve configured not only the “core” database (which is the database containing information about Sitecore’s editing interfaces) but also the “master” database. While we want aggressive language resolution to run against requests from the general public, content authors using Experience Editor need to be able to put the site into any of the system’s installed languages for editing. Since we’re “in the correct site”, we also need to make sure we’re “in the correct database” for our pipeline processor.

Installation

Constellation.Foundation.Data is managed via NuGet.

In Visual Studio, fire up the Package Manager console and install into any of your Sitecore projects:

PM> Install-Package Constellation.Foundation.Contexts

Source code available on GitHub.

Usage

To enable your HttpRequestBegin pipeline processors to take advantage of the context limiting configuration settings, they need to descend from:

Constellation.Foundation.Context.Pipelines.ContextSensitiveHttpRequestProcessor

If you have an existing processor, you also need to rename your

public override void Process(HttpRequestArgs args)

to

protected override void Execute(HttpRequestArgs args)

The base ContextSensitive HttpRequestProcessor has two absract methods you need to implement:

Execute()

This is the code you want your processor to execute when it is “within context” and should execute. if you’re converting an existing pipeline processor, this is the code in your Process() method.

Defer()

This is the code you want your processor to execute when it is “outside context” and should defer execution.

This method allows you to completely replace a “stock” Sitecore processor when “within context” but default back to “stock” Sitecore behavior when the request is not germane to your processor’s custom code. Simply instantiate the stock Sitecore pipeline processor and call Process(args).

Configuration Options

The following pipeline processor options are available in your configuration file, allowing you to either blacklist or whitelist options, whichever is shorter or more precise.

  1. All attributes are comma-delimited,
  2. All string comparisons are done with “starts with”
  3. All strings are Invariant Culture, Ignore Case.
  4. All strings are “trimmed” so you can use spaces between names: “core, master” is legal.

Options

  • databasesToProcess
  • databasesToIgnore
  • hostnamesToProcess (full hostname, no wildcards)
  • hostnamesToIgnore (full hostname no wildcards)
  • pathsToProcess (path to item in Sitecore)
  • sitesToProcess
  • sitesToIgnore

Extending to Other Pipelines

The ContextSensitiveHttpRequestPipelineProcessor is an example of what’s possible with Constellation, but there are some base classes you need to understand if you’re going to add context validation to other Pipelines.

Core Contract: IContextSensitive

When building any Processor, the processor should implement Constellation.Foundation.Contexts.IContextSensitive. This interface gives you the configuration options defined above, and allows you to pass your Processor through to the second essential component:

Core Component: ContextValidator

The ContextValidator takes an instance of IContextSensitive within its constructor. To evaluate the target, call validator.ContextIsValidForExecution(). This method returns true if the configuration settings match the context settings.

Your Pipeline Processor

When designing your IContextSensitive processor, you need to keep a few things in mind:

  1. Your Processor must create a ContextValidator.
  2. Because IContextSensitive is just a contract, you’re responsible for populating the whitelist and blacklist properties as well as the Context properties that are required for the Validator to do its job.
  3. If your whitelist and blacklist properties are not populated via Sitecore’s configuration factory, make sure they are populated..
  4. Make sure your Context properties are filled out.
  5. Your Processor’s Process() Method must call ContextValidator.ContextIsValidForExecution().
  6. Based upon the ContextValidator’s result, you can choose to execute your custom code, fall back to some default behavior, or exit your processor.
  7. We recommend writing to the Sitecore log whenever your processor is not going to execute, with sufficient information to understand why it did not execute.
  8. To be really thorough, we recommend writing “Debug” status level information to the log whenever your processor is going to execute. It can be very useful to determine why it is executing when you weren’t expecting.
  9. Refer to ContextSensitiveHttpRequestPipelineProcessor as an excellent working example.

Example Pipelines where Constellation.Foundation.Context has proven useful

These are probably the most common pipelines where Sitecore and Implementer code flow through the exact same pipeline, but have different requirements.

  • HttpRequestBegin
  • HttpRequestEnd
  • RenderField
  • mvc.CreateController
  • mvc.RenderRendering

Most Common Whitelist/Blacklist Strategies

Whitelist:

  • DatabasesToProcess: “web”

Blacklist

  • DatabasesToIgnore: “core”
  • SitesToIgnore: “shell,login,admin,service,modules_shell,modules_website,scheduler,system,publisher”

Increasingly it is useful to also add “website” to SitesToIgnore as this particular Site definition has meaning to some Sitecore Experience Editor processes. Besides, you’re not actually using “website” as one of your Sitecore custom sites, are you?

Next Steps

Review the source code available on GitHub.