5

Im am trying to mimic the functionality of using a custom item resolver. In Sitecore 7.2, I had a custom item resolver. It would look at the resolved item, and if it was of a specific Template, it would fall back to a different item.

In Sitecore 8.2, this doesnt work because in the PageContext GetItem method, it looks at the Context.Item and checks the ID against the mvc.GetPageItem result ID. If they are different, it resets the Context.Item.

So, I created a custom PageContext, and added my fallback logic.

I replaced Sitecore's SetupPageContext with my own that created my custom PageContext:

public class SetupPageContext : RequestBeginProcessor
{
    public override void Process(RequestBeginArgs args)
    {
        Assert.ArgumentNotNull(args, "args");
        this.SetupContext(args);
    }

    protected virtual PageContext CreateInstance(RequestContext requestContext, RequestBeginArgs args)
    {
        return new PageContext()
        {
            RequestContext = requestContext
        };
    }

    protected virtual void SetCurrent(PageContext pageContext, RequestBeginArgs args)
    {
        ContextService.Get().Push<PageContext>(pageContext);
    }

    protected virtual void SetupContext(RequestBeginArgs args)
    {
        this.SetCurrent(this.CreateInstance(args.RequestContext, args), args);
    }
}

Then I made a custom PageContext that inherits from Sitecore's PageContext:

public class PageContext : Sitecore.Mvc.Presentation.PageContext
{
    private readonly Flag _getItemFlag = new Flag();

    public new static PageContext Current => ContextService.Get().GetCurrent<PageContext>();

    protected override Item GetItem()
    {
        using (new RecursionPreventer("GetItem", _getItemFlag))
        {
            using (TraceBlock.Start("Get item for {0}.".FormatWith((object)typeof(PageContext))))
            {
                Item obj = PipelineService.Get().RunPipeline<GetPageItemArgs, Item>("mvc.getPageItem", new GetPageItemArgs(), (Func<GetPageItemArgs, Item>)(args => args.Result));
                obj = GetFallbackItem(obj);

                if (obj != null)
                {
                    if (Context.Language == (Language)null || Context.Language != obj.Language)
                        Context.Language = obj.Language;
                    if (Context.Item == null || Context.Item.ID != obj.ID)
                        Context.Item = obj;
                    return obj;
                }
                Tracer.Info((object)"Using Sitecore.Context.Item.");
                return Context.Item;
            }
        }
    }

    protected virtual Item GetFallbackItem(Item requestItem)
    {
        var defaultPath = System.Configuration.ConfigurationManager.AppSettings["CountryResolver.DefaultPath"].ToString();

        var localPath = string.Empty;
        if (RequestContext.HttpContext != null)
        {
            if (RequestContext.HttpContext.Request.Url != null)
            {
                localPath = RequestContext.HttpContext.Request.Url.LocalPath;
            }
        }
        if (localPath == string.Empty)
            return requestItem;

        if (!ShouldRun(localPath))
        {
            return requestItem;
        }

        // Use the item under the client home if it exists, otherwise use the item under the default item       
        var countryItem = Context.Database.GetItem($"{Context.Site.StartPath}{localPath}");
        var defaultItem = Context.Database.GetItem($"{defaultPath}{localPath}");
        var countryItemTemplate = countryItem?.TemplateName.ToLower() ?? "";

        var excludedTemplates = System.Configuration.ConfigurationManager.AppSettings["CountryResolver.ExcludedTemplates"].Split(",".ToCharArray());

        if (countryItem != null && !excludedTemplates.Contains(countryItemTemplate))
        {
            return !countryItem.HasLanguageVersion(countryItem.Language.Name) ? Context.Database.GetItem(Context.Site.StartPath) : countryItem;
        }
        else
        {
            if (defaultItem != null) //This check is necessary to permit aliases to pass.
            {
                return !defaultItem.HasLanguageVersion(defaultItem.Language.Name) ? Context.Database.GetItem(Context.Site.StartPath) : defaultItem;
            }
        }
        return requestItem;
    }

    private static bool ShouldRun(string localPath)
    {
        // Has context NOT been established
        // or is this not a page request?
        if (Context.Database == null || localPath.Length == 0)
            return false;


        // This is here to avoid slowing down TDS Sitecore syncs on developer machines.
        if (localPath.StartsWith("/_dev/", StringComparison.InvariantCultureIgnoreCase))
            return false;


        // This is here to avoid interfering with system functions like visitor identification.
        if (localPath.StartsWith("/layouts/system", StringComparison.InvariantCultureIgnoreCase))
            return false;


        // This prevents this resolver from running for excluded sites
        var excludedSites = System.Configuration.ConfigurationManager.AppSettings["ItemResolver.ExcludedSites"].ToString().Split(",".ToCharArray());

        return !excludedSites.Contains(Context.Site.Name.ToLower());
    }
}

