Quantcast
Channel: briancaos – Brian Pedersen's Sitecore and .NET Blog
Viewing all 263 articles
Browse latest View live

Sitecore PostSessionEndPipeline failed – MongoDB.Driver.MongoQueryException

0
0

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:



Sitecore Advanced Configuration in .config files

0
0

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:

 


Sitecore Measure Pipeline performance and profile processors

0
0

In Sitecore, Pipelines are by far my favorite dependency injection pattern, and I have used them since Sitecore 5. One of the secret features of pipelines are the built in profiling. Oh, yes, by flipping 2 switches you can measure the performance of your pipeline processors.

STEP 1: SWITCH ON PIPELINE PROFILING

Enable the \App_Config\Include\Sitecore.PipelineProfiling.config.disabled by removing the .disabled extension.

Or add the following 2 settings to your /App_Config/sitecore.config:

<setting name="Pipelines.Profiling.Enabled" value="true" />
<setting name="Pipelines.Profiling.MeasureCpuTime" value="true" />

The Pipelines.Profiling.MeasureCpuTime is not enabled by default in the Sitecore.PipelineProfiling.config file as it introduces a performance overhead, so only use this setting on your test environment.

STEP 2: FIND THE PROFILE PAGE:

Sitecore comes with a profiling page (it has been there since Sitecore 7 i guess):

http://yourwebsite/sitecore/admin/pipelines.aspx

Pipeline Profiler

Pipeline Profiler

The page displays all pipelines executed since you pressed the refresh button, how many times it has been executed, and the average wall time (wall time = real-world time from processor started to it finished, as opposed to CPU time which is the time spend by the CPU executing the processor) per execution.

MORE TO READ:

 


Sitecore Event Queue – How to clean it – and why

0
0

Yesterday the dreaded Sitecore Event Queue almost killed me again – well it certainly almost killed my CM server. The server went from being busy but stable to being unresponsive. CPU and memory load skyrocketed:

A fully loaded CM server

A fully loaded CM server

Obviously it happened after a system update, so after a panic debug and rollback the system owner pointed out: “Hey, the event queue table is quite big?“.
Of course, the system updated flooded the event with 1.5 million events, and the problem did not go away because I keep 1 day of events in the queue.

SO WHAT TO DO ABOUT IT?

First we need to stabilize the system, then we need to update the configuration.

STEP 1: CLEAN OUT EVENT QUEUE, HISTORY TABLE, PUBLISH QUEUE

The following SQL statement will clean out the history table, publish queue and event queue, leaving only 12 hours of history and publish data and 4 hours of events. Replace YOURDATABASE with the name of your database:

/****** History ******/
delete FROM [YOURDATABASE_Core].[dbo].[History] where Created < DATEADD(HOUR, -12, GETDATE())
delete FROM [YOURDATABASE_Master].[dbo].[History] where Created < DATEADD(HOUR, -12, GETDATE())
delete FROM [YOURDATABASE_Web].[dbo].[History] where Created < DATEADD(HOUR, -12, GETDATE())

/****** Publishqueue ******/
delete FROM [YOURDATABASE_Core].[dbo].[PublishQueue] where Date < DATEADD(HOUR, -12, GETDATE());
delete FROM [YOURDATABASE_Master].[dbo].[PublishQueue] where Date < DATEADD(HOUR, -12, GETDATE());
delete FROM [YOURDATABASE_Web].[dbo].[PublishQueue] where Date < DATEADD(HOUR, -12, GETDATE());

/****** EventQueue ******/
delete FROM [YOURDATABASE_Master].[dbo].[EventQueue] where [Created] < DATEADD(HOUR, -4, GETDATE())
delete FROM [YOURDATABASE_Core].[dbo].[EventQueue] where [Created] < DATEADD(HOUR, -4, GETDATE())
delete FROM [YOURDATABASE_Web].[dbo].[EventQueue] where [Created] < DATEADD(HOUR, -4, GETDATE())

STEP 2: CONFIGURE THE SYSTEM TO CLEAN THE TABLES MORE OFTEN

With the system stabilized, we need to take more care of the table sizes.

HISTORY TABLE:

Sitecore is already configured to clean the tables so they only contain 12 hours of data. 12 hours of data is usually what any SQL server will handle, and you will have up to 10.000 rows in the table.

<Engines.HistoryEngine.Storage>
  <obj type="Sitecore.Data.$(database).$(database)HistoryStorage, Sitecore.Kernel">
    <param connectionStringName="$(id)" />
    <EntryLifeTime>00.12:00:00</EntryLifeTime>
  </obj>
</Engines.HistoryEngine.Storage>

PUBLISH QUEUE:

Sitecore keeps 30 days of publish queue. If you insert and update items often, you should lower this number. For each item change (including any change that the system does) is stored here.

<agent type="Sitecore.Tasks.CleanupPublishQueue, Sitecore.Kernel" method="Run" interval="04:00:00">
  <DaysToKeep>2</DaysToKeep>
</agent>

EVENT QUEUE:

The event queue is the most important table to keep small. In a distributed environment, each server will read the contents of the table every 5 seconds, using a time stamp stored in the Properties table as key. Any row before the time stamp will not be read.

You therefore need enough history to cater that a server will be offline for a while, but at the same time so little contents that any read and write will be amazingly fast.

If you can keep the number of rows below 7.000, most SQL server should be able to handle that amount of data. Even smaller numbers are preferred as well.

Before Sitecore 8.1, Sitecore would only allow you to clean events older that 1 day. This is way too much, especially if you publish often. The new IntervalToKeep will allow you to determine the hours to keep as well:

<agent type="Sitecore.Tasks.CleanupEventQueue, Sitecore.Kernel" method="Run" interval="04:00:00">
  <IntervalToKeep>04:00:00</IntervalToKeep>
  <DaysToKeep>1</DaysToKeep>
</agent>

 

THE EFFECT ON THE CLEANUP

After these changes, my server is back to normal, busy but responsive:

Normal Load

Normal Load

MORE TO READ:

 


Sitecore Publishing – Programmatically determine if item should be published

0
0

Sitecore uses it’s publish settings to determine if an item should be published. But you can only control a publish from versions and date times.

Sitecore Publishing Settings

Sitecore Publishing Settings

So what if you have other values that determine if an item must be published or not? Say, a status field or a checkbox, or a combination of both? And what do you do if these fields are updated from external systems?

One way is to extend the publishItem pipeline. Every item that must be published goes through this pipeline one by one and you can therefore add a new processor that determines if an item is published or not.

AN EXAMPLE:

These are our publish options: A status field and a checkbox is updated by users and external systems to determine if this item is eligible for publishing.

Publish Options

Publish Options

The publishItem pipeline works “the other way round”, meaning that if the item can be published we leave it alone, and if not we change the publish status to “DeleteTargetItem“.

Here is my new processor (some pseudocode, apply your own checks):

using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Globalization;
using Sitecore.Publishing;
using Sitecore.Publishing.Pipelines.PublishItem;

