Sitecore Advanced Configuration in .config files

Sitecore allows you to create configuration sections in /configuration/sitecore/ that consists of hierarchies of configurations including lists, and it will even help you serialize the configuration to object instances.

Observe the following real life example of a configuration I created to configure lists of IP adresses and IP ranges to ignore in my web application:

<pt_restrictions>
  <restriction type="Restrictions.Model.Configuration, Restrictions">
    <ipraddresses hint="raw:AddIpAddresses" >
<address ip="127.0.0.1" />
<address ip="127.0.0.2" />
    </ipraddresses>
    <ipranges hint="raw:AddIpRanges">
      <range from="192.168.0.1" to="192.168.0.255"/>
      <range from="192.169.0.1" to="192.169.0.255"/>
    </ipranges>
  </restriction>
</pt_restrictions>

The configuration section is added to a Sitecore .config file under /configuration/sitecore/.

Please notice the “type” property “Restrictions.Model.Configuration, Restrictions” and the 2 “hint” properties. These allows Sitecore to perform some reflection magic that will map the configuration section to classes.

So let’s code. First I will create an interface that outlines my configuration, a list of Ip Addresses and a list of Ip Ranges

namespace Restrictions.Model
{
  public interface IConfiguration
  {
    IEnumerable<IpAddress> IpAddresses { get; }
    IEnumerable<IpRange> IpRanges { get; }
  }
}

I need implementations of IpAddress and IpRange. The implementations contains the XML attributes as properties:

namespace Restrictions.Model
{
  public class IpAddress
  {
    public string Ip { get; set; }
  }

  public class IpRange
  {
    public string From { get; set; }
    public string To { get; set; }
  }
}

For the final touch,  I will assemble it all in a Configuration class. And here is where the magic lies. Remember the “hint=”raw:AddIpAddresses” and “hint=”raw.AddIpRanges”” attributes? These are method names in my class. Sitecore calls these for each Xml node:

using System.Collections.Generic;
using System.Xml;

namespace Restrictions.Model
{
  public class Configuration : IConfiguration
  {
    private readonly List<IpAddress> _ipAddresses = new List<IpAddress>();
    public IEnumerable<IpAddress> IpAddresses { get { return _ipAddresses; } }

    private readonly List<IpRange> _ipRanges = new List<IpRange>();
    public IEnumerable<IpRange> IpRanges  { get { return _ipRanges; } }

    protected void AddIpAddresses(XmlNode node)
    {
      if (node == null)
        return;
      if (node.Attributes == null)
        return;
      if (node.Attributes["ip"] == null)
        return;
      if (node.Attributes["ip"].Value == null)
        return;

      _ipAddresses.Add(new IpAddress() { Ip = node.Attributes["ip"].Value });
    }

    protected void AddIpRanges(XmlNode node)
    {
      if (node == null)
        return;
      if (node.Attributes == null)
        return;
      if (node.Attributes["from"] == null)
        return;
      if (node.Attributes["from"].Value == null)
        return;
      if (node.Attributes["to"] == null)
        return;
      if (node.Attributes["to"].Value == null)
        return;

      _ipRanges.Add(new IpRange() { From = node.Attributes["from"].Value, To = node.Attributes["to"].Value });
    }
  }
}

The final class is the repository that will use reflection magic to convert an XmlNode to a class implementation:

using System.Xml;

namespace Restrictions.Model.Repositories
{
  public class ConfigurationRepository
  {
    public IConfiguration GetConfiguration(string restrictionName)
    {
      XmlNode xmlNode = Sitecore.Configuration.Factory.GetConfigNode("pt_restrictions/restriction");
      return Sitecore.Configuration.Factory.CreateObject<IConfiguration>(xmlNode);
    }
  }
}

I use the class like this:

ConfigurationRepository rep = new ConfigurationRepository();
IConfiguration config = rep.GetConfiguration();

foreach (var ipaddress in config.IpAddresses)
  // do stuff;
  
foreach (var iprange in config.IpRanges)
  // do stuff

It looks like a big job (and it probably is) but once you get it running, it is easy to extend and use elsewhere.

MORE TO READ:

 

Posted in .net, c#, General .NET, Sitecore, Sitecore 5, Sitecore 6, Sitecore 7, Sitecore 8 | Tagged , , , , | Leave a comment

Sitecore PostSessionEndPipeline failed – MongoDB.Driver.MongoQueryException

In my (huge) Sitecore solution we experienced the following error:

