WebAPI 2 native support in Sitecore 8.2

webapi-pic-300x300-2-2

Here we go with an other episode of the WebAPI and Sitecore saga…

As you probably know each version of Sitecore has managed WebAPI in a different way….

https://sitecorecommerce.wordpress.com/2014/11/30/webapi-attribute-routing-is-not-working-with-sitecore-7-5/

http://kamsar.net/index.php/2014/05/using-web-api-2-attribute-routing-with-sitecore/

Within 8.2 finally Sitecore is officially supporting WebApi NATIVELY

So what do you need to do to write your first WebAPI Controller and start using it?

very simple:

public class MyCustomController : Controller
{
   public ActionResult Test()
   {
      return Content("This is a test");
    }
}

and this is the Url where you can access your controller

Url: http://yourInstanceName/api/sitecore/MyCustom/test

public class MyOtherCustomController : Controller
{
   public ActionResult OtherTest(string mycustomId)
   {
      return Content("This is a test with params" + mycustomId);
    }
}

Url: http://yourInstanceName/api/sitecore/MyOtherCustom/OtherTest/thisIsMyParam

will return “This is a test with params thisIsMyParam”

Happy Sitecore and happy webApi to everybody!

Advertisements

LifestyleTransient, CastleWindsor, avoid memory leak…

Before starting this post, I have to say that I am not an huge expert of CastleWindsor & my knowledge on garbage collection and memory management is limited, if you think that I am saying something wrong, please leave a comment or point to an useful link.

Let’s say that you are coding a pipeline component and you want to make it easy to test and isolate the dependency and most of the logic for your pipeline component is handled in an external class….

let’s say that your class was installed as LifestyleTransient

The easiest way to use it in your pipeline component would be using the service locator pattern:

var classInstance = DIContainer.Resolve<IYourInterface>()

but you have to be aware that following this route, nobody will dispose for you the memory…. initially I thought that the GarbageCollector would do the job, but talking with some CastleWindsor expert, he explained me that your DIContainer has a reference to your class and it won’t clean it up when you need memory since it is still referenced by the Container object….

The only solution that I have been suggested is to Release your object explicitally in your pipeline component otherwise you will have a good chance to get a memory leak (obviously it depend on the size in memory that your class take and the traffic of your website…)

DIContainer.Release(classInstance);

Installer.Cs
container.Register(Component.For<ISomeClass>()
                    .ImplementedBy<SomeClass>()
                    .LifestyleTransient());

 
public class PipelineSample : HttpRequestProcessor
 {
  public override void Process(HttpRequestArgs args)
     {
       var classInstance = DIContainer.Resolve<ISomeClass>;
//Do Something
//Do not forget at the end of your method
DIContainer.Release(classInstance); 
      }
 }

The issue that I have faced is related to the ServiceLocator pattern and it does not affect the ControllerFactory at all since the controller factory dispose all the dependency passed in the constructor.

Most of developers are using Sitecore & CastleWindsor mostly for histoical reasons, than for a specific reason since from Sitecore 8, DI is completely decoupled from Sitecore and Glass but I would expect that also the other DI would be affected from the same issue if they implement the same concept of lifestyle transient…

An other possible solution to avoid this issue is install the components without using LifestyleTransient but using LifestylePerWebRequest assuming that your code would work reusing the same class multiple time per the request…
 

Custom RSS Atom Feed in a Sitecore Application

I had the necessity to customize the order and the number of items provided in the RSS feed generated within Sitecore and I have written the following code…

I am not particularly proud of it, but there are scenarios where this is the easiest solution to fix a production issue….

You could also reuse the following code to expose a custom RSS endpoint within your Sitecore web site

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Sitecore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Http;
using Sitecore.Data.Items;
using System.Xml;
using System.ServiceModel.Syndication;
using System.Net.Http;
using System.IO;
using System.Net; 

public class CustomRSSController : ApiController
  {
     [HttpGet]
public HttpResponseMessage GetNews()
     {
     string url = "http://yourWebSite.com/rss" ;
      XmlReader reader = XmlReader.Create(url);
      SyndicationFeed feed = SyndicationFeed.Load(reader);
      reader.Close();

      var output = new MemoryStream();
      string xml;

      feed.Items = feed.Items.OrderByDescending(x=>x.PublishDate).Take(5);

      var formatter = new Rss20FeedFormatter(feed);
      var xws = new XmlWriterSettings { Encoding = Encoding.UTF8 };
            using (var xmlWriter = XmlWriter.Create(output, xws))
            {
                formatter.WriteTo(xmlWriter);
                xmlWriter.Flush();
            }
            using (var sr = new StreamReader(output))
            {
                output.Position = 0;
                xml = sr.ReadToEnd();
                sr.Close();
            }

 var response = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(xml, Encoding .UTF8, "application/atom+xml") };
            return response;        
       }

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);
        }
    }
}