tirsdag den 29. oktober 2013

Leaving Page Editor mode resulting in a 404 error.

There is a bug in Sitecore, that causes it to generate an excess of cookies when leaving Page Editor mode (clicking the Close button in the ribbon).

This causes browsers to behave weird, and Internet Explorer just displays a 404 error page.

The problem happens, if there is code on the page calling SiteContextFactory.GetSiteContext , which is used to get a SiteContext for a certain site.

For every time this function is called, a new cookies is set, so if you have logic in your solution that uses, you must cache the SiteContext objects you get from GetSiteContext, to work around this problem.

Due to a bug in one of our controls on a solution we where designing, a menu was looking up the SiteContext for every element in a menu, which caused it to fail.

If you want to see the proble, here is a step-by-step guide to how to trigger it - I haven't tested it on anything newer than Sitecore 6.5, but to my knowledge, the bug is still there.

How to trigger:

  1. Create an empty aspx page, and add the following code below to the Page_Load event.
  2. Add this aspx page as a layout in Sitecore, and assign it to an item.
  3. Open the page editor, go to the item, and then click Close on the ribbon.
Here is the code needed to trigger the bug - you would not normally do this, but it trigger the creation of way too many cookies:

            for (int i = 0; i < 300; i++)
            {
                foreach (string siteName in Sitecore.Sites.SiteContextFactory.GetSiteNames())
                {
                    Sitecore.Sites.SiteContextFactory.GetSiteContext(siteName);
                }
            }

Workaround:

Since quite alot of Sitecore's API uses SiteContext objects as parameters, we have to deal with them, which poses these problems when using solutions with multiple sites, that needs to be able to get links across.

However, here is a small code snippet that we are using, that works around this bug:

        internal class SiteContextWrapper : SiteContext
        {
            public SiteContextWrapper(SiteInfo siteInfo) : base(siteInfo, false)
            {
            }
        }

It simply extends the SiteContext class, causing it to be created without setting the cookie.
To get the SiteContext object, you simply class the GetSiteInfo function in SiteContextFactory, and use that to create a new SiteContextWrapper object, which can be cast to a SiteContext object.

mandag den 28. oktober 2013

Using Axes.GetDescendant vs. Axes.SelectSingleItem

I've come across an interresting problem, where people are using the SelectSingleItem function, when they could be using GetDescendant.

Here is some sample code to test the difference between those two performance wise:

            Stopwatch stopWatch = new Stopwatch();
            stopWatch.Start();
            
            for (int i = 0; i < 10; i++)
            {
                rootItem.Axes.GetDescendant("Test Article 2/Test Subarticle 1/Test Subsubarticle 1");
                rootItem.Axes.GetDescendant("Test Article 2/Test Subarticle 1/Test Subsubarticle 2");
                rootItem.Axes.GetDescendant("Test Article 2/Test Subarticle 1/Test Subsubarticle 3");
            }

            stopWatch.Stop();
            TimeSpan ts1 = stopWatch.Elapsed;

            stopWatch = new Stopwatch();
            stopWatch.Start();

            for (int i = 0; i < 10; i++)
            {
                rootItem.Axes.SelectSingleItem("Test Article 2/Test Subarticle 1/Test Subsubarticle 1");
                rootItem.Axes.SelectSingleItem("Test Article 2/Test Subarticle 1/Test Subsubarticle 2");
                rootItem.Axes.SelectSingleItem("Test Article 2/Test Subarticle 1/Test Subsubarticle 3");
            }

            stopWatch.Stop();
            TimeSpan ts2 = stopWatch.Elapsed;

            litResult.Text = string.Format("GetDescendant: {0} ms, SelectSingleItem: {1} ms.", ts1.TotalMilliseconds, ts2.TotalMilliseconds);

This results in:
GetDescendant: 9,2628 ms, SelectSingleItem: 22,5741 ms.
The numbers vary a bit from each run, but it averages out at SelectSingleItem being about 2.5x times slower than GetDescendant - now imagine using SelectSingleItem in a sublayout loaded on every page view, that cannot be cached...

So, why is there even a SelectSingleItem function on the Axes class, one might ask?

SelectSingleItem uses XPath to lookup the item, which also explains why it is slower than GetDescendant (which just crawles down the tree).

So, what to take away from this?

If you just need to look up an item below the current item, use GetDescendant - and if you need to use XPath, use SelectSingleItem.