ERROR PostSessionEndPipeline failed.
Exception: MongoDB.Driver.MongoQueryException
Message: QueryFailure flag was Runner error: Overflow sort stage buffered data usage of 33570904 bytes exceeds internal limit of 33554432 bytes (response was { “$err” : “Runner error: Overflow sort stage buffered data usage of 33570904 bytes exceeds internal limit of 33554432 bytes”, “code” : 17144 }).
Source: MongoDB.Driver
at Sitecore.Analytics.Pipelines.CommitSession.SubmitSession.Process(CommitSessionPipelineArgs args)
at (Object , Object[] )
at Sitecore.Pipelines.CorePipeline.Run(PipelineArgs args)
at Sitecore.Analytics.Pipelines.PostSessionEnd.CommitSession.Process(PostSessionEndArgs args)
at (Object , Object[] )
at Sitecore.Pipelines.CorePipeline.Run(PipelineArgs args)
at Sitecore.Web.Application.RaiseSessionEndEvent(HttpApplication context)

Followed by the following message:

QueryFailure flag was Runner error: Overflow sort stage buffered data usage of xxxxxxxx bytes exceeds internal limit of 33554432 bytes (response was { “$err” : “Runner error: Overflow sort stage buffered data usage of xxxxxxxx bytes exceeds internal limit of 33554432 bytes”, “code” : 17144 }).

The error is caused by an internal 32MB memory size restriction in MongoDB.

The problem can be solved by adding an index to the Interactions collection. This will allow Sitecore to stream interactions sorted rather than attempting to load them all into memory and do the sorting in memory.

Connect to the analytics index using RoboMongo or MongoVUE and run the following query:

db.Interactions.createIndex(
{
    "StartDateTime" : 1,
    "_id" : 1
}
)

A WORD OF CAUTION:

According to MongoDB, adding indexes to a live environment does not have any side effects. But running the above query on my 7.5 million entries in the Interactions collection caused my entire Sitecore to fail (total catastrophic failure) for 5-10 minutes until the index was built.

Thank you to Sitecore Support and Joshua Wheelock for helping with the issue.

MORE TO READ:

Posted in Sitecore 8 | Tagged , , | Leave a comment

Sitecore get host name from a different context

Ohno, you are running in one context, say the “shell” context, and all of your URL’s have to point to the “website” context. The hustler will obviously hard-code the domain name of the “website” into the code. But the Sitecore aficionado will check the SiteContext and retrieve the “TargetHostName” property.

This tiny class will qualify your relative URL’s to absolute URL’s, matching the domain name as specified in the <sites> section of your configuration (code simplified for readability, you should apply your own exception handling):

using System;
using System.Web;
using Sitecore.Sites;

namespace MyNameSpace
{
  internal static class FullyQualifiedUrlService
  {
    public static string Qualify(string relativeUrl, string sitename)
    {
      SiteContext sitecontext = SiteContext.GetSite(sitename);
      return Qualify(relativeUrl, sitecontext);
    }

    public static string Qualify(string relativeUrl, SiteContext sitecontext)
    {
      if (!relativeUrl.StartsWith("/"))
        relativeUrl = relativeUrl + "/";
      return string.Format("{0}://{1}{2}", sitecontext.SiteInfo.Scheme, sitecontext.TargetHostName, relativeUrl);
    }
  }
}

USAGE:

Imagine this is your sites definition for the “website” context:

<sites>
  ...
  <site name="website" targetHostName="www.mysite.com" hostName="mysite.com|www.mysite.com" scheme="http" ... .../>
  ...
</sites>

The class is called using the following code:

string relativeUrl = "/this/is/my/page";
string absoluteUrl = FullyQualifiedUrlService.Qualify(relativeUrl, "website");

And will return the following result:

http://www.mysite.com/this/is/my/page

Both the scheme and the targetHostName is resolved from the context using the Scheme and TargetHostName properties of the SiteContext.

Sitecore uses 2 properties for host resolving:

  • The “hostName” can be a pipe-separated list of domains and is used to target the number of possible URL’s that points to this context.
  • The “targetHostName” is one URL which is used internally to resolve and fully qualify your URL.

MORE TO READ:

 

Posted in c#, General .NET, Sitecore 6, Sitecore 7, Sitecore 8 | Tagged , , , | Leave a comment

Sitecore EXM keeps reloading

Have you experienced that in Sitecore, the EXM Email Experience Manager window keeps reloading?

Email Experience Manager

Email Experience Manager

