500 Error Page in your Sitecore application

I have been asked about the simplest implementation of a 500 Error Page for a Sitecore project that support Multiple Sites/Brands and multiple languages….

There are several theories on how to implement it, but in my opinion the 500 Error page HAS to be a static Html file…
Therefore I would recommend the following implementation within your Global.Asax

private void Application_Error( object sender, EventArgs e)
 {
    var customErrorsSection = ( CustomErrorsSection)   ConfigurationManager.GetSection("system.web/customErrors" );
    var lastException = Server.GetLastError();
    if (customErrorsSection.Mode != CustomErrorsMode.Off)
       {
         try
             {
               Log.Error( "There was an error in the application", lastException);
               Server.ClearError();
    HttpContext.Current.Response.StatusCode = (int )HttpStatusCode.InternalServerError;
      Server.Transfer( string.Format( "/Error/{0}_500.html" , GetSafeLanguage()));
                }
                catch
                 {
                 }
         }
 }

private string GetSafeLanguage()
 {
    try
        {
          return Sitecore.Context.Language.CultureInfo.TwoLetterISOLanguageName;
        }
        catch
        {
        }
          return string.Empty;
 }

I would also recommend the following setting to handle Sitecore Pipelines error as all normal errors

<system.webServer>
 <httpErrors existingResponse="PassThrough" />
Advertisements

TDD – HttpContextFactory

TDD on the controllers could be painful, this class abstract the httpcontext making your code easier to test

using System;
using System.Web;

namespace SdbBackbone.Factories
{
    public class HttpContextFactory
    {
        private static HttpContextBase _context;

        public static HttpContextBase Current
        {
            get
            {
                if (_context != null)
                    return _context;

                if (HttpContext.Current == null)
                    throw new InvalidOperationException("HttpContext not available");

                return new HttpContextWrapper(HttpContext.Current);
            }
        }

        public static void SetCurrentContext(HttpContextBase context)
        {
            _context = context;
        }
    }
}

Xml Serializer utility

Every developer at somepoint has to deal with Xml Serialization.

This utility make it simple the object serialization and deserialization

 

using System.IO;
using System.Xml.Serialization;

namespace SdbBackbone.Extensions
{
    public static class XmlTools
    {
        public static string ToXmlString<T>(this T input)
        {
            using (var writer = new StringWriter())
            {
                input.ToXml(writer);
                return writer.ToString();
            }
        }

        public static void ToXml<T>(this T objectToSerialize, Stream stream)
        {
            new XmlSerializer(typeof(T)).Serialize(stream, objectToSerialize);
        }

        public static void ToXml<T>(this T objectToSerialize, StringWriter writer)
        {
            var ns = new XmlSerializerNamespaces();
            ns.Add("", "http://www.w3.org/2001/XMLSchema-instance");
            ns.Add("", "http://www.w3.org/2001/XMLSchema");

            new XmlSerializer(typeof(T)).Serialize(writer, objectToSerialize, ns);
        }
    }
}

String Extension

Who does not need some extension to the string class?

this is an useful one to have in your core library

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using System.Web;
using System.Xml;
using System.Xml.Serialization;
using HtmlAgilityPack;
using SdbBackbone.Utils.Xml;

namespace SdbBackbone.Extensions
{
    public static class StringExtensions
    {
        public static bool Contains(this string target, string value, StringComparison comparison)
        {
            return target.IndexOf(value, comparison) >= 0;
        }

        /// <summary>Cleans the html string and removes the body tag if present</summary>
        /// <param name="html">The html</param>
        /// <param name="errors">Retuns error when parsing the html. If html is valid, returns an empty IEnumerable</param>
        /// <returns>The cleaned html</returns>
        public static string CleanHtml(this string html, out IEnumerable<string> errors)
        {
            var doc = new HtmlDocument {OptionWriteEmptyNodes = true};
            doc.LoadHtml(html);
            errors = doc.ParseErrors != null && doc.ParseErrors.Any()
                         ? doc.ParseErrors.Select(e => e.ToString())
                         : Enumerable.Empty<string>();
            return doc.DocumentNode.SelectSingleNode("//body") != null
                       ? doc.DocumentNode.SelectSingleNode("//body").InnerHtml
                       : doc.DocumentNode.InnerHtml;
        }

        public static string FormatWith(this string str, params object[] args)
        {
            return String.Format(str, args);
        }

