decodeURIComponent() equivalent in C#

The Javascript decodeURIComponent()  function decodes a string that was URI encoded with the encodeURIComponent() Javascript function. If you wish to encode a string in C# that can be decoded with decodeURIComponent you have to use the EscapeDataString function:

System.Uri.EscapeDataString("This is my text");

The escaping an unescaping of strings can for example be used to add HTML strings to a Javascript array from C#. Adding HTML strings to an array using the ClientScript.RegisterArrayDeclaration() function will produce an error when the page is executed. But if you encode/decode the strings, the function will succeed:

protected void Page_Load(object sender, EventArgs e)
{
  StringBuilder sb = new StringBuilder();
  sb.AppendFormat("\"{0}\"", Uri.EscapeDataString("
<h1>This is my first heading</h1>
"));
  sb.AppendFormat("\"{0}\"", Uri.EscapeDataString("
<h2>This is my second heading</h2>
"));
  sb.AppendFormat("\"{0}\"", Uri.EscapeDataString("
<h3>This is my third heading</h3>
"));
  Page.ClientScript.RegisterArrayDeclaration("myArray", sb.ToString());
}

This function appends 3 HTML strings to an array and adds the array to the page. The strings are now accessible from Javascript. This pseudo function will take an ID of a DIV tag and the index of the array and output the text from the above created array in the DIV tag:

<script language="javascript" type="text/javascript">
  function ShowDescription(divID, index)
  {
    var divTag = document.getElementById(divID);
    divTag.innerHTML = decodeURIComponent( myArray[index] );
  }
</script>

Streaming objects into a cookie

Cookies are text strings that is used within the browser memory. In reality, cookies can store anything, as long as you follow these limitations:

  • Do not store more than 20 cookies per domain.
  • Do not store more than 4096 bytes per cookie.

Microsoft states that their browser can store at least 20 cookies per domain and at least 4096 bytes per cookies,  but the official max size is 4k, so you should stick to this.

With this in mind, my proposal to stream objects into a cookie is probably not such a good idea. There is some overhead when streaming classes in C#, as the class definitions are also stored. However, if you observe the limits as described above, you should be fine. The following piece of code saved me a lot of time, that’s for sure.

The first function will store a made-up class (called MyClass) into a cookie:

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

public void Store(MyClass myClass)
{
  HttpCookie cookie = new HttpCookie("myCookie")
  {
    // Set the expiry date of the cookie to 15 years
    Expires = DateTime.Now.AddYears(15)
  };
  Stream myStream = new MemoryStream();
  try
  {
    // Create a binary formatter and serialize the
    // myClass into the memorystream
    IFormatter formatter = new BinaryFormatter();
    formatter.Serialize(myStream, myClass);
    // Go to the beginning of the stream and
    // fill a byte array with the contents of the
    // memory stream
    myStream.Seek(0, SeekOrigin.Begin);
    byte[] buffer = new byte[myStream.Length];
    myStream.Read(buffer, 0, (int)myStream.Length);
    // Store the buffer as a base64 string in the cookie
    cookie.Value = Convert.ToBase64String(buffer);
    // Add the cookie to the current http context
    HttpContext.Current.Response.Cookies.Add(cookie);
  }
  finally
  {
    // ... and remember to close the stream
    myStream.Close();
  }
}

One thing to remember is that cookies store strings, not binary data. That’s why I have to convert the stream to a byte array, and convert the byte array to a base 64 string. This conversion introduces another overhead as base64 strings converts 3 bytes into 4 ASCII chars.

Now lets restore my object from the cookie:

public MyClass Restore()
{
  // Always remember to check that the cookie is not empty
  HttpCookie cookie = HttpContext.Current.Request.Cookies["myCookie"];
  if (cookie != null)
  {
    // Convert the base64 string into a byte array
    byte[] buffer = Convert.FromBase64String(cookie.Value);
    // Create a memory stream from the byte array
    Stream myStream = new MemoryStream(buffer);
    try
    {
      // Create a binary formatter and deserialize the
      // contents of the memory stream into MyClass
      IFormatter formatter = new BinaryFormatter();
      MyClass streamedClass = (MyClass)formatter.Deserialize(myStream);
      return streamedClass;
    }
    finally
    {
      // ... and as always, close the stream
      myStream.Close();
    }
  }
  return null;
}

If you replace the MyClass with an interface or a base class, you will have the flexibility to store any class either implementing the interface or inheriting from the base class.

And if you need help on how to remove cookies, you can read this article on how to add and remove cookies.

Generic lists and Predicates

Recently I was working with a generic list containing my own Favorite class. My Favorite class contains an url and a title:

using System.Collections.Generic;

public class Favorites
{
  private List<Favorite> _favoriteList = new List<Favorite>();
}

One of the functions on my list is to remove all Favorites with a specific url. I quickly jumped to the help file and found the RemoveAll() function which takes a Predicate. A predicate is a generic delegate that takes the same type as the List<> and returns true if the parameter matches a statement:

public void DinosaurExample()
{
  List<string> dinosaurs = new List<string>();

  dinosaurs.Add("Compsognathus");
  dinosaurs.Add("Amargasaurus");
  dinosaurs.Add("Oviraptor");
  dinosaurs.RemoveAll(EndsWithSaurus));
}

private static bool EndsWithSaurus(String s)
{
  if ((s.Length > 5) &&
     (s.Substring(s.Length - 6).ToLower() == "saurus"))
  {
    return true;
  }
  else
  {
    return false;
  }
}

 The EndsWithSaurus(String s) is the predicate and the function returns true if the matching string ends with “saurus”, removing “Amargasaurus” from the list.

But hey! What if I don’t have a hard-coded match in my code (I rarely have)? The predicate cannot take any parameters. Also, it’s a Static member, polluting my class.

The solution is to create a “predicate class” that takes the expression to match as a parameter:

internal class FavoriteUrlMatch
{
  private string _url;
 
  public FavoriteUrlMatch(string url)
  {
    _url = url;
  }
   
  public bool Match(Favorite fav)
  {
    return fav.Url == _url;
  }
}

Then I can invoke my FavoriteUrlMatch class from my Favorites class:

public void Delete(string url)
{
  FavoriteUrlMatch match = new FavoriteUrlMatch(url);
  _favoriteList.RemoveAll(match.Match);
}

I am not very proud of this solution, but I cannot find a better one.

Create an xslExtension for Lix for Sitecore

Pentia A/S‘ Lix for Sitecore module is a readability checker for Sitecore 5 and Sitecore 6 based on the Lix readability algorithm which is widely used for scandinavian languages (Danish, Swedish, Norwegian, …) and for Dutch. Having a low Lix is one of the criterias in the Bedst på Nettet benchmark evaluation.

The Lix module is a Sitecore shell extension allows you to see the Lix of your text in the Rich Text Editor. It is also possible to apply Lix for Sitecore as a field validator (Sitecore 6 only):

Lix for Sitecore embedded as a Shell Extension

Lix for Sitecore embedded as a Shell Extension

But what if you want to show the Lix number on your website? You will have to write your own Xsl extension for that. It’s not that hard though. I’ll show you an example where I serialize the calculation and uses it as an Xsl extension.

The lix calculation is returned as an ICalculationResult interface. As you might know, interfaces cannot be serialized in .net, so I’ll have to create a concrete class for the serialization. In this example I’ll call the class MyResult:

using System.Xml.XPath;
using PT.Lix.Core;
using System.Xml;
using System.Xml.Serialization;
using System.IO;

namespace MyNamespace
{
  public class MyResult
  {
    public double AverageSentenceLength { get; set; }
    public int LixNumber { get; set; }
    public int NumberOfLongWords { get; set; }
    public int NumberOfSentences { get; set; }
    public int NumberOfWords { get; set; }
    public double PercentageLongWords { get; set; }
    public string Text { get; set; }

    public MyResult() { }

    public MyResult(ICalculationResult result)
    {
      AverageSentenceLength = result.AverageSentenceLength;
      LixNumber = result.LixNumber;
      NumberOfLongWords = result.NumberOfLongWords;
      NumberOfSentences = result.NumberOfSentences;
      NumberOfWords = result.NumberOfWords;
      PercentageLongWords = result.PercentageLongWords;
      Text = result.Text;
    }
  }
}

The MyResult class takes the ICalculationResult as parameter to a constructor and I construct a new class using the same fields as in the interface. Also note the parameterless constructor that will alow me to XML serialize the class.

With my new class I can then make a class with a function that returns a XPathNodeIterator with the contents from my XML serialized MyResult class:

public class XsltHelper
{
  public XPathNodeIterator Calculate(string text)
  {
    Calculator calculator = new Calculator(Sitecore.Context.Database);
    ICalculationResult result = calculator.Calculate(text);
    MyResult myResult = new MyResult(result);

    XmlDocument doc = new XmlDocument();
    MemoryStream stream = new MemoryStream();
    try
    {
      XmlSerializer serializer = new XmlSerializer(typeof(MyResult));
      serializer.Serialize(stream, myResult);
      stream.Seek(0, SeekOrigin.Begin);
      doc.Load(stream);
    }
    finally
    {
      stream.Close();
    }
    return doc.CreateNavigator().Select(".");
  }
}

The next step is to apply my class as an XslExtention in web.config:

<xslExtensions>
  ...
  <extension mode="on" type="MyNamespace.XsltHelper, MyAssembly" namespace="http://www.sitecore.net/lixutil" singleInstance="true" />
</xslExtensions>

I can then add my extension to any XSLT:

<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:sc="http://www.sitecore.net/sc"
  xmlns:dot="http://www.sitecore.net/dot"
  xmlns:sql="http://www.sitecore.net/sql"
  xmlns:lix="http://www.sitecore.net/lixutil"
  exclude-result-prefixes="dot sc sql lix">

And the extension is ready to be used:

<!-- entry point -->
<xsl:template match="*">
  <sc:dot />
 
<h1><sc:text field="title"/></h1>
 
<div><sc:text field="text"/></div>
  <!-- calculate the lix for the field "text" -->
  <xsl:variable name="lixResult" select="lix:Calculate(sc:field('text',.))" />
 
    <!-- Outout the lix value: -->
    Lix: <xsl:value-of select="$lixResult/MyResult/LixNumber"/>
 
</xsl:template>

My page will now display the Lix value of the current text on the current page. For further information and pricing of the Lix for Sitecore module, please contact Pentia A/S.

Sitecore.Links.LinkManager and the context

A new feature in Sitecore 6 is the LinkManager. Previously, in Sitecore 5, in order to get a friendly url (an URL with the .aspx extension) you would write the following:

Item item;
string path = item.Paths.GetFriendlyUrl(false);

In Sitecore 6, the GetFriendlyUrl() is deprecated. Instead we are encouraged to use the Sitecore.Links.LinkManager. The same piece of code would look like this:

Item item;
string path = Sitecore.Links.LinkManager.GetItemUrl(item);

The LinkManager provides us with a lot of features that the GetFriendlyUrl() did not. But it also introduces some pitfalls. The LinkManager runs in a context that is not necessarily the same as the Item you are getting the link from, nor the context you wish to run in. Let me explain. Imagine you write a Sitecore shell extension that returns the path to an item. The Item is grabbed from the web database:

Sitecore.Data.Database database = Sitecore.Data.Database.GetDatabase("web");
Sitecore.Data.Items.Item item = database.GetItem("/sitecore/content/home");
string path = item.Paths.FullPath;

The path to the item is “/sitecore/content/home”.

Now, lets get the item’s URL:

string path = Sitecore.Links.LinkManager.GetItemUrl(item)

This returns the item URL for the current Site as defined in the LinkManager.GetDefaultUrlOptions().Site. If my code runs in the modules_shell site, the url is /en/sitecore modules/shell.aspx. Is this really the url of my item? Yes, seen from the modules_shell site it is, but not as seen from the website site.

So my Item used the website site, but my LinkManager uses the modules_shell site. The URL i wanted is the one from the website site, but the URL i requested is from the modules_shell site.

What can I do about this? The URL is constructed by prefixing the current site’s virtual path (which for the modules_shell site is /sitecore modules/shell) to the actual path. The easy solution would be to remove the virtual path from the URL:

string virtualFolder = LinkManager.GetDefaultUrlOptions().Site.VirtualFolder.TrimEnd('/');
Response.Write(LinkManager.GetItemUrl(item).Replace(virtualFolder, ""));

This produces the URL /en.aspx, which is correct for my website site, since this site has no virtual path.

Another, and probably better, solution would be to change the context before getting the link:

string oldSiteName = Sitecore.Context.GetSiteName();
Sitecore.Context.SetActiveSite("website");
Response.Write(LinkManager.GetItemUrl(item));
Sitecore.Context.SetActiveSite(oldSiteName);

This produces the URL “/”, which is correct for the home page of my website.

Get local path from UNC path

This is not a Sitecore issue. It’s not even web related, but it was a problem I stumbled upon when working on the Continuous Integration project that we in Pentia use. In order to simplify some of the scripts I would like to resolve the local path from a UNC path. For example if I have a UNC path to a network drive called \\INTEGRATIONSERVER\projects I would like to know what the local URL for that server is, for example d:\projects\.

I looked at the internet without finding the exact solution. I found some VB sripting examples, and I decided to rewrite these scripts using C#.

This function will take a UNC path (for example \\server\share or \\server\c$\folder and return the local path (for example c:\share or c:\folder).

using System.Management;

public static string GetPath(string uncPath)
{
  try
  {
    // remove the "\\" from the UNC path and split the path
    uncPath = uncPath.Replace(@"\\", "");
    string[] uncParts = uncPath.Split(new char[] {'\\'}, StringSplitOptions.RemoveEmptyEntries);
    if (uncParts.Length < 2)
      return "[UNRESOLVED UNC PATH: " + uncPath + "]";
    // Get a connection to the server as found in the UNC path
    ManagementScope scope = new ManagementScope(@"\\" + uncParts[0] + @"\root\cimv2");
    // Query the server for the share name
    SelectQuery query = new SelectQuery("Select * From Win32_Share Where Name = '" + uncParts[1] + "'");
    ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query);
 
    // Get the path
    string path = string.Empty;
    foreach (ManagementObject obj in searcher.Get())
    {
      path = obj["path"].ToString();
    }

    // Append any additional folders to the local path name
    if (uncParts.Length > 2)
    {
      for (int i = 2; i < uncParts.Length; i++)
        path = path.EndsWith(@"\") ? path + uncParts[i] : path + @"\" + uncParts[i];
    }

    return path;
  }
  catch (Exception ex)
  {
    return "[ERROR RESOLVING UNC PATH: " + uncPath + ": "+ex.Message+"]";
  }
}

The function uses the ManagementObjectSearcher to search for shares on the network server. If you do not have read access to this server, you will need to log in using different credentials. Replace the line with the ManagementScope with the following lines:

ConnectionOptions options = new ConnectionOptions();
options.Username = "username";
options.Password = "password";
ManagementScope scope = new ManagementScope(@"\\" + uncParts[0] + @"\root\cimv2", options);

The function is not perfect as it searches for shares only. If you give an UNC path to a folder that does not exist, the function will return the local path to that folder if the server and share exists. For example, let’s say that the folder \\SERVER\c$\myfolder does not exist, the fucntion still returns c:\myfolder.

Could not post to my webservice

Have you noticed that when calling a web service from JavaScript using POST (or GET for that matter), the call will work fine on your local development server, but when the thing is put into production it will fail?

This is because HTTP GET and HTTP POST are disabled by default in .NET (from version 1.1).
This article (http://support.microsoft.com:80/default.aspx?scid=kb;en-us;819267) explains the situation.

The symptoms are as follows:

  • The statuscode of the XMLHTTP call is “undefined”.
  • The statustext of the XMLHTTP call is “Internal Server Error”.
  • You get the following exception: Request format is unrecognized for URL unexpectedly ending in ‘/MyFunction‘.

In order to fix this issue you will have to enable HTTP POST (and maybe also HTTP GET) by adding a declaration to the web.config. I prefer to add a local web.config along with my web service. Simply put a web.config in the same folder as your .asmx file with the following contents:

<?xml version="1.0"?>
<configuration>
  <appSettings/>
  <connectionStrings/>
  <system.web>
    <webServices>
<protocols>
        <add name="HttpPost"/>
      </protocols>
    </webServices>
    <compilation debug="false"></compilation>
  </system.web>
  <system.codedom>
  </system.codedom>
  <!--
    The system.webServer section is required for running ASP.NET AJAX under Internet
    Information Services 7.0.  It is not necessary for previous version of IIS.
  -->
  <system.webServer>
  </system.webServer>
</configuration>

This configuration file allows HTTP POST calls to be executed on all services installed in the same folder. To allo HTTP GET calls, simply add HttpGet to the file as well:

<configuration>
  <system.web>
    <webServices>
<protocols>
        <add name="HttpGet"/>
        <add name="HttpPost"/>
      </protocols>
    </webServices>
  </system.web>
</configuration>

You could also add the XML to the web site’s web.config, but this will allow all HTTP GET and HTTP POST calls to all services, and you might not want this.