In my solution, the problem was that I have a cookie with a “:” (colon) in the cookie name. According to the old RFC 2616 specification, colons are not allowed in cookie names.

Although this is not a problem in the website or in the Sitecore Shell, it causes problems when this particular SPEAK app calls the server API.

The solution is to avoid using colons in cookie names.

 

Posted in Sitecore 8 | Tagged , , , | Leave a comment

Sitecore SVG files

In Sitecore 8.1, there is a tiny but annoying glitch in the matrix, where SVG files are not allowed in the Media Library.

But do not worry, the fix is easy. Go to your /App_Config/Sitecore.config, find the /sitecore/mediaLibrary/mediaTypes/mediaType section, and add the SVG section yourself:

<mediaType name="SVG" extensions="svg">
  <mimeType>image/svg+xml</mimeType>
  <forceDownload>false</forceDownload>
  <sharedTemplate>system/media/unversioned/image</sharedTemplate>
  <versionedTemplate>system/media/versioned/image</versionedTemplate>
  <mediaValidator type="Sitecore.Resources.Media.ImageValidator"/>
  <thumbnails>
    <generator type="Sitecore.Resources.Media.ImageThumbnailGenerator, Sitecore.Kernel">
      <extension>png</extension>
    </generator>
    <width>150</width>
    <height>150</height>
    <backgroundColor>#FFFFFF</backgroundColor>
  </thumbnails>
</mediaType>

MORE TO READ:

Posted in Sitecore, Sitecore 8 | Tagged , | 1 Comment

Sitecore EXM: Send an email from code

The Sitecore Email Experience Manager is your way to send personalized emails to Sitecore users.   You do not need to send bulk emails, you can easily send single emails with contents like “Here is your new password” or “Your profile has been updated”.

The emails to send are “Standard Messages” so the email you create must be of the type “Triggered message”:

Triggered Message Settings

Triggered Message Settings

There is 2 ways of sending emails: To Sitecore users or Sitecore Contacts.

SEND AN EMAIL TO A SITECORE USER

Your Sitecore user must exist in the Sitecore User repository (usually as an “extranet” user).

using Sitecore.Data;
using Sitecore.Diagnostics;
using Sitecore.Modules.EmailCampaign;
using Sitecore.Modules.EmailCampaign.Messages;
using Sitecore.Modules.EmailCampaign.Recipients;

public void Send(ID messageItemId, string userName)
{
  MessageItem message = Factory.GetMessage(messageItemId);
  Assert.IsNotNull(message, "Could not find message with ID " + messageItemId);
  RecipientId recipient = new SitecoreUserName(userName);
  Assert.IsNotNull(recipient, "Could not find recipient with username " + userName);
  new AsyncSendingManager(message).SendStandardMessage(recipient);
}

You call the function like this:

Send(new ID("{12A6D766-CA92-4303-81D2-57C66F20AB12}"), "extranet\\user@domain.com");

SEND AND EMAIL TO A CONTACT

The contact must (obviously) contain an email address. To create a contact see Sitecore Contacts – Create and save contacts to and from xDB (MongoDB). The code is near identical to the previous, but the Receipient is retrieved by resolving the contact ID:

using Sitecore.Data;
using Sitecore.Diagnostics;
using Sitecore.Modules.EmailCampaign;
using Sitecore.Modules.EmailCampaign.Messages;
using Sitecore.Modules.EmailCampaign.Recipients;

public void Send(ID messageItemId, Guid contactID)
{
  MessageItem message = Factory.GetMessage(messageItemId);
  Assert.IsNotNull(message, "Could not find message with ID " + messageItemId);
  RecipientId recipient = RecipientRepository.GetDefaultInstance().ResolveRecipientId("xdb:" + contactID);
  Assert.IsNotNull(recipient, "Could not find recipient with ID " + contactID);
  new AsyncSendingManager(message).SendStandardMessage(recipient);
}

You call the function like this:

Send(new ID("{12A6D766-CA92-4303-81D2-57C66F20AB12}"), Guid.Parse("c3b8329b-7930-405d-8852-7a88ef4f0cb1"));

MORE TO READ:

 

Posted in c#, General .NET, Sitecore 8 | Tagged , , , , , | Leave a comment

Sitecore 8 EXM: Failed to enroll a contact in the engagement plan

In Sitecore 8.1 update 1, you might experience the following error when trying to send an email:

ERROR Failed to enroll a contact in the engagement plan.
Exception: System.Net.WebException
Message: The remote name could not be resolved: ‘default-cd-cluster’