        public static bool IsEmpty(this string str)
        {
            return String.IsNullOrWhiteSpace(str);
        }

        public static string RemoveHtmlTags(this string text)
        {
            return Regex.Replace(text ?? string.Empty, "</?[^>]*>", string.Empty, RegexOptions.IgnoreCase);
        }

        /// <summary>
        ///     A case insenstive replace function.
        /// </summary>
        /// <param name="originalString">The string to examine.(HayStack)</param>
        /// <param name="oldValue">The value to replace.(Needle)</param>
        /// <param name="newValue">The new value to be inserted</param>
        /// <returns>A string</returns>
        public static string CaseInsenstiveReplace(string originalString, string oldValue, string newValue)
        {
            var regEx = new Regex(oldValue, RegexOptions.IgnoreCase | RegexOptions.Multiline);
            return regEx.Replace(originalString, newValue);
        }

        public static string StripForUrl(this string title)
        {
            string url = title;

            url.RemoveHtmlTags();
            url = HttpUtility.HtmlDecode(url);

            //get rid of URI escapes
            url = Regex.Replace(url, "%[a-fA-F0-9]{2}", "", RegexOptions.Compiled);

            //get rid of HTML escapes
            url = Regex.Replace(url, "&(\\#{1}\\d*|\\w*);", "", RegexOptions.Compiled);

            //go to town on special characters
            url = Regex.Replace(url, "[~`!@#$%^*()<>?,.\\|/:;'\"\\\\]", "", RegexOptions.Compiled);

            //get rid of all non-ascii characters
            url = Regex.Replace(url, "[^\\x20-\\x7E]", "", RegexOptions.Compiled);

            //All theese lines are covered by otehr lines in this function
            //strOutput = Regex.Replace(strOutput, "</?[^>]*>", "", RegexOptions.Compiled) ' Removes any HTML tags from the text
            //strOutput = Regex.Replace(strOutput, "[^\w \s\./-]", "", RegexOptions.Compiled)
            //strOutput = Regex.Replace(strOutput, "\s", "-", RegexOptions.Compiled)
            //strOutput = Regex.Replace(strOutput, "\.", "", RegexOptions.Compiled)
            //strOutput = Regex.Replace(strOutput, "/.", "", RegexOptions.Compiled)

            //replace ampersands with the word 'and', providing they're surrounded by white space
            url = Regex.Replace(url, "\\s&\\s", " and ", RegexOptions.Compiled);

            //replace all other ampersands with hyphens
            url = Regex.Replace(url, "&", "-", RegexOptions.Compiled);

            //get rid of groups of spaces (ie 2 or more in a row)
            url = Regex.Replace(url, "\\s{2,}", "", RegexOptions.Compiled);

            //replace spaces with dashes
            url = url.Trim();
            url = Regex.Replace(url, "\\s", "-", RegexOptions.Compiled);
            url = Regex.Replace(url, "-{2,}", "-", RegexOptions.Compiled);

            const int maxLength = 100;
            if (url.Length > maxLength)
            {
                if (url.IndexOf("-", StringComparison.Ordinal) > maxLength)
                {
                    url = url.Substring(0, maxLength);
                }
                else
                {
                    string strTemp = "";

                    foreach (string word in url.Split('-'))
                    {
                        if (strTemp.Length + word.Length > maxLength)
                        {
                            break;
                        }

                        if (strTemp.Length == 0)
                        {
                            strTemp += word;
                        }
                        else
                        {
                            strTemp += "-" + word;
                        }
                    }

                    url = strTemp;
                }
            }

            return url;
        }

        public static string EscapeSingleQuotes(this string value)
        {
            return value.Replace("'", "\\u0027");
        }

        public static string UnescapeSingleQuotes(this string value)
        {
            return value.Replace("\\u0027", "'");
        }

        public static string Decode(this string value)
        {
            return WebUtility.HtmlDecode(value);
        }

        public static Stream ToStream(this string @this)
        {
            var stream = new MemoryStream();
            var writer = new StreamWriter(stream);
            writer.Write(@this);
            writer.Flush();
            stream.Position = 0;
            return stream;
        }