This worked fine, in that it compiled, and I was able to have Sitecore create my PageContext instead of theirs, except when it tries to retrieve the current PageContext, I get an error because it is looking specifically for Sitecore.Mvc.Presentation.PageContext, and not my custom PageContext. Although the methods on PageContext are mostly all virtual, and thus overrideable, the PageContext.Current method is static.

public static PageContext Current
{
  get
  {
    return ContextService.Get().GetCurrent<PageContext>();
  }
}

I tried replacing that call using 'new' as you can see above but that did not seem to solve the problem.

And so, it looks for Sitecore's PageContext and not mine. How can I get past this.

Attempt to retrieve context object of type 'Sitecore.Mvc.Presentation.PageContext' from empty stack.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code. 

Exception Details: System.InvalidOperationException: Attempt to retrieve context object of type 'Sitecore.Mvc.Presentation.PageContext' from empty stack.

Source Error: 

An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.

Stack Trace: 


[InvalidOperationException: Attempt to retrieve context object of type 'Sitecore.Mvc.Presentation.PageContext' from empty stack.]
  Sitecore.Mvc.Common.ContextService.Peek() +267
  Sitecore.Mvc.Pipelines.MvcPipelineArgs.get_PageContext() +23 

Sitecore.Mvc.Pipelines.Request.RequestBegin.ExecuteFormHandler. 
 Process(RequestBeginArgs args) +26
   (Object , Object[] ) +74
   Sitecore.Pipelines.CorePipeline.Run(PipelineArgs args) +479
   Sitecore.Pipelines.DefaultCorePipelineManager.Run(String pipelineName, PipelineArgs args, String pipelineDomain) +22
   Sitecore.Mvc.Pipelines.PipelineService.RunPipeline(String pipelineName, TArgs args) +195
   Sitecore.Mvc.Routing.RouteHttpHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, Object extraData) +74
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication. IExecutionStep.Execute() +1028
 System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +134
1

Let me start by saying that I think this should be raised to Sitecore Support as a bug, as the MVC pipelines shouldn't be overriding your custom ItemResolver processor.

That aside, I have a workaround for you. Start by taking a look at the mvc.getPageItem pipeline:

enter image description here

In this pipeline, Sitecore tries to get the page item first from the Route Value, then the Route Url, and finally from the "Old Context" (the context that you created a custom ItemResolver for).

Note that the GetFromOldContext processor is the fourth to run, and both it and the processors that run before it first check if an item has already been resolved and return if it does, rather than running their own logic. The problem is that the GetFromOldContext processor is the one that does what you're looking for - it gets the item from the existing Sitecore Context, and your custom ItemResolver set that item - but it isn't actually being used because the item is being resolved by the GetFromRouteValue or the GetFromRouteUrl processor.

Solution

What you can do here is add your own custom processor to the beginning of this pipeline and have it do relatively the same thing as the GetFromOldContext processor. However, since you don't want to duplicate behavior or accidentally override any native logic (since I'm not sure why the GetFromOldContext processor is so late in the pipeline), what I would do is first check the Sitecore Context for a custom flag, e.g. IsItemResolvedByCustomResolver, which if true indicates that you should run your custom processor.

For example, you could extend the Sitecore.Context with ExtendedSitecoreContext class and have a property like the following:

public static bool IsItemResolvedByCustomResolver
{
    get
    {
        var obj = Context.Items["sccustom_IsItemResolvedByCustomResolver"];
        return obj == null || (bool)obj;
  }
  set
  {
    Context.Items["sccustom_IsItemResolvedByCustomResolver"] = (object) value;
  } 
}

Then your processor would look something like the following:

public class GetFromOldContext : GetPageItemProcessor
{
    public override void Process(GetPageItemArgs args)
    {
      Assert.ArgumentNotNull((object) args, "args");
      if (!ExtendedSitecoreContext.IsItemResolvedByCustomResolver || args.Result != null)
        return;

      args.Result = this.ResolveItem(args);
    }

    protected virtual Item ResolveItem(GetPageItemArgs args)
    {
      return Sitecore.Context.Item;
    }
}
0

is this answer any use to you?

Custom Item Resolver not setting context item

The processor GetFromOldContext will set the PageItem to Context.Item only if the previous processors haven't already identified an item.

In Sitecore MVC the MVC pipeline overrides the context Item so this might be what you are seeing.

  • That is why my code wasnt working, but did not present an actual solution. Although it is why I went down that path. – Ethan Schofer Jul 5 '17 at 15:32
  • Could use a bit more clarification on this one. – Mark Cassidy Jul 5 '17 at 15:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.