namespace MyCode.Infrastructure.Pipelines.PublishItem
{
  public class DetermineActionForItem : PublishItemProcessor
  {
    public override void Process(PublishItemContext context)
    {
      // First a list of checks to ensure that
      // it is our item and we can make the decision
      // about it's publish status
      if (context == null)
        return;
      if (context.Aborted)
        return;
      Item sourceItem = context.PublishHelper.GetSourceItem(context.ItemId);
      if (sourceItem == null)
        return;
      if (!sourceItem.Paths.IsContentItem)
        return;
      // I will only check our specific item
      if (!sourceItem.TemplateName == "mytemplate")
        return;

      // OK, now we know that this is our item and
      // we can determine it's faith
      // Check every language to see if it is eligible for publishing
      foreach (Language language in sourceItem.Languages)
      {
        Item languageVersion = sourceItem.Versions.GetLatestVersion(language);
        // A little pseudocode, here is the check to see if the item can be published
        if (StatusIsOK(languageVersion["StatusField"]) && CheckBoxIsChecked(languageVersion["CheckBoxField"])
        {
          // Yes, the item can be published
          return;
        }
      }

      // No, the item cannot be published, set the publishaction == DeleteTargetItem
      Log.Info(string.Format("{0}: Unpublishing Item '{1}' from database '{2}' because it is not in the correct state.",
               GetType().Name,
               AuditFormatter.FormatItem(sourceItem),
               context.PublishContext.PublishOptions.TargetDatabase),
               this);
      context.Action = PublishAction.DeleteTargetItem;
    }
  }
}

As you can see, the function returns if the item being processed is not our item, or if the item can be published. If the item is not eligible for publishing, we change the Action to DeleteTargetItem.

The processor is added to the publish pipeline just before the Sitecore “DetermineAction” processor:

<publishItem help="Processors should derive from Sitecore.Publishing.Pipelines.PublishItem.PublishItemProcessor">
  <processor type="Sitecore.Publishing.Pipelines.PublishItem.RaiseProcessingEvent, Sitecore.Kernel"/>
  <processor type="Sitecore.Publishing.Pipelines.PublishItem.CheckVirtualItem, Sitecore.Kernel"/>
  <processor type="Sitecore.Publishing.Pipelines.PublishItem.CheckSecurity, Sitecore.Kernel"/>
  <!-- Our Processor inserted before Sitecore's "DetermineAction" proecssor -->
  <processor type="MyCode.Infrastructure.Pipelines.PublishItem.DetermineActionForItem, MyDll" />
  <!-- ... -->
  <processor type="Sitecore.Publishing.Pipelines.PublishItem.DetermineAction, Sitecore.Kernel"/>
  ...
  ...
  ...
</publishItem>

MORE TO READ:

 


Sitecore Scheduled Tasks – Run on certain server instance

0
0

If you would like certain Sitecore Scheduled Tasks run on a certain server instance (your CM server for example) the approach is pretty simple.

First, create a new folder under /sitecore/system/Tasks. I have a new folder called “CMSchedules“. Place your Scheduled Tasks in this folder.

Scheduled Tasks in separate folder

Scheduled Tasks in separate folder

Then tell Sitecore to execute these tasks. Place the following configuration on the instance you wish should run these tasks. Do not place the configuration on servers that should not run the tasks.

You need to patch the Sitecore.config (for Sitecore 8.1 and up) or web.config (for versions prior to Sitecore 8.1). Add the following to the /sitecore/scheduling section:

<agent type="Sitecore.Tasks.DatabaseAgent" method="Run" interval="00:05:00">
  <param desc="database">master</param>
  <param desc="schedule root">/sitecore/system/Tasks/CMSchedules</param>
  <LogActivity>true</LogActivity>
</agent>

The configuration tells Sitecore to execute all tasks in the /sitecore/system/Tasks/CMSchedules every 5 minutes.

MORE TO READ:

 


Sitecore General error when submitting contact – Another contact with the same identifier already exists

0
0

In Sitecore when creating your own contacts you can get the following exception:

10604 10:16:27 ERROR General error when submitting contact.
Exception: System.InvalidOperationException
Message: Another contact with the same identifier already exists.
Source: Sitecore.Analytics.MongoDB
   at Sitecore.Analytics.Data.DataAccess.MongoDb.MongoDbDataAdapterProvider.SaveContactWithIdentifier(IContact contact, ContactSaveOptions saveOptions)
   at Sitecore.Analytics.Data.DataAccess.MongoDb.MongoDbDataAdapterProvider.<>c__DisplayClass9.<SaveContact>b__7()
   at Sitecore.Analytics.Data.DataAccess.MongoDb.MongoDbDataAdapterProvider.Try(Action action)
   at Sitecore.Analytics.Data.DataAccess.MongoDb.MongoDbDataAdapterProvider.SaveContact(IContact contact, ContactSaveOptions saveOptions)
   at Sitecore.Analytics.Data.ContactRepository.SaveContact(Contact contact, ContactSaveOptions options)
   at Sitecore.Analytics.Tracking.ContactManager.SubmitContact(Contact contact, ContactSaveOptions options, Contact& realContact)

The error can occur if you try to flush a contact with a new contact ID but with an existing identifier. It usually is a result of Sitecore having a mismatch between what the ContactRepository and the MongoDB contains.

This pseudocode explains the situation:

ContactRepository contactRepository = Factory.CreateObject("tracking/contactRepository", true) as ContactRepository;
ContactManager contactManager = Factory.CreateObject("tracking/contactManager", true) as ContactManager;
// Contact is loaded from repository but repository cannot find it
Contact contact = contactRepository.LoadContactReadOnly(username);
// ... so we try to create it
if (contact == null)
{
  // Contact is created in memory
  contact = contactRepository.CreateContact(ID.NewID);
 contact.Identifiers.Identifier = username;
  // And we try to write it to xDB (MongoDB)
  // but the contact is already there.
  contactManager.FlushContactToXdb(contact);
}

The problem arises as the FlushContactToXdb does not throw an exception, so you are left with the impression that the Contact is created and everything is fine.

If you are using the ExtendedContactRepository as I described in the blog post Sitecore Contacts – Create and save contacts to and from xDB (MongoDB), you risk that the GetOrCreateContact method goes in an infinite loop because:

  1. We try to load the contact, but the contact is not found
  2. We Create a new contact
  3. We flush the contact, but the flush method fails silently
  4. We call step 1

To avoid the infinite loop, please see Dan’s suggestion to how to solve it:

https://briancaos.wordpress.com/2015/10/09/sitecore-contacts-create-and-save-contacts-directly-to-and-from-xdb-mongodb/#comment-5890

Or you can add a retry counter in the class.

Add the following 2 lines to the class:

private const int _MAX_RETRIES = 10;
private int _retries;

Then modify the private method GetOrCreateContact:

private Contact GetOrCreateContact(string username, LockAttemptResult<Contact> lockAttempt, ContactRepository contactRepository, ContactManager contactManager)
{
  switch (lockAttempt.Status)
  {
	case LockAttemptStatus.Success:
	  Contact lockedContact = lockAttempt.Object;
	  lockedContact.ContactSaveMode = ContactSaveMode.AlwaysSave;
	  return lockedContact;

	case LockAttemptStatus.NotFound:
	  Contact createdContact = CreateContact(username, contactRepository);
	  Log.Info(string.Format("{0}: Flushing contact '{1}' (contact ID '{2}') to xDB.", GetType().Name, createdContact.Identifiers.Identifier, createdContact.ContactId), this);
	  contactManager.FlushContactToXdb(createdContact);

	  // NEW CODE: Check for retries, and throw an exception if retry count is reached
	  _retries++;
	  if (_retries >= _MAX_RETRIES)
		throw new Exception(string.Format("ExtendedContactRepository: Contact {0} could not be created. ", username));
	  // END NEW CODE

	  return GetOrCreateContact(username);

	default:
	  throw new Exception("ExtendedContactRepository: Contact could not be locked - " + username);
  }
}

This is still experimental code, but as described before, so is the ExtendedContactRepository.

MORE TO READ:

 


Sitecore custom cache that is cleared on publish

0
0

In this article I will demonstrate how to create a custom Sitecore cache and how to ensure that it is cleared when you publish.

First I will create the simplest custom cache available:

namespace MyNamespace
{
  public class MyCustomCache : CustomCache
  {
    public MyCustomCache(string name, long maxSize) : base("MyCustomCache." + name, maxSize)
    {
    }