        /// <summary>
        /// Deserialize an XML string to an object of type T
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value"></param>
        /// <returns></returns>
        public static T DeserializeXML<T>(this string value)
        {
            var serializer = new XmlSerializer(typeof (T));
            var reader = new NamespaceIgnorantXmlTextReader(new StringReader(value));  //XmlReader.Create(value.Trim().ToStream());
            return (T) serializer.Deserialize(reader);
        }

        public static string SerializeXML<T>(this string value, T obj)
        {
            var serializer = new XmlSerializer(typeof (T));
            var stream = new MemoryStream();
            using (var tw = new XmlTextWriter(stream, System.Text.Encoding.UTF8))
            {
                serializer.Serialize(stream, obj);
                return System.Text.Encoding.UTF8.GetString(stream.ToArray());   
            }
        }
         
    }
}

Encryption Utility based on MachineKey

This is a supersimple utility class to encrypt data in .net

Obviously you need to have the same MachineKey in your config to be able to decrypt it…

using System;
using System.Configuration;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Web.Configuration;

namespace SdbBackbone.Encryption
{
    public static class EncrytionService
    {
        // This constant string is used as a "salt" value for the PasswordDeriveBytes function calls.
        // This size of the IV (in bytes) must = (keysize / 8).  Default keysize is 256, so the IV must be
        // 32 bytes long.  Using a 16 character string here gives us 32 bytes when converted to a byte array.
        private const string initVector = "tu89geji340t89u2";

        // This constant is used to determine the keysize of the encryption algorithm.
        private const int keysize = 256;

        public static string ValidationKey
        {
            get
            {
                //Retrieves a specified configuration section for the current application's default configuration.
                MachineKeySection section = (MachineKeySection) ConfigurationManager.GetSection("system.web/machineKey");
                return section.ValidationKey;
                // return ConfigurationManager.GetSection("system.web/machineKey/validationKey").ToString();
            }
        }

        public static string Encrypt(string plainText )
        {
            return Encrypt(plainText, ValidationKey);
        }

        private static string Encrypt(string plainText, string passPhrase)
        {
            byte[] initVectorBytes = Encoding.UTF8.GetBytes(initVector);
            byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
            var password = new PasswordDeriveBytes(passPhrase, null);
            byte[] keyBytes = password.GetBytes(keysize/8);
            var symmetricKey = new RijndaelManaged();
            symmetricKey.Mode = CipherMode.CBC;
            ICryptoTransform encryptor = symmetricKey.CreateEncryptor(keyBytes, initVectorBytes);
            var memoryStream = new MemoryStream();
            var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write);
            cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
            cryptoStream.FlushFinalBlock();
            byte[] cipherTextBytes = memoryStream.ToArray();
            memoryStream.Close();
            cryptoStream.Close();
            return Convert.ToBase64String(cipherTextBytes);
        }

        public static string Decrypt(string plainText)
        {
            return Decrypt(plainText, ValidationKey);
        }
        private static string Decrypt(string cipherText, string passPhrase)
        {
            byte[] initVectorBytes = Encoding.ASCII.GetBytes(initVector);
            byte[] cipherTextBytes = Convert.FromBase64String(cipherText);
            var password = new PasswordDeriveBytes(passPhrase, null);
            byte[] keyBytes = password.GetBytes(keysize/8);
            var symmetricKey = new RijndaelManaged();
            symmetricKey.Mode = CipherMode.CBC;
            ICryptoTransform decryptor = symmetricKey.CreateDecryptor(keyBytes, initVectorBytes);
            var memoryStream = new MemoryStream(cipherTextBytes);
            var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read);
            var plainTextBytes = new byte[cipherTextBytes.Length];
            int decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
            memoryStream.Close();
            cryptoStream.Close();
            return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
        }
    }
}

Compression Utility

This is my favourite compression utility, supersimple & good performances

using System;
using System.IO;
using System.IO.Compression;
using System.Runtime.Serialization.Formatters.Binary;

namespace SdbBackbone.Compression
{
    public class CompressionUtility
    {
        public static string CompressValue(string input)
        {
            var bf = new BinaryFormatter();

            var ms = new MemoryStream();

            bf.Serialize(ms, input);

            byte[] inbyt = ms.ToArray();

            var objStream = new MemoryStream();

            var objZS = new DeflateStream(objStream, CompressionMode.Compress);

            objZS.Write(inbyt, 0, inbyt.Length);

            objZS.Flush();

            objZS.Close();

            byte[] b = objStream.ToArray();

            return Convert.ToBase64String(b);
        }

