Sitecore Adaptive Images – Huge Crawling Logs Problem

Recently we noticed that on our production environment the Crawling Log files were abnormally big – between 500 MB and 1 GB. After some digging we found out that they were just filling up with exceptions while crawling the media library. The exceptions look like this:


4576 10:48:07 WARN Could not compute value for ComputedIndexField: urllink for indexable: sitecore://master/{8EA15044-EE2C-41DB-81D6-0A9C42062814}?lang=en&ver=1
Exception: System.NullReferenceException
Message: Object reference not set to an instance of an object.
Source: Sitecore.Kernel
at Sitecore.Context.PageMode.get_IsNormal()
at adaptiveImages.AdaptiveImagesMediaProvider.GetMediaUrl(MediaItem item)
at Sitecore.ContentSearch.ComputedFields.UrlLink.ComputeFieldValue(IIndexable indexable)
at Sitecore.ContentSearch.LuceneProvider.LuceneDocumentBuilder.AddComputedIndexFields()

Considering the case that we have tons of media (over 2000 items) and languages (there is a separate exception for each language) we had some pretty serious index time performance issues and hard disk space problems.

After some debugging we found out that the Sitecore.Context.PageMode.IsNormal was throwing null reference. The huge problem was that there is no way to protect against null pointers here because you cannot check the Sitecore.Context and the Sitecore.Context.PageMode for nulls. The good part is that the PageMode.IsNormal method code is using the Sitecore.Context.Site (which is the actual null in this case) to check the PageMode.


public static bool IsNormal
{
    get
    {
        return Context.Site.DisplayMode == DisplayMode.Normal;
    }
}

With this information the fix became pretty trivial. As try-catch is pretty expensive operation – we just needed to check if the Context.Site is not null and the Context.Site.DisplayMode is normal. The fixes took place in the two GetMediaUrl overrides. Here is the code for the modified methods which were causing the exception.

  /// <summary>
        /// Gets a media URL.
        /// </summary>
        /// <param name="item">The media item.</param>
        /// <returns>
        /// The media URL.
        /// </returns>
        public override string GetMediaUrl(MediaItem item)
        {
            Assert.ArgumentNotNull(item, "item");

            // FIX FOR CHECKING THE PAGE MODE
            bool isNormal = Context.Site != null && Context.Site.DisplayMode == DisplayMode.Normal;

            //If media item is not an image or the page context is not normal, then return
            if (!IsImage(item) || !isNormal)
                return base.GetMediaUrl(item);

            MediaUrlOptions mediaUrlOptions = new MediaUrlOptions();

            return GetMediaUrl(item, mediaUrlOptions);
        }

        /// <summary>
        /// Gets the media URL.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <param name="mediaUrlOptions">The media URL options.</param>
        /// <returns></returns>
        public override string GetMediaUrl(MediaItem item, MediaUrlOptions mediaUrlOptions)
        {
            Assert.ArgumentNotNull(item, "item");
            Assert.ArgumentNotNull(mediaUrlOptions, "mediaUrlOptions");

            // FIX FOR CHECKING THE PAGE MODE
            bool isNormal = Context.Site != null && Context.Site.DisplayMode == DisplayMode.Normal;

            //If media item is not an image or the page context is not normal, then return
            if (!IsImage(item) || !isNormal || Context.Database == null || Context.Database.Name != _database)
                return base.GetMediaUrl(item, mediaUrlOptions);

            //If resolution cookie is not set
            if (!IsResolutionCookieSet())
            {
                //If mobileFirst is set to FALSE or user agent is identifying as a desktop, return with largest break-point resolution
                if (!_mobileFirst || IsDesktopBrowser())
                {
                    mediaUrlOptions.MaxWidth = GetLargestBreakpoint();
                    return base.GetMediaUrl(item, mediaUrlOptions);
                }
                //Return with mobile-first breakpoint (Smallest)
                mediaUrlOptions.MaxWidth = GetMobileFirstBreakpoint();
                return base.GetMediaUrl(item, mediaUrlOptions);
            }

            // If Max-width is not set or Max-width is greater than the selected break-point, then set the Max-width to the break-point
            if (mediaUrlOptions.MaxWidth == 0 || mediaUrlOptions.MaxWidth > GetScreenResolution())
                mediaUrlOptions.MaxWidth = GetScreenResolution();

            // If Max-width is not set and the 'maxWidth' setting is not empty, then set the Max-width property to the maxWidth
            if (mediaUrlOptions.MaxWidth == 0 && !string.IsNullOrEmpty(_maxWidth))
            {
                int maxWidth = 0;
                if (int.TryParse(_maxWidth, out maxWidth))
                {
                    // If pixel ratio is normal
                    if (GetCookiePixelDensity() == 1)
                        mediaUrlOptions.MaxWidth = maxWidth;
                    else
                        mediaUrlOptions.MaxWidth = maxWidth * GetCookiePixelDensity();
                }

            }

            return base.GetMediaUrl(item, mediaUrlOptions);
        }