    public string Get(string cacheKey)
    {
      return GetString(cacheKey);
    }

    public void Set(string cacheKey, string value)
    {
      SetString(cacheKey, value);
    }
  }
}

The cache is instantiated with a name, meaning that you can use the class to create more than one cache, as long as you remember to give each instance its own name.

The cache contains a string as key and returns a string as value. It is used like this (pseudocode):

// Instantiation, set up an 1MB cache
private MyCustomCache myCustomCache = new MyCustomCache("CacheName", StringUtil.ParseSizeString("1MB"));

// Get value from cache
string myValue = myCustomCache.Get(myKey);

// Set value in cache
myCustomCache.Set(myKey, myValue);

To clear the cache on publish, we need to set up an event handler on either publish:end and publish:end:remote or publish:complete and publish:complete:remote.

There is a little confusion as to when in the publish pipeline these events are fired. In previous versions (prior to Sitecore 7.2), publish:end and publish:end:remote was fired for each language and each target, and publish:complete and publish:complete:remote was fired when the publish job was done. But in later versions, publish:end and publish:end:remote is also only fired once.

The :remote events (publish:end:remote and publish:complete:remote) is fired on your remote (i.e. CD servers) by the way.

Enough talk, lets code the cache clearer. First I will set up the 2 new events on publish:end and publish:end:remote:

<events>
  <event name="publish:end">
    <handler type="MyNamespace.MyCacheClearer, Mydll" method="ClearCaches">
      <caches hint="list">
        <cache>MyCustomCache.CacheName</cache>
      </caches>
    </handler>
  </event>
  <event name="publish:end:remote">
    <handler type="MyNamespace.MyCacheClearer, Mydll" method="ClearCaches">
      <caches hint="list">
        <cache>MyCustomCache.CacheName</cache>
      </caches>
    </handler>
  </event>
</events>

The events will now call the method ClearCaches on the Mynamespace.MyCacheClearer class:

namespace MyNamespace
{
  public class MyCacheClearer
  {
    public void ClearCaches(object sender, EventArgs args)
    {
      Assert.ArgumentNotNull(sender, "sender");
      Assert.ArgumentNotNull(args, "args");
      try
      {
        DoClear();
      }
      catch (Exception ex)
      {
        Log.Error(this + ": " + ex, ex, this);
      }
    }

    private void DoClear()
    {
      foreach (string cacheName in Caches)
      {
        Cache cache = CacheManager.FindCacheByName(cacheName);
        if (cache == null)
          continue;
        Log.Info(this + ". Clearing " + cache.Count + " items from " + cacheName, this);
        cache.Clear();
      }
    }

    private readonly ArrayList _caches = new ArrayList();
    public ArrayList Caches
    {
      get
      {
        return this._caches;
      }
    }
  }
}

The DoClear() method uses the <caches> list from the configuration to find which caches to clear. You should write the names of each of the caches to be cleared in the configuration.

FINAL NOTES:

The caches are first created when instantiated and the first element is added. That is why it is not available in the /sitecore/admin/cache.aspx administration tool on Sitecore startup.

MORE TO READ:

 



Send email via SparkPost and C#

0
0

SparkPost is yet another email service. It is easy to work with, seems robust, and it will be fairly easy on your client’s budget, as you get 100.000 emails per month for free (as per jan 2017).

A SparkPost email is set up using a template where you specify replacement tokens using a {{tokenname}} syntax:

Sparkpost Template with token replacements

Sparkpost Template with token replacements

Sending an email is done by calling the SparkPost API endpoint. You submit a JSON document containing the recipient email address plus the replacement tokens of your choice.

So let’s send an email.

First I will create a class that can generate the JSON string containing the data to send to SparkPost. The real coder would create objects that get serialized, but for this example, a StringBuilder will do the trick. To send an email we need at least a template name (the name of the SparkPost template), a campaignID (when tracking the emails afterwards) and an email address. The rest is dynamic data (and there is much more to do than just replacing strings but let’s keep it simple):

public byte[] CreateJsonBody(string templateName, string campaignID, string email, string firstName, string lastName)
{
  StringBuilder sb = new StringBuilder();
  sb.Append(@"{");
  sb.AppendFormat(@"  ""campaign_id"": ""{0}"",", campaignID);
  sb.Append(@"  ""recipients"": [");
  sb.Append(@"    {");
  sb.AppendFormat(@"      ""address"": ""{0}"",", email);
  sb.Append(@"      ""substitution_data"": {");
  sb.AppendFormat(@"        ""email"": ""{0}"",", email);
  sb.AppendFormat(@"        ""firstname"": ""{0}"",", firstName);
  sb.AppendFormat(@"        ""lastname"": ""{0}""", lastName);
  sb.Append(@"      }");
  sb.Append(@"    }");
  sb.Append(@"  ],");
  sb.Append(@"  ""content"": {");
  sb.AppendFormat(@"    ""template_id"": ""{0}""", templateName);
  sb.Append(@"  }");
  sb.Append(@"}");

  byte[] body = Encoding.UTF8.GetBytes(sb.ToString());
  return body;
}

Next step is to create a Service that will post the data to the SparkPost service:

using System;
using System.IO;
using System.Net;
using System.Text;

namespace MyNamespace
{
  public class SparkPostService
  {
    private readonly string _SPARKPOSTURL = "https://api.sparkpost.com/api/v1/transmissions?num_rcpt_errors=3";
    private readonly string _sparkPostAuthorizationKey;

    public SparkPostService(string sparkPostAuthorizationKey)
    {
      _sparkPostAuthorizationKey = sparkPostAuthorizationKey;
    }