Message: Recipient sc:extranet\someemail@pentia.dk skipped. Failed to enroll its corresponding contact in the engagement plan.
Source: Sitecore.EmailCampaign
at Sitecore.Modules.EmailCampaign.Core.Dispatch.DispatchTask.OnSendToNextRecipient()

The error occurs if you do not change the Analytics.ClusterName in the /App_Config/Include/Sitecore.Analytics.Tracking.config.

<setting name="Analytics.ClusterName" value="default-cd-cluster" />

The Analytics.ClusterName must be a valid, reachable URL.

<setting name="Analytics.ClusterName" value="hostname.of.my.site.com" />

This is because Sitecore calls the /sitecore/AutomationStates.ashx page using the Analytics.ClusterName as the host name.

Posted in Sitecore 8 | Tagged , , , , | 1 Comment

Sitecore xDB – flush data to MongoDB

When debugging Sitecore xDB issues, it is a pain that data is not written to MongoDB directly, but you have to wait for your session to end.

The legend said that you could either set the session timeout to 1 minute, or call Session.Abandon() to write to MongoDB. None of this have ever worked for me, until my colleague Lasse Pedersen found the final, bullet-proof way of having the xDB session data written to MongoDB.

Create a page with the following contents, and call it when you wish to flush data from xDB to MongoDB:

Sitecore.Analytics.Tracker.Current.EndTracking();
HttpContext.Current.Session.Abandon();

We will all pray to the debugging gods that this fix will work in every scenario.

Posted in c#, General .NET, Sitecore 8 | Tagged , , , | 3 Comments

Why is session NULL in BeginRequest()? (httpRequestBegin)

Have you ever wondered why the session is NULL when a request begins?
The answer is simple: The Application_BeginRequest() event is fired before the Session_Start() event.
The BeginRequest() event is fired for each request as the first event. The Session_Start() event is only fired once per session, after the BeginRequest() event.

The following is specific for Sitecore:

This means that if you add a new processor in the httpRequestBegin pipeline, you will get an exception if you call HttpContext.Current.Session:

namespace MyNameSpace
{
  class MyFunction : HttpRequestProcessor
  {
    public override void Process(HttpRequestArgs args)
    {
	  // THIS WILL FAIL:
	  var somevar = HttpContext.Current.Session;
    }
  }
}

Simply because the session is not available at the time.

So what do you do then?

Sitecore have added another pipeline, httpRequestProcessed, which is fired AFTER httpRequestBegin AND Session_Start():

<httpRequestProcessed>
  <processor type="MyNamespace.MyFunction, MyDLL" />
</httpRequestProcessed>

This pipeline is usually used for replacing content or setting http status codes after the contents of a website have been processed, but you can use it for any purpose you require.

And please note that the pipeline is called for every request that have ended, so you need the same amount of carefulness as when calling httpRequestBegin. Remember to filter out doublets, Sitecore calls, check for Sitecore context etc., before calling your code.

// This is a classic (maybe a little over-the-top) 
// test to see if the request is a request
// that needs processing
namespace MyNamespace
{
  public class MyClass : HttpRequestProcessor
  {
    public override void Process(HttpRequestArgs args)
    {
      Assert.ArgumentNotNull(args, "args");
      if (Context.Site == null)
        return;
      if (Context.Site.Domain != DomainManager.GetDomain("extranet"))
        return;
      if (args.Url.FilePathWithQueryString.ToUpperInvariant().Contains("redirected=true".ToUpperInvariant()))
        return; // already redirected
      if (Context.PageMode.IsPageEditor)  
	    return;

      // DO CODE
    }
  }
}

MORE TO READ:

 

Posted in General .NET, Sitecore 6, Sitecore 7, Sitecore 8 | Tagged , , , , , , | 1 Comment

Sitecore sublayout caching vary by cookie

In Sitecore you can control the caching of your sublayouts in many different ways.

Caching parameters

Caching parameters

Checking the “Cacheable” box allows you you vary the cache by data (data source), device, login (anonymous users have a different cached version from named users), param (item parameters), query string or user (each user has his own version).

Caching is an area that hasn’t changed much since Sitecore 5, which is why this code should work on any Sitecore version. However, For caching in MVC, Sitecore have implemented a different approach. See CUSTOM CACHE CRITERIA WITH MVC IN THE SITECORE ASP.NET CMS by John West for more information.

But what if you wish to vary the cache on another parameter, say, the contents of a cookie?

My colleague, Niclas Awalt, solved this for me a while ago.

STEP 1: SETTING UP SITECORE

