Thursday, 12 January 2017

Sitecore -Getting IIS error pages outside the server instead of custom page not found/error page


Recently we faced an issue where custom page redirect on page not found/access denied was working fine inside the server but was showing IIS pages outside the server




One of my colleague Sufal came up with the fix for this which is as below

In web.config  - Add  <httpErrors errorMode="Detailed" /> just above </system.webServer> node.

or you can do the same directly in IIS -> Select your site -> error pages -> Edit feature settting -> Select detailed errors








Sunday, 8 January 2017

Sitecore CD server went down - Exception- Tracker.Current is not initialized


Day before yesterday our one of the CD server went down with plenty of below exceptions


2108 03:18:13 ERROR Cannot create tracker.
Exception: System.IndexOutOfRangeException
Message: Index was outside the bounds of the array.
Source: mscorlib
   at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
   at Sitecore.Analytics.Model.Framework.ModelFactory.GetConcreteType(Type elementType)
   at Sitecore.Analytics.Model.Framework.ModelFactory.CreateFacet(Type facetType)
   at Sitecore.Analytics.Model.Framework.Faceted.AddFacet(String name, Type type)
   at Sitecore.Analytics.Model.Framework.ModelFactory.CreateContact(ID id, IReadOnlyDictionary`2 facets)
   at Sitecore.Analytics.Data.ContactFactory.Create(ID id)
   at Sitecore.Analytics.Data.ContactRepository.CreateContact(Guid id)
   at Sitecore.Analytics.Pipelines.EnsureSessionContext.LoadContact.Process(InitializeTrackerArgs args)
   at (Object , Object[] )
   at Sitecore.Pipelines.CorePipeline.Run(PipelineArgs args)
   at Sitecore.Analytics.DefaultTracker.EnsureSessionContext()
   at Sitecore.Analytics.Pipelines.CreateTracker.GetTracker.Process(CreateTrackerArgs args)
   at (Object , Object[] )
   at Sitecore.Pipelines.CorePipeline.Run(PipelineArgs args)
   at Sitecore.Analytics.Tracker.Initialize()

We did an iisreset which fixed the issue. We also reported the incident to Sitecore support to find the root cause . They confirmed it as a sitecore knwon issue and asked us to apply the patch following the below article

 https://kb.sitecore.net/articles/032518

The issue is related to the following method:

Sitecore.Analytics.Model.Framework.ModelFactory.GetConcreteType(Type elementType)
The above method use the "private static readonly IDictionary<Type, Type> typeMap;" internal class field which is initialized in static constructor as "Dictionary" collection:
static ModelFactory()
{
    typeMap = new Dictionary<Type, Type>();
}
However, since the above class is used from different threads the type of the collection, the patch replaces it with ConcurrentDictionary for correct threads synchronization.

Monday, 2 January 2017

Sitecore Page editor keep on loading- Script error in require.js Experienceeditor.js not found

I recently faced this issue where my page editor was keep on loading. I found a JS error in console which says "Script error in require.js" , Experienceeditor.js not found. I had to create a temporary Experienceeditor.js file as below and had to place it in /sitecore/shell/client/Sitecore/ExperienceEditor folder.

define(["sitecore"], function (Sitecore) {
    return Sitecore.ExperienceEditor;
});

More details why this error happens and the root cause can be found in below post

http://www.awareweb.com/awareblog/11-16-15-retrofitsitecore8scripts

Happy New Year Everyone :)

Tuesday, 27 December 2016

Sitecore Versioned media file loading from en version


Recently we had an issue where versioned media items were always getting loaded from en language version instead of current context language(Sitecore 8.0) . We were unable to find the root cause of this and finally have to come up with a work around.


1. Create a custom media provider . Append language in the media url if media item template is versioned.


namespace MediaHandler
{
    public class CustomMediaProvider:MediaProvider
    {
        public override string GetMediaUrl(MediaItem item, MediaUrlOptions options)
        {
         
            Assert.ArgumentNotNull(item, "item");
            Assert.ArgumentNotNull(options, "options");
            bool flag = options.Thumbnail || this.HasMediaContent(item);
            bool flag2 = true;
            if (!flag && item.InnerItem.Paths.Path.Length > 0)
            {
                if (!options.LowercaseUrls)
                {
                    return item.InnerItem.Paths.Path;
                }
                return item.InnerItem.Paths.Path.ToLowerInvariant();
            }
            else if (options.UseDefaultIcon && !flag)
            {
                if (!options.LowercaseUrls)
                {
                    return Themes.MapTheme(Settings.DefaultIcon);
                }
                return Themes.MapTheme(Settings.DefaultIcon).ToLowerInvariant();
            }
            else
            {
                Assert.IsTrue(this.Config.MediaPrefixes[0].Length > 0, "media prefixes are not configured properly.");
                string text = this.MediaLinkPrefix;
                if (options.AbsolutePath)
                {
                    text = options.VirtualFolder + text;
                }
                else if (text.StartsWith("/", StringComparison.InvariantCulture))
                {
                    text = StringUtil.Mid(text, 1);
                }
                string text2 = MainUtil.EncodePath(text, '/');
                if (options.ToString().Length > 1 && !string.IsNullOrEmpty(StringUtil.ExtractParameter("la", options.ToString())))
                {
                    flag2 = false;
                }
                Item item2 = Context.Database.GetItem(item.InnerItem.TemplateID);
                if (flag2 && item2 != null && item2.Paths.FullPath.ToLower().Contains("/versioned/"))
                {
                    text2 = "/" + Context.Language.CultureInfo.ToString() + text2;
                }
                if (options.AlwaysIncludeServerUrl)
                {
                    text2 = FileUtil.MakePath(string.IsNullOrEmpty(options.MediaLinkServerUrl) ? WebUtil.GetServerUrl() : options.MediaLinkServerUrl, text2, '/');
                }
                string text3 = StringUtil.EnsurePrefix('.', StringUtil.GetString(new string[]
                {
                    options.RequestExtension,
                    item.Extension,
                    "ashx"
                }));
                string text4 = options.ToString();
                if (text4.Length > 0)
                {
                    text3 = text3 + "?" + text4;
                }
                string text5 = "/sitecore/media library/";
                string path = item.InnerItem.Paths.Path;
                string str = MainUtil.EncodePath((!options.UseItemPath || !path.StartsWith(text5, StringComparison.OrdinalIgnoreCase)) ? item.ID.ToShortID().ToString() : StringUtil.Mid(path, text5.Length), '/');
                string text6 = text2 + str + (options.IncludeExtension ? text3 : string.Empty);
                if (!options.LowercaseUrls)
                {
                    return text6;
                }
                return text6.ToLowerInvariant();
            }
        }
    }
     
}


2. Replace Sitecore media provider with your custom media provider

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <mediaLibrary>
<mediaProvider>
 <patch:attribute name="type">MediaHandler.CustomMediaProvider, [Assembly Name]</patch:attribute>
  </mediaProvider>
 </mediaLibrary>
  </sitecore>
</configuration>


With above solution, Versioned media url will include language locale in its url and will always get loaded from current context language. 

Sitecore - Enable preview mode for Anonymous user


Recently we had a requirement to enable preview mode for anonymous user so that content authors can share the preview url to others for review. Since it was just for review purpose we wanted them to see the preview of the page without logging into sitecore. Of course there is another way to just point CM server database to master but we did not want to change our architecture for this. We contacted Sitecore support(support id 471776) and they helped us in achieving this. Please note the fix is for Sitecore 8.0 version but should work with other version as well.

Configuration- you need to patch your configuration in such a way so that your class will come as below in showconfig page.



<httpRequestBegin>
… … …
<processor type="Sitecore.Pipelines.HttpRequest.UserResolver, Sitecore.Kernel"/>
<processor type="[Your namespace].AllowAnonymousPreview, YourAssemblyName"/>
<processor type="Sitecore.Pipelines.HttpRequest.DatabaseResolver, Sitecore.Kernel"/>
</httpRequestBegin>


Class code :- 

namespace [Namespace]
{
    public class AllowAnonymousPreview : HttpRequestProcessor
    {
        public override void Process(HttpRequestArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            var activeUser = AuthenticationManager.GetActiveUser();
            Assert.IsNotNull(activeUser, "User cannot be null.");
            var userIsExtranetAnonymous = activeUser.Name == "extranet\\Anonymous";
            var userIsSitecoreAnonymous = activeUser.Name == "sitecore\\Anonymous";
            if (!userIsExtranetAnonymous && !userIsSitecoreAnonymous)
            {
                return;
            }
            var isRibbonRequest = args.Url != null && args.Url.ItemPath == "/sitecore/content/home/applications/webedit/webeditribbon";
            if (Sitecore.Context.PageMode.IsPreview || isRibbonRequest)
            {
                PerformAutoLogin();
            }
         
        }

        private void PerformAutoLogin()
        {
            string userName = "extranet\\Preview Anonymous User";
            AuthenticationManager.Login(userName);
            string ticket = Sitecore.Web.Authentication.TicketManager.CreateTicket(userName, @"/sitecore/shell");
            HttpContext current = HttpContext.Current;
            if (current != null)
            {
                HttpCookie cookie = new HttpCookie(Sitecore.Web.Authentication.TicketManager.CookieName, ticket)
                {
                    HttpOnly = true
                };
                current.Response.AppendCookie(cookie);
            }
        }

    }
}

extranet\\Preview Anonymous User is a custom user which we have created for preview access and have assigned "sitecore\Sitecore Minimal Page Editor" role to him. We have revoked write access for this user and tested and confirmed that user should not be able to go back to edit mode and edit the page with this access.


Happy Coding :)