    public string SendEmail(byte[] jsonBody, out string contentType)
    {
      try
      {
        HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(_SPARKPOSTURL);

        webRequest.KeepAlive = false;
        webRequest.ServicePoint.ConnectionLimit = 24;
        webRequest.Headers.Add("UserAgent", "MyUserAgent");
        webRequest.ProtocolVersion = HttpVersion.Version10;

        ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
        ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Ssl3;

        webRequest.ContentType = "application/json";
        webRequest.Accept = "application/json";
        webRequest.Headers.Add("Authorization", _sparkPostAuthorizationKey);
        webRequest.Method = WebRequestMethods.Http.Post;
        webRequest.ContentLength = jsonBody.Length;
        Stream dataStream = webRequest.GetRequestStream();
        dataStream.Write(jsonBody, 0, jsonBody.Length);
        dataStream.Close();
        byte[] bytes;
        using (WebResponse webResponse = webRequest.GetResponse())
        {
          contentType = webResponse.ContentType;
          using (Stream stream = webResponse.GetResponseStream())
          {
            using (MemoryStream memoryStream = new MemoryStream())
            {
              byte[] buffer = new byte[0x1000];
              int bytesRead;
              while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
              {
                memoryStream.Write(buffer, 0, bytesRead);
              }
              bytes = memoryStream.ToArray();
            }
          }
        }
        return Encoding.UTF8.GetString(bytes);
      }
      catch (Exception ex)
      {
        throw new Exception(GetType() + "Failed to retrieve data from '" + _SPARKPOSTURL + "': " + ex.Message, ex);
      }
    }


  }
}

To send an email I can now do the following:

string sparkPostKey = (found in SparkPost);
string contentType;

SparkPostService service = new SparkPostService(sparkPostKey);
string result = service.SendEmail(CreateJsonBody("my-first-email", "myCampaign", "bp@pentia.dk", "Brian", "Pedersen"), out contentType);

The SparkPost authorization key is created inside SparkPost.

SparkPost will now send an email where my name and email is substituted:

Sparkpost Email

Sparkpost Email

MORE TO READ:


Sitecore MongoDB – What do we know about a contact?

0
0

The Sitecore Analytics can be somewhat of a black box. All we know is that Sitecore stores everything about a site visitor in a Contact object and stuffs it into MongoDB when the visitor session expires.

A while ago my colleague Lasse Pedersen gave me the following scripts to be executed in RoboMongo to debug and see what Sitecore does know about my contact. The script is especially useful when working with named contacts. Replace the IDENTIFIER_NAME with the name of your contact and run it inside RoboMongo:

identifier = db.Identifiers.findOne({_id: /IDENTIFIER_NAME/i});
contact = db.Contacts.findOne({_id: identifier.contact});
classifications = db.ClassificationsMap.findOne({_id: contact._id });
userAgents = db.UserAgents.find({_id:{$in: classifications.Classificators.UserAgents}});
devices = db.Devices.find({LastKnownContactId: contact._id});
interactions = db.Interactions.find({ContactId: contact._id});

So what does the scripts return?

CONTACT IDENTIFIER

Contact Identifier

Contact Identifier

This is just the identifier information.

CONTACT

Contact Contact

Contact Contact

This is the complete Contact object, including all the Contact Facets that you have added yourself.

CONTACT CLASSIFICATIONS

Contact Classifications

Contact Classifications

This is the data that helps Sitecore identify if the current contact is a human or a robot.

 CONTACT USERAGENTS

Contact Useragents

Contact Useragents

Here you will find the reference to the user agent data, and the information as to whether the contact was indeed identified as a human or a robot.

CONTACT DEVICES

Contact Devices

Contact Devices

A device is essential the cookie that was used when the user interacted with the website. Sitecore will use these data to merge contacts as it links one Contact to every device used.

CONTACT INTERACTIONS

Contact Interactions

Contact Interactions

This is each interaction that the contact have had with the website.

MORE TO READ:


Get Sitecore System Info

0
0

Would you like to know the System Info of your current Sitecore instance:

Sitecore System Info

Sitecore System Info

It’s fairly easy. I created an SysInfo.aspx page and dumped in into the /sitecore modules/shell folder. The page is now available on the URL <yourwebsite>/sitecore modules/shell/sysinfo.aspx.

The top info is retrieved directly from Sitecore using the Sitecore.Configuration.About.ToHtml() method. The rest is retrieved from the Sitecore Configuration:

<%@ Page Language="c#" EnableEventValidation="false" AutoEventWireup="true" EnableViewState="false" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Security.Principal" %>
<%@ Import Namespace="System.Threading" %>

<script runat="server">
  void Page_Init(object sender, System.EventArgs e)
  {
  }

