Resolving Custom Context.Item in Sitecore MVC

One of the most common customizations that I have seen in Sitecore is the addition of custom processors in the httpRequestBegin pipeline, usually to add is some custom logic to resolve the context item, maybe to deal with custom URLs or wildcard items. Since this pipeline runs for every single request, there are plenty of reasons to customize here.

Most often I’ve seen , you plug in after the Sitecore.Pipelines.HttpRequest.ItemResolver processor with whatever your custom requirements are. However, if you are using Sitecore MVC (and I hope you are) then you may find that your custom logic has not been applied and the Sitecore.Context.Item has been reset back to default Sitecore logic.

This has come up a number of times on Slack and caught a few colleagues out. It also caught me out a while back when I was doing some wildcard work with MVC:

Where did you lose your Context Item?

With Sitecore MVC a number of additional pipelines were introduced that were only run specifically for MVC requests.

But does this affect the Context Item?

The GetItem() method in the Sitecore.Mvc.Presentation.PageContext class in Sitecore.MVC.dll runs the mvc.getPageItem pipeline, which has a number of processors defined. One of those is Sitecore.Mvc.Pipelines.Response.GetPageItem.GetFromRouteUrl which resolves the Item (again) from the Route URL.

When the pipeline returns, a number of checks are made, one of which is to check if the Context.Item.ID matches the Item returned, if not it sets Context.Item to the one resolved by the mvc.getPageItem pipeline.

You can see the logic in Sitecore.Mvc.Presentation.PageContext, Sitecore.MVC :

protected virtual Item GetItem()
{
    using (new RecursionPreventer("GetItem", this.getItemFlag))
    {
        using (TraceBlock.Start(Sitecore.StringExtensions.StringExtensions.FormatWith("Get item for {0}.", (object)typeof(PageContext))))
        {
            Item obj = PipelineService.Get().RunPipeline<GetPageItemArgs, Item>("mvc.getPageItem", new GetPageItemArgs(), (Func<GetPageItemArgs, Item>)(args => args.Result));
            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;
        }
    }
}

How to get your Context Item back

Update the code in your custom ItemResolver in the httpRequestBegin pipeline and store a boolean flag to indicate to indicate that the item has already been resolved using custom logic, this saves having to run the resolving logic twice:

public class CustomItemResolver : HttpRequestProcessor
{
    public override void Process(HttpRequestArgs args)
    {
        Assert.ArgumentNotNull(args, "args");

        if (Sitecore.Context.Item == null || Sitecore.Context.Item.TemplateID != {guid})
            return;

        Sitecore.Context.Item = ResolveCustomItem(args.Url);
        Sitecore.Context.Items["custom::ItemResolved"] = true;
    }
}

Create another class that checks if the item has previously been resolved in the regular httpRequestPipeline and if so sets the result to the context item:

public class CheckItemResolved : MvcPipelineProcessor<GetPageItemArgs>
{
    public override void Process(GetPageItemArgs args)
    {
        var resolved = Sitecore.Context.Items["custom::ItemResolved"];
        if (MainUtil.GetBool(resolved, false))
        {
             // item has previously been resolved
             args.Result = Sitecore.Context.Item;
        }
    }
}

And then patch it in before the MVC pipeline that resolves the item again from the Route URL.

<pipelines>
    <mvc.getPageItem>
        <processor type="MyProject.Custom.Pipelines.CheckItemResolved, MyProject.Custom"
        patch:before="*[@type='Sitecore.Mvc.Pipelines.Response.GetPageItem.GetFromRouteUrl, Sitecore.Mvc']" />
    </mvc.getPageItem>
</pipelines>

The advantage of setting the result item to the Context Item and patching it before the default MVC GetFromFromUrl() pipeline is the code breaks out early so it doesn’t go through a whole bunch of logic to resolve the item only for you to go and reset it again.

Now the Context.Item should be what you are expecting. Although this “reset” is important, you probably don’t want to be using Context.Item in MVC. Take a read of Sitecore MVC Item Maze post by Pavel Veller, it’s really great detail on the different types of Item properties.

What you probably want is PageContext.Current.Item (i.e. current page Item) or RenderingContext.Current.Rendering.Item (i.e. Datasource with fallback) but the above is an important and required step when using custom Item Resolvers.

Aliens

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s