Basically we are using an isNormal variable to check the Context.Site display mode instead of using the Context.PageMode.IsNormal property:


            bool isNormal = Context.Site != null && Context.Site.DisplayMode == DisplayMode.Normal;

By doing this we kept the module’s consistency and logic as we just added a null check for the Context.Site. After the fix our logs became around 2 kb again and our indexing speed came back to normal !

Happy Log Fixing ! 🙂

 

5 Reasons To Pick Active Commerce For Your E-Commerce Needs

Recently the guys at Active Commerce (Follow @activecommerce) provided a free development training for Sitecore MVPs led by Nick Wesselman (Follow @techphoria414). The guys there did an amazing job in implementing a really simple yet powerful E-Commerce solution for Sitecore, so I decided to write a blog post about why you should pick Active Commerce as your E-Commerce platform.

Reason 1 –  It is built on top of Sitecore with Sitecore ! 

One of the best things about Active Commerce is that it uses all the built-in Sitecore Capabilities for creating an E-Commerce solution. Active Commerce uses and extends the Sitecore Layout Engine by adding skinning capabilities (which will be covered in Reason 2). It uses and extends Sitecore Ecommerce Services (SES) for E-Commerce related things like product catalogs, shopping carts, etc. Also it ships with already build templates for common things like product, product variants, filters and so on.

Reason 2 – Shapeshifting (more known as Active Commerce Skinning System)

Active Commerce ships with already built frontend (which is in fact pretty good and responsive). The bad part about this is that our clients don`t want to be mainstream and they want their own brand “look and feel”. The good part about this is that the guys at Active Commerce know that ! The Skinning System is an extension of the existing Sitecore Layout Engine, which allows the developers to utilize the existing skin and just replace the parts which need modification (including styles, html templates, scripts, images etc.). So here is a brief overview of the frontend goodies Active Commerce utilizes to make our lives easier:

  1. Cassete for asset management, bundling, minification and bundling.
  2. LESS Preprocessor for styling – which means less development work to modify the existing styles thanks to the variables and mixins less provides.
  3. jQuery-tmpl for HTML Templating – which allows quick markup changes thanks to the dynamic generation of the HTML Templates.
  4. jQuery for javascript library – nothing to say here – even a frontend newbie like me is familiar with jQuery :).
  5. Special mobile-optimized templates and behaviours (Using the Sitecore Device Layer).

Reason 3 – Easy to Configure

As already stated in Reason 1 – Active Commerce is built to work seamlessly with Sitecore, which means that it utilizes the already existing Sitecore config patching strategy we are used to. If you want to extend or just modify something Active Commerce related there are patch files which are self-descriptive (and well commented :)). Of course Active Commerce is not configurable solely by configuration files, there is a pretty robust system for site configuration inside the Content Tree. From inside the content tree your editors will be able to configure everything starting from basic contact data of the company through tax calculators, default currencies, etc.

Reason 4 – Inversion of Commerce (or Control :)) and Major Extension Points

Active Commerce is easily extendable thanks to the fact that it uses Microsoft Unity as dependency injection container. Unity makes it easier for the developers to replace existing functionality, extend the existing components and add their own custom logic on top. From data modeling standpoint – Active Commerce uses Glass Mapper as ORM which makes it even easier to extend the models and create custom types for our own data models – the only thing necessary is to inherit from the ActiveCommerce.Products.Product class.

Reason 5 – Support

Having a good support team is one of the most important things when you want your product to be successful and in my experience I can honestly say the guys in the Active Commerce Support team are amazing. I saw nothing else but fast and proper responses when I had any questions.

Happy E-Commercing ! 🙂