        public static string DecompressValue(string input)
        {
            byte[] bytCook = Convert.FromBase64String(input);

            var inMs = new MemoryStream(bytCook);

            inMs.Seek(0, 0);

            var zipStream = new DeflateStream(inMs,
                                              CompressionMode.Decompress, true);

            byte[] outByt = ReadFullStream(zipStream);

            zipStream.Flush();

            zipStream.Close();

            var outMs = new MemoryStream(outByt);

            outMs.Seek(0, 0);

            var bf = new BinaryFormatter();

            object retval = bf.Deserialize(outMs, null);

            string returnValue = retval.ToString();

            return returnValue;
        }

        private static byte[] ReadFullStream(Stream stream)
        {
            var buffer = new byte[32768];

            using (var ms = new MemoryStream())
            {
                while (true)
                {
                    int read = stream.Read(buffer, 0, buffer.Length);

                    if (read &lt;= 0)

                        return ms.ToArray();

                    ms.Write(buffer, 0, read);
                }
            }
        }
    }
}

CacheProvider factory

I have written a simple class to abstract all the caching paradigms in a managed world….

this is the interface:

namespace SdbBackbone.Caching
{
    public interface ICacheProvider
    {
        void AddItem(string key, object obj);
        void AddItem(string key, object obj, int minutesDuration);
        void AddApplicationItem(string key, object obj);
        object ReadItem(string key);
        T Get<T>(string key, Func<T> function, int minutesDuration);
        void Clear();
    }
}

This is the real implementation

using System;
using System.Collections;
using System.Collections.Generic;
using System.Web;
using System.Web.Caching;

namespace SdbBackbone.Caching
{
    public class CacheProvider : ICacheProvider
    {
        private const int DefaultCacheDurationInMinutes = 1;

        private static Cache Cache { get { return HttpRuntime.Cache; } }
        private int _applicationCacheDuration = -1;

        private int ApplicationCacheDuration
        {
            get
            {
                if (_applicationCacheDuration == -1)
                {

                    _applicationCacheDuration = 5;
                }

                return _applicationCacheDuration;
            }
        }

        public void AddItem(string key, object obj)
        {
            AddItem(key, obj, DefaultCacheDurationInMinutes);
        }

        public object ReadItem(string key)
        {
            return Cache.Get(key);
        }

        public void AddItem(string key, object obj, int minutesDuration)
        {
            Cache.Add(key, obj, null, DateTime.Now.AddMinutes(minutesDuration), Cache.NoSlidingExpiration, CacheItemPriority.High, null);
        }

        public void AddApplicationItem(string key, object obj)
        {
            AddItem(key, obj, ApplicationCacheDuration);
        }

        public T Get<T>(string key, Func<T> function, int minutesDuration) 
        {
            if (ReadItem(key) != null)
                return (T)ReadItem(key);

            var item = function();
            AddItem(key, item, minutesDuration);
            return item;
        }

        public void Clear()
        {
            var keys = new List<string>();

            // retrieve application Cache enumerator
            IDictionaryEnumerator enumerator = Cache.GetEnumerator();

            // copy all keys that currently exist in Cache
            while (enumerator.MoveNext())
            {
                keys.Add(enumerator.Key.ToString());
            }

            // delete every key from cache
            foreach (var t in keys)
            {
                Cache.Remove(t);
            }
        }
    }
}

and this is a possible fake for testing purpose

using System;
using System.Collections.Generic;

namespace SdbBackbone.Caching
{
    public class FakeCache : ICacheProvider
    {
        private readonly Dictionary<string, object> _cache = new Dictionary<string, object>(); 
        
        public void AddItem(string key, object obj)
        {
            if(_cache.ContainsKey(key))
                return;
            
            _cache.Add(key, obj);    
        }

        public void AddItem(string key, object obj, int minutesDuration)
        {
            AddItem(key, obj);
        }

        public object ReadItem(string key)
        {
            return (_cache.ContainsKey(key)) ? _cache[key] : null;
        }

        public void AddApplicationItem(string key, object obj)
        {
            AddItem(key, obj);
        }

        public T Get<T>(string key, Func<T> function, int minutesDuration)
        {
            return function();
        }

        public void Clear()
        {
            foreach (var o in _cache)
            {
                _cache.Remove(o.Key);
            }
        }
    }
}