  void Page_Load(object sender, System.EventArgs e)
  {
    Response.Buffer = false;
    Response.BufferOutput = false;
    DataBind();
  }
</script>

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>System Info</title>
</head>
<body>
  <form id="MainForm" runat="server"></form>
  <%# Sitecore.Configuration.About.ToHtml() %><br />
  <br />
  <table>
    <tr>
      <td>Instance name</td>
      <td><%# Sitecore.Configuration.Settings.InstanceName %></td>
    </tr>
    <tr>
      <td>Data Folder</td>
      <td><%# Sitecore.Configuration.Settings.DataFolder %></td>
    </tr>
    <tr>
      <td>Log folder</td>
      <td><%# Sitecore.Configuration.Settings.LogFolder %></td>
    </tr>
    <tr>
      <td>Temp folder</td>
      <td><%# Sitecore.Configuration.Settings.TempFolderPath %></td>
    </tr>
    <tr>
      <td>Version file</td>
      <td><%# Sitecore.Configuration.Settings.VersionFilePath %> <%# File.Exists(Sitecore.Configuration.Settings.VersionFilePath) ? "found" : "Not found" %></td>
    </tr>
    <tr>
      <td>License file</td>
      <td><%# Sitecore.Configuration.Settings.LicenseFile %> <%# File.Exists(Sitecore.Configuration.Settings.LicenseFile) ? "found" : "Not found" %></td>
    </tr>
    <tr>
      <td>Analytics enabled</td>
      <td><%# Sitecore.Configuration.Settings.Analytics.Enabled %></td>
    </tr>
    <tr>
      <td>License ID</td>
      <td><%# Sitecore.SecurityModel.License.License.LicenseID %></td>
    </tr>
    <tr>
      <td>Sitecore.xDB.Base is present</td>
      <td><%# Sitecore.SecurityModel.License.License.HasModule("Sitecore.xDB.Base") %></td>
    </tr>
    <tr>
      <td>xDB enabled</td>
      <td><%# Sitecore.Configuration.Settings.GetBoolSetting("Xdb.Enabled", true) %></td>
    </tr>
  </table>
  <hr />
  <%# System.Web.Helpers.ServerInfo.GetHtml() %>
</body>
</html>

To top it off I also added a call to System.Web.Helpers.ServerInfo.GetHtml() to get the .ASP Net info:

.ASP Net Server Info

.ASP Net Server Info

 

CAUTION! THIS IS HIGHLY CLASSIFIED INFORMATION

Please be aware that the information on a page like this is highly classified and is pure candy for any hacker. So make sure that the page is not available from outside, or at least protected by security of some kind.

I always protect pages like this using Declarative security as described in this blog post: Using Declarative Security in Sitecore. Even if the page is not available from the web.

MORE TO READ: 


Sitecore Failed to update profile pattern

0
0

My Sitecore CD servers logged the following errors 2-3 times per minute:

7760 11:10:46 ERROR Failed to update profile pattern
Exception: System.ArgumentNullException
Message: Value cannot be null.
Parameter name: item
Source: Sitecore.Kernel
at Sitecore.Diagnostics.Assert.ArgumentNotNull(Object argument, String argumentName)
at Sitecore.Analytics.Data.Items.ItemRecords`1.<GetDescendants>d__8.MoveNext()
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at Sitecore.Analytics.Data.Items.ProfileItem.get_MappedPatterns()
at Sitecore.Analytics.Data.Items.ProfileItem.MatchPattern(IProfileData profile, IPatternDistance distance)
at Sitecore.Analytics.Tracking.Profile.UpdatePattern()

The error occurs because the editors who set up the profile and pattern cards on the solution accidentally renamed the Pattern Cards folder:

Profile and Pattern Cards

Profile and Pattern Cards

Do not rename the folder names. Sitecore uses the names to find the cards. Instead, change the Display Name if the name of the folder does not suit you.

MORE TO READ:


Sitecore how to make the experience editor a great experience for your editors

0
0

In Sitecore it is very easy to toss in a few renderings on a page, set the renderings flag to “editable”, and voila, the page is editable using the Sitecore Experience Editor.
But that kind of laziness often ends up being a pain for the people using the Experience Editor. Just image that it is you who gets this screen when selecting a rendering:

Select a Rendering - the bad way

Select a Rendering – hmm what to choose?

Wouldn’t you prefer a screen like this:

Select a rendering - the better way

Select a rendering – with images and translated text

Stuff like this takes more time that knowledge to do. So here is a quick guide on how to set up Sitecore so your renderings are presented properly in the experience editor.

 

SETTING UP YOUR RENDERING OR SUBLAYOUT

I still use sublayouts, the webform (.ascx) based renderings, but the techniques are the same for MVC based renderings. I will use the term “rendering” to cover both technologies.

THE DISPLAY TITLE

First of all, you need to set a display title for your renderings, as this is what Sitecore displays below the image. Remember that the language used for the display title is not the language you are editing, but the language in which Sitecore is running – exactly the opposite of what the Sitecore Shell does.

A rendering with a display title

All renderings with a display title, here in Danish

THE DATASOURCE LOCATION AND DATASOURCE TEMPLATE

In my project I save all data sources in a hidden folder below my item. This means that they are not shareable between items. A small trade off for a better overview of which components are placed on the page (this is only half the truth – some renderings are in fact shared but for the most part they are not. You should carefully decide what content is shareable and what is not). Setting up the data source location is done using a Sitecore query pointing to the folder below the current item:

Datasource Location and Template

Datasource Location and Template

When the user is seeing the “Select the Associated Content” window, only the components on the page is displayed. Also note that I have given each of my templates their own icon. This helps also.

Select the Associated Content

Select the Associated Content window

THE IMAGE

The field for the image shown in the “Select a Rendering” window is hidden in the appearance section of Sitecore, so you must enable the “Standard Fields” to find it. It is the field called “Thumbnail“.
Now, I know that the field have a “Take screenshot” function, the thumbnail is only 128×128 pixels, making a screenshot very small. I always make a stylized image, upload it to the media library in my own “System” folder and use that instead.

Thumbnail

Thumbnail

When moving the rendering to production you must remember to include the image in your package.

 

SETTING UP YOUR TEMPLATE

The template needs to be user friendly too. Here is what you can do:

THE ICON

Sitecore contains 16.000+ icons, and yes, none of these match your needs. However, spend a few minutes to see if you can find an icon that matches as close as possible. The icon is presented in the “Select the Associated Content” window so although it is not the most important part of your user experience, it is cheap to implement.

Templates with icons

Templates with icons

THE FIELD TITLE

Remember to set a title for your field. My field names are namespaced and rather technical. The user should not be aware of my strange naming. Use the field “Title” and again, remember that it is the language of the Sitecore instance that is displayed, not the language of your content – the exact opposite of what the Sitecore Shell does.

Editing the title

Editing the title – here with a translated text for the field

 

SUMMARY

Setting up the renderings for the experience editor requires more time than knowledge. Be lazy and the end user will hate you. Be thorough and your end user will love you.

 


Sitecore Hiding items: Clean up the cluttered content tree

0
0

As a followup to a question from Derek Dysart on my previous post, How to make the experience editor a great experience for your editors I will explain how you can clean up the Sitecore content tree by hiding system items.

A common system item is the “components” (or whatever you call it) folder containing the data sources for components belonging to a certain content item. This folder is usually placed below your content item. The folder have no presentation, no URL, and does only exist because we like to build pages from many data sources in order to be able to personalize every component of a page.

Components Folder

A Components folder below the content item

This is not something the editors should see. Their tree should only contain the actual contents of the website.

A tree containing content only

A tree containing content only

All you have to do to is to select the “components” folder on your master, go to the “Configure” tab and select “Hide item“:

Hide Item

Hide Item

When the item is hidden, the button text changes:

Hidden Item

Hidden Item

Hiding items does only mean that the item is gone from the content tree. The item is still fully available from Sitecore, and also from the Experience Editor:

Hidden folder visible in the Experience Editor

Hidden folder visible in the Experience Editor

Also, as an administrator you can still see the item, provided you check the “Hidden Items” on the “View” tab:

View Hidden Items Enabled

View Hidden Items Enabled

MORE TO READ:


Sitecore how to configure SSL (HTTPS) in your Sitecore config files

0
0

SSL and HTTPS have been the hot topic since Google began to use HTTPS as a signal in their search rankings.

Fortunately, Sitecore really does not care if it runs in HTTP or HTTPS, so most of the configurations are purely .NET related. Here is what you do if you have a single domain Sitecore installation, or a Sitecore installation that shares the root domain (en.mydomain.com/de.mycomain.com/…).

STEP 1: OBTAIN A SSL CERTIFICATE AND INSTALL IT ON YOUR SERVER

This usually involves a hosting company. If you are lucky enough (as I am) to know a hosting company that takes responsibility and provide a great service, this requires sending a mail and wait for the answer. If not you can follow one of the many guides online on how to obtain and install the SSL certificate.

STEP 2: MODIFY FORMS AUTHENTICATION

In web.config, add your domain to the forms authentication. This is pure .NET, nothing to do with Sitecore. Replace the MYCOMAIN.COM with your own domain.

<authentication mode="None">
  <forms domain=".MYDOMAIN.COM" timeout="43200" slidingExpiration="true" name=".ASPXAUTH" cookieless="UseCookies" />
</authentication>

STEP 3: MODIFY HTTPCOOKIES

In web.config, add your domain and set requireSSL in the httpCookies property. Again, this is .NET, not Sitecore specific. This binds any cookies to the root domain we specify, and secures the cookie (no cookies are sent to the server unless the connection is secure (HTTPS)).

<httpCookies httpOnlyCookies="true" requireSSL="true" lockItem="true" domain=".YOURDOMAIN.COM" />

STEP 4: CHANGE THE SCHEME PROPERTY IN THE SITES SECTION

In Sitecore.config, set the scheme=”https” in your sites section. This is used by Sitecore when URL’s are generated, and ensures that when a fully qualified URL is generated, the URL is prefixed with https://

<site name="website"
      scheme="https"
      hostName="YOURDOMAIN.COM"
      targetHostName="YOURDOMAIN.COM"
      ... ...
/>

STEP 5: ALLOW YOUR IIS OR LOAD BALANCER TO REDIRECT ALL REQUESTS FROM HTTP TO HTTPS

The site as configured now will still serve pages using the HTTP protocol. But any cookies will not be persisted unless we use the proper domain and proper scheme. So you need to redirect any HTTP request to HTTPS. This is always a fun coding project, but can also be achieved by configuring the load balancer, or using the IIS URL Rewrite Module.

MORE INFO

 



Sitecore and Feature Flags using LaunchDarkly

0
0

Feature flags are a software development best practice of gating functionality. Functionality can be deployed “off”, then turned on via the feature flag, separate from deployment. With feature flags, you can manage the entire lifecycle of a feature.

  • Launchdarkly.com

In essence, a feature flag is a if..then..else statement, where the outcome of the statement is controlled by feeding a flag into a feature flag system:

if (GetFeatureFlag("feature-flag") == true)
{
  // enable my feature
}
else
{
  // disable my feature
}

Feature flags are user specific and can be used cross platform, and is therefore a great way to release new features:

  • At the same time on all platforms
  • To 10% of all users
  • To all users from a specific segment
  • To website only

Feature flags should not be confused with Sitecore Personalization, although they both share similarities. The purpose of Personalization is to tailor the user experience to the end users needs, whilst feature flags (or feature toggling) is enabling or disabling features based on rules separate from deployment or code.

A feature flag is often short-lived, and the flag is removed when the gated feature is considered stable.

 

ABOUT THE FEATURE FLAG PLATFORM

I have lately worked with a feature flag platform called “Launchdarkly“. They offer API’s for every possible programming language and for every possible platform. Their code is blazingly fast, and you do not notice any performance loss when checking for a flag.

Feature Flags Overview

Feature Flags Overview

In Launchdarkly, you can enable features by flipping a switch, or by segmenting on user data:

Feature Flags Targeting

Feature Flags Targeting

 

IMPLEMENTING LAUNCHDARKLY IN SITECORE

In order to check for a feature flag, we need to send user data and a feature flag name to Launchdarkly. The Launchdarkly API uses a Launchdarkly.Client.User and you can fill any property from your Sitecore User to the Launchdarkly Client User.

This is a simple example where I create a Launchdarkly user containing a unique ID, the email address and a name:

using System;
using LaunchDarkly.Client;

namespace MyNamespace
{
  static internal class LaunchDarklyFactory
  {
    static internal User CreateUser()
    {
      var user = Sitecore.Context.User;
      var key = user.IsAuthenticated ? user.Name : Guid.NewGuid().ToString();
      var ldUser = User.WithKey(key).AndAnonymous(!user.IsAuthenticated).AndEmail(user.Profile.Email).AndFirstName(user.Profile.FullName).AndLastName("."); //LaunchDarkly won't update display name unless both first and last is set.
      return ldUser;
    }
  }
}

With the user in hand it is easy to create a feature flag repository:

using System;
using LaunchDarkly.Client;
using Sitecore.Configuration;
using Sitecore.Diagnostics;

namespace MyNamespace
{
  public static class LaunchDarklyRepository
  {
    // My Launchdarkly key is stored in a setting in a config file
    private static readonly LdClient _client = new LdClient(Settings.GetSetting("LaunchDarkly.Client"));

    public static bool GetFeatureFlag(string flag, bool defaultValue)
    {
      try
      {
        var ldUser = LaunchDarklyFactory.CreateUser();
        return _client.BoolVariation(flag, ldUser, defaultValue);
      }
      catch (Exception ex)
      {
        Log.Error(typeof (LaunchDarklyRepository) + ".GetFeatureFlag() failed to get feature flag '" + flag + "', returning default value " + defaultValue + ". Message:" + ex.Message, typeof (LaunchDarklyRepository));
        return defaultValue;
      }
    }
  }
}

The repository can now be used to enable or disable features:

if (LaunchDarklyRepository.GetFeatureFlag("my-feature-flag", false))
{
  // feature enabled
}
else
{
  // feature disabled
}

 

INTEGRATING LAUNCHDARKLY INTO THE EXPERIENCE EDITOR

Experience Editor

Experience Editor

With the new feature flag framework in place, it could be nice with a Sitecore Rule. This rule will allow me to enable and disable components on my website by flipping a feature flag switch. The rule itself is very simple:

using System;
using Sitecore.Diagnostics;
using Sitecore.Rules;
using Sitecore.Rules.Conditions;

namespace MyNamespace
{
  public class FeatureFlagHasBoolVariation<T> : WhenCondition<T> where T : RuleContext
  {
    public string FeatureFlag
    {
      get;
      set;
    }

    public bool BoolVariation
    {
      get;
      set;
    }

    protected override bool Execute(T ruleContext)
    {
      try
      {
        LaunchDarklyUser user = LaunchDarklyUserFactory.GetLaunchDarklyUser();
        var boolVariation = LaunchDarklyRepository.GetFeatureFlag(FeatureFlag, false);
        return boolVariation == BoolVariation;
      }
      catch (Exception ex)
      {
        Log.Error(string.Format("{0}: {1} ", GetType(), ex.Message), ex, this);
        return false;
      }
    }
  }
}

To enable the rule in Sitecore, add an item under /sitecore/system/Settings/Rules/Definitions/Elements/…

Feature Flag Personalisation Rule

Feature Flag Personalization Rule

The rule can now applied to a component:

Rule Set Editor

Rule Set Editor

And the component can be visible if the flag is set:

Personalize Content

Personalize Content

MORE TO READ


Sitecore Caching – Clear caches individually

0
0

The Sitecore caching engine is a simple yet powerful tool where Sitecore not only stores it’s own data for fast retrieval, but allows you to store your own data of any kind. Sitecore have a built in overview of the contents of the caches, but the overview lacks the possibility to clear caches individually.
The cache admin is located on the /sitecore/admin/cache.aspx URL, and as you can see, you can only clear all caches:

Sitecore Cache Overview

Sitecore Cache Overview

Clearing one cache at a time is very easy:

Sitecore.Caching.Cache cache = Sitecore.Caching.CacheManager.FindCacheByName("name of cache");
cache.Clear();

So it is not that hard to create your own cache admin page that can clear each cache individually:

Your own Cache Overview Page

Your own Cache Overview Page

This is a simple .aspx page that lists all caches, displays some nice cache stats, and sports a nice “Clear Cache” button that clears that specific cache. The code is simple:

<%@ Page language="c#" EnableEventValidation="false" AutoEventWireup="true" EnableViewState="false" %>
<%@ Import Namespace="System.Security.Permissions" %>
<%@ Import Namespace="System.Collections.Generic" %>
<%@ Import Namespace="System.Threading" %>
<%@ Import Namespace="System.Security.Principal" %>

<script runat="server">
  void Page_Load(object sender, System.EventArgs e)
  {
    Response.Buffer = false;
    Response.BufferOutput = false;
    DataBind();
  }

  IEnumerable<Sitecore.Caching.Cache> Caches
  {
    get
    {
      return Sitecore.Caching.CacheManager.GetAllCaches().OrderBy(c => c.Name);
    }
  }

  double PercentageUsed(Sitecore.Caching.Cache cache)
  {
    if (cache.MaxSize == 0)
      return 0;
    return Math.Round(((double)cache.Size/(double)cache.MaxSize*100), 0);
  }

  string PercentageColor(double percentage)
  {
    if (percentage >= 0 && percentage <= 50)
      return "green";
    if (percentage >= 50 && percentage <= 75)
      return "orange";
    return "red";
  }

  private void repCaches_Command(object source, RepeaterCommandEventArgs e)
  {
    switch (e.CommandName)
    {
      case "ClearCache":
        string cacheName = e.CommandArgument as string;
        Sitecore.Caching.Cache cache = Sitecore.Caching.CacheManager.FindCacheByName(cacheName);
        cache.Clear();
        repCaches.DataBind();
        break;
    }
  }

  long TotalMaxSize()
  {
    long ac = 0;
    foreach (var cache in Caches)
      ac = ac + cache.MaxSize;
    return ac;
  }

  long TotalSize()
  {
    long ac = 0;
    foreach (var cache in Caches)
      ac += cache.Size;
    return ac;
  }

  double TotalPercentageUsed()
  {
    if (TotalMaxSize() == 0)
      return 0;
    return Math.Round(((double)TotalSize()/(double)TotalMaxSize()*100), 1);
  }
</script>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<html>
  <head>
    <title>Cache Plus</title>
    <style type="text/css">
      body {
        font: normal 12pt arial, verdana;
        padding:20px;
      }

      .box {
        padding:20px;
        margin:20px;
        border: solid 1px black;
        background-color:#efefef;
      }

      input {
        height: 30px;
        width: 100px;
      }

      td {
        border-bottom: solid 1px #aaa;
        padding-right: 20px;
        padding-left: 5px;
        padding-top: 5px;
        padding-bottom: 5px;
      }

      table {
        width:100%;
      }

      thead td {
        font-weight: bold;
        border-bottom: solid 1px #aaa;
        padding-right: 20px;
      }
    </style>
  </head>
  <body style="font-size:14px">
    <form runat="server">
      <div style="padding:20px; background-color:#eaeaea; border-bottom: solid 1px #777777; font-size:16px">
        <%# Sitecore.StringUtil.GetSizeString(TotalSize()) %> of <%# Sitecore.StringUtil.GetSizeString(TotalMaxSize()) %> used <strong>(<%# TotalPercentageUsed() %>%)</strong>
      </div>
      <asp:Repeater ID="repCaches" runat="server" EnableViewState="false" OnItemCommand="repCaches_Command" DataSource="<%# Caches %>">
        <HeaderTemplate>
          <table>
            <thead>
            <tr>
              <td>Name</td>
              <td></td>
              <td>Size</td>
              <td>MaxSize</td>
              <td>% Used</td>
            </tr>
            </thead>
        </HeaderTemplate>
        <FooterTemplate>
          </table>
        </FooterTemplate>
        <ItemTemplate>
          <tr>
            <td style="width:250px">
              <%# (Container.DataItem as Sitecore.Caching.Cache).Name %>
            </td>
            <td style="width:100px">
              <asp:Button ID="btnClearCache" runat="server" CommandArgument="<%# (Container.DataItem as Sitecore.Caching.Cache).Name %>" CommandName="ClearCache" text="Clear Cache"/>
            </td>
            <td style="text-align: right; width:80px">
              <%# Sitecore.StringUtil.GetSizeString((Container.DataItem as Sitecore.Caching.Cache).Size) %>
            </td>
            <td style="text-align: right; width:80px">
              <%# Sitecore.StringUtil.GetSizeString((Container.DataItem as Sitecore.Caching.Cache).MaxSize) %>
            </td>
            <td>
              <div style="width:<%# PercentageUsed((Container.DataItem as Sitecore.Caching.Cache)) %>%; height:30px; background-color:<%# PercentageColor(PercentageUsed((Container.DataItem as Sitecore.Caching.Cache))) %>; float:left;"></div>
              <div style="width:<%# (100 - PercentageUsed((Container.DataItem as Sitecore.Caching.Cache))) %>%; height:30px; background-color:#ccffcc; float:left;"></div>
            </td>
          </tr>
        </ItemTemplate>
      </asp:Repeater>
    </form>
  </body>
</html>

Copy the code to a file named .aspx, and put the file in /sitecore modules/shell.

MORE TO READ:

Thanks to Richard Hauuer @richardhauer for the inspiration to write this article.


Sitecore ContentSearch Get Latest Version

0
0

The Sitecore ContentSearch API allows you to index content in either .NET Lucene or SOLR, dramatically speeding up retrieval of content, especially when querying items that are scattered across your content tree.

Content retrieved from the ContentSearch API is not Sitecore Content Items (of type Sitecore.Data.Items.Item), they are objects that you have defined yourself. This is why you will experience that when querying from the MASTER database index (SITECORE_MASTER_INDEX), you will receive one result per version (and one result per language) rather than one Item object containing all versions and languages.

To overcome this issue, Sitecore have added a few nifty fields to the index, for example the _latestversion field:

Latest Version Field from SOLR

Latest Version Field found in my SOLR index

So when querying the index, you can add the _latestversion field:

query = query.Where(item => item["_latestversion"].Equals("1")); 

BTW, _latestversion is defined in a constant value in Sitecore:

Sitecore.ContentSearch.BuiltinFields.LatestVersion;

MORE TO READ: 


Sitecore user:created event not fired on Membership.CreateUser

0
0

Sitecore have since 7.5’ish fired events each time you manipulate the users in the .NET Membership database:

<event name="user:created"></event>
<event name="user:deleted"></event>
<event name="user:updated"></event>
<event name="roles:usersAdded"></event>
<event name="roles:usersRemoved"></event>

But I noticed that the user:created event was not fired. This is because I call the .NET Membership provider directly:

string userNameWithDomain = "extranet\\myuser";
string password = "somepassword";
string email = "myuser@somewhere.com";
Membership.CreateUser(userNameWithDomain, password, email);

This call to Membership is not handled by Sitecore, thus no event is executed. To fix this I have found 2 solutions, one is not good and the other one is not good either.

SOLUTION 1: CALL THE SITECORE MEMBERSHIP PROVIDER DIRECTLY

This solution ignores the web.config settings and assume that you have not switched or overwritten the Membership Provider yourself. But it will fire the event though:

Sitecore.Security.SitecoreMembershipProvider provider = new Sitecore.Security.SitecoreMembershipProvider();
MembershipCreateStatus status = MembershipCreateStatus.Success;
provider.CreateUser(usernameWithDomain, password, email, "", "", true, null, out status);
if (status != MembershipCreateStatus.Success)
  throw new MembershipCreateUserException(status);

SOLUTION 2: RAISE THE EVENT YOURSELF

This solution requires you to raise the event yourself. You need encapsulate the call to create a user in your own class, and instruct everyone to never call Membership.CreateUser() directly:

MembershipUser user = Membership.CreateUser(usernameWithDomain, password, email);
Event.RaiseEvent("user:created", user);

I can see from other blog posts that the user events are not the most widely used events in the Sitecore toolbox. If you have found another solution to this problem please let me know.

MORE TO READ:


Sitecore ContentSearch – Get items from SOLR or Lucene – A base class implementation

0
0

Reading items from Sitecore is pretty straight forward:

Sitecore.Data.Items.Item item =
   Sitecore.Context.Database.GetItem("/sitecore/context/.../...");

And it is fast, unless you need to retrieve items from many paths, or need to retrieve every child of a certain base class. In these situations you resolve to using the built in ContentSearch, which is a Lucene or SOLR index.

When working with objects from the ContentSearch API you will have to create your own model classes that maps the indexed fields to class properties. This is done using an IndexFieldAttribute to the properties of the class that will represent the indexed data:

[IndexField("customerpage")]
public ID CustomerPageId {	get; internal set; }

[IndexField("customername")]
public string CustomerName { get; internal set; }

The default indexes called sitecore_core_index, sitecore_master_index and sitecore_web_index is born with a long list of default properties that is useful for every class. Because of this it makes sense to let every one of your model classes inherit from a base class that maps these fields for you.

So let’s code.

STEP 1: CREATE A BASE CLASS

This base class maps the most common fields. There are many more for you to explore, but this particular class have been the base class of a huge project that I have been working on for the past 4 years:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using Sitecore.Configuration;
using Sitecore.ContentSearch;
using Sitecore.ContentSearch.Converters;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;

namespace MySearch
{
  [Serializable]
  public abstract class SearchResultItem
  {
    [NonSerialized]
    private Item _item;

    // Get the actual Sitecore item. Beware that using this property
    // will substantially slow your query, as it looks up the item
    // in Sitecore. Use with caution, and try to avoid using it in
    // LINQ or enumerations
    public virtual Item Item
    {
      get { return _item ?? (_item = GetItem()); } set { _item = value; }
    }

    // Returns the Item ID (in SOLR this is stored as a short GUID in the _group field)
    [IndexField(Sitecore.ContentSearch.BuiltinFields.Group)]
    [TypeConverter(typeof(IndexFieldIDValueConverter))]
    public virtual ID ItemId
    {
      get; set;
    }

    // This is a combined key describing the Sitecore item in details
    // For example: sitecore://web/{7102ee6b-6361-41ad-a47f-832002082a1a}?lang=da&ver=1&ndx=sitecore_web_index
    // With the ItemUri class you can extract the individual values like database, id, language, version
    [IndexField(Sitecore.ContentSearch.BuiltinFields.UniqueId)]
    [TypeConverter(typeof(IndexFieldItemUriValueConverter))]
    public virtual ItemUri ItemUri
    {
      get; set;
    }

    // Return the item language
    [IndexField(Sitecore.ContentSearch.BuiltinFields.Language)]
    public virtual string Language
    {
      get; set;
    }

    // Returns true if the item is the latest version. When reading from the
    // web database index, this will alwaus be true.
    [IndexField(Sitecore.ContentSearch.BuiltinFields.LatestVersion)]
    public bool IsLatestVersion
    {
      get; set;
    }

    // Returns the ID's of every parent sorted by top parent first
    [IndexField(Sitecore.ContentSearch.BuiltinFields.Path)]
    [TypeConverter(typeof(IndexFieldEnumerableConverter))]
    public virtual IEnumerable<ID> ItemAncestorsAndSelf
    {
      get; set;
    }

    // Returns the updated datetime
    [IndexField(Sitecore.ContentSearch.BuiltinFields.SmallUpdatedDate)]
    public DateTime Updated
    {
      get; set;
    }

    // Returns every template that this item implements and inherits
    [IndexField(Sitecore.ContentSearch.BuiltinFields.AllTemplates)]
    [TypeConverter(typeof(IndexFieldEnumerableConverter))]
    public virtual IEnumerable<ID> ItemBaseTemplates
    {
      get; set;
    }

    private Item GetItem()
    {
      Assert.IsNotNull(ItemUri, "ItemUri is null.");
      return Factory.GetDatabase(ItemUri.DatabaseName).GetItem(ItemUri.ItemID, ItemUri.Language, ItemUri.Version);
    }
  }
}

STEP 2: CREATE A MODEL CLASS FOR A SPECIFIC TEMPLATE

This example inherits from the SearchResultItem base class, and encapsulates a Customer template containing 2 fields, CustomerPage and CustomerName.

namespace MySearch
{
  [DataContract]
  [Serializable]
  public class CustomerModel : SearchResultItem
  {
    [DataMember]
    [IndexField("customername")]
    public string CustomerName { get; internal set; }

    [IndexField("customerpage")]
    public ID CustomerPageId { get; internal set; }
  }
}

STEP 3: USING THE BASE CLASS TO SEARCH USING PREDICATES

A Predicate are a Latin word for “making search soo much easier”. Predicates defines reusable static functions. When run, Predicates become part of the index query itself, further improving performance. So let’s start by making 3 predicates:

namespace MySearch
{
  public static class Predicates
  {
    // Ensure that we only return the latest version
    public static Expression<Func<T, bool>> IsLatestVersion<T>() where T : SearchResultItem
    {
      return searchResultItem => searchResultItem.IsLatestVersion;
    }

    // Ensure that the item returned is based on, or inherits from the specified template
    public static Expression<Func<T, bool>> IsDerived<T>(ID templateID) where T : SearchResultItem
    {
      return searchResultItem => searchResultItem.ItemBaseTemplates.Contains(templateID);
    }

    // Ensure that the item returned is a content item by checking that the
    // content root is part of the item path
    public static Expression<Func<T, bool>> IsContentItem<T>() where T : SearchResultItem
    {
      return searchResultItem => searchResultItem.ItemAncestorsAndSelf.Contains(ItemIDs.ContentRoot);
    }
  }
}

With these predicates in place, I can create a repository for my Customer items:

namespace MySearch
{
  public class CustomerModelRepository
  {
    private readonly Database _database;

    public CustomerModelRepository() : this(Context.Database)
    {
    }

    public CustomerModelRepository(Database database)
    {
      _database = database;
    }

    public IEnumerable<CustomerModel> GetAll()
    {
      return Get(PredicateBuilder.True<CustomerModel>());
    }

    private IEnumerable<CustomerModel> Get(Expression<Func<CustomerModel, bool>> predicate)
    {
      using (IProviderSearchContext context = GetIndex(_database).CreateSearchContext(SearchSecurityOptions.DisableSecurityCheck))
      {
        return context.GetQueryable<CustomerModel>()
          .Where(Predicates.IsDerived<CustomerModel>(new ID("{1EB6DC02-4EBD-427A-8E36-7D2327219B6C}")))
          .Where(Predicates.IsLatestVersion<CustomerModel>())
          .Where(Predicates.IsContentItem<CustomerModel>())
          .Where(predicate).ToList();
      }
    }

    private static ISearchIndex GetIndex(Database database)
    {
      Assert.ArgumentNotNull(database, "database");
      switch (database.Name.ToLowerInvariant())
      {
        case "core":
          return ContentSearchManager.GetIndex("sitecore_core_index");
        case "master":
          return ContentSearchManager.GetIndex("sitecore_master_index");
        case "web":
          return ContentSearchManager.GetIndex("sitecore_web_index");
        default:
          throw new ArgumentException(string.Format("Database '{0}' doesn't have a default index.", database.Name));
      }
    }
  }
}

The private Get() method returns every index item following these criteria:

  • Must implement or derive from the template with the specified GUID (the GUID of the Customer template) = Predicates.IsDerived
  • And must be the latest version = Predicates.IsLatestVersion
  • And must be a content item = Predicates.IsContentItem

The repository is used like this:

CustomerModelRepository rep = new CustomerModelRepository(Sitecore.Context.Database);
IEnumerable<CustomerModel> allCustomers = rep.GetAll();
foreach (CustomerModel customer in allCustomers)
{
  // do something with the customer
  customer.CustomerName;
}

I hope this introduction will help you create your own base class implementation and start making fast content searches.

MORE TO READ:

For more SOLR knowledge, you should read my colleague Søren Engel‘s posts about SOLR:

These resources could also be helpful:

 


Viewing all 263 articles
Browse latest View live




Latest Images