First we need to define the name of the cookie to vary by. This is done in the “Parameters” field on the sublayout:

Parameters

The “VaryByCookie” defines my caching key, and the “Mycookie” defines the cookie to vary by.

STEP 2: CREATE A CUSTOM SUBLAYOUT

This custom sublayout contains the code that determines if the sublayout is cacheable, and if the “VaryByCookie” value is set, and if so, creates a new cache key based on the contents of the cookie:

using System.Collections.Specialized;
using System.Text;

namespace MyNamespace
{
  public class CustomSublayout : Sitecore.Web.UI.WebControls.Sublayout
  {
    private string _varyByCookies;
    private string VaryByCookies
    {
      get
      {
        return _varyByCookies ?? (_varyByCookies = GetVaryByCookiesParameter());
      }
    }

    public override string GetCacheKey()
    {
      Sitecore.Sites.SiteContext site = Sitecore.Context.Site;

      if (!Cacheable)
        return base.GetCacheKey();

      if (site != null && (!site.CacheHtml))
        return base.GetCacheKey();

      if (SkipCaching())
        return base.GetCacheKey();

      if (string.IsNullOrEmpty(VaryByCookies))
        return base.GetCacheKey();

      var cacheKey = base.GetCacheKey() + BuildCookieValueString();
      return cacheKey;
    }

    private string BuildCookieValueString()
    {
      StringBuilder stringBuilder = new StringBuilder();
      foreach (var cookieName in VaryByCookies.Split('|'))
      {
        stringBuilder.Append(GetCookieValue(cookieName, "0NoValue0"));
      }
      if (stringBuilder.Length > 0)
        stringBuilder.Insert(0, "_#cookies:");
      return stringBuilder.ToString();
    }

    private string GetCookieValue(string cookieName, string defaultValue)
    {
      var cookieValue = Context.Request.Cookies[cookieName];
      return string.Concat(cookieName, cookieValue == null ? defaultValue : cookieValue.Value);
    }

    private string GetVaryByCookiesParameter()
    {
      NameValueCollection parameters =
        Sitecore.Web.WebUtil.ParseUrlParameters(this.Parameters);

      return parameters["VaryByCookie"] ?? string.Empty;
    }
  }
}

STEP 3: HOOK UP THE NEW SUBLAYOUT:

Next step is to replace the Sitecore Sublayout rendering control with our own.

First, create a small rendering type class:

using System.Collections.Specialized;
using System.Web.UI;

namespace MyNamespace
{
  public class CustomSublayoutRenderingType : Sitecore.Web.UI.SublayoutRenderingType
  {
    public override Control GetControl(NameValueCollection parameters, bool assert)
    {
      CustomSublayout sublayout = new CustomSublayout();

      foreach (string name in parameters.Keys)
      {
        string str = parameters[name];
        Sitecore.Reflection.ReflectionUtil.SetProperty(sublayout, name, str);
      }

      return sublayout;
    }
  }
}

Then, in the web.config, in the <renderingControls> section, call our new sublayout rendering control:

<renderingControls>
  <control template="method rendering" type="Sitecore.Web.UI.WebControls.Method, Sitecore.Kernel" propertyMap="AssemblyName=assembly, ClassName=class, MethodName=method" />
  <!-- OUR NEW RENDERING -->
  <control template="sublayout" type="MyNamespace.CustomSublayoutRenderingType, MyDll" propertyMap="Path=path" />
  <!-- OUR NEW RENDERING -->
  <control template="url rendering" type="Sitecore.Web.UI.WebControls.WebPage, Sitecore.Kernel" propertyMap="Url=url" />
  <control template="xsl rendering" type="Sitecore.Web.UI.XslControlRenderingType, Sitecore.Kernel" propertyMap="Path=path" />
  <control template="webcontrol" type="Sitecore.Web.UI.WebControlRenderingType, Sitecore.Kernel" propertyMap="assembly=assembly, namespace=namespace, class=tag, properties=parameters" />
  <control template="xmlcontrol" type="Sitecore.Web.UI.XmlControlRenderingType, Sitecore.Kernel" propertyMap="controlName=control name, properties=parameters" />
</renderingControls>

Now, each time you create a sublayout, it will automatically be able to use the VaryByCookie parameter, and vary the cache based on the contents of the cookie.

MORE TO READ:

Posted in c#, General .NET, Sitecore, Sitecore 5, Sitecore 6, Sitecore 7, Sitecore 8 | Tagged , , , | 1 Comment