Home » Customization » Publish Items With the Sitecore Item Web API Using a Custom ResolveAction itemWebApiRequest Pipeline Processor

Publish Items With the Sitecore Item Web API Using a Custom ResolveAction itemWebApiRequest Pipeline Processor

Sitecore Technology MVP 2016
Sitecore MVP 2015
Sitecore MVP 2014

Enter your email address to follow this blog and receive notifications of new posts by email.

At the end of last week, when many people were probably thinking about what to do over the weekend, or were making plans with family and/or friends, I started thinking about what I might need to do next on the project I’m working on.

I realized I might need a way to publish Sitecore items I’ve touched via the Sitecore Item Web API — a feature that appears to be missing, or I just cannot figure out how to use from its documentation (if there is a way to do this “out of the box”, please share in a comment below).

After some digging around in Sitecore.ItemWebApi.dll and \App_Config\Include\Sitecore.ItemWebApi.config, I thought it would be a good idea to define a new action that employs a request method other than get, post, put, delete — these request methods are used by a vanilla install of the Sitecore Item Web API.

Where would one find a list of “standard” request methods? Of course Google knows all — I learned about the patch request method, and decided to use it.

According to Wikipedia — see this entry subsection discussing request methods — the patch request method is “used to apply partial modifications to a resource”, and one could argue that a publishing an item in Sitecore is a partial modification to that item — it’s being pushed to another database which is really an update on that item in the target database.

Now that our research is behind us — and we’ve learned about the patch request method — let’s get our hands dirty with some code.

Following the pipeline processor convention set forth in code for the Sitecore Item Web API for other request methods, I decide to box our new patch requests into a new pipeline, and doing this called for creating a new data transfer object for the new pipeline processor we will define below:

using Sitecore.Data.Items;

using Sitecore.ItemWebApi.Pipelines;

namespace Sitecore.Sandbox.ItemWebApi.Pipelines.Patch.DTO
{
    public class PatchArgs : OperationArgs
    {
        public PatchArgs(Item[] scope)
            : base(scope)
        {
        }
    }
}

Next, I created a base class for our new patch processor:

using Sitecore.ItemWebApi.Pipelines;

using Sitecore.Sandbox.ItemWebApi.Pipelines.Patch.DTO;

namespace Sitecore.Sandbox.ItemWebApi.Pipelines.Patch
{
    public abstract class PatchProcessor : OperationProcessor<PatchArgs>
    {
        protected PatchProcessor()
        {
        }
    }
}

I then created a new pipeline processor that will publish items passed to it:

using System;
using System.Collections.Generic;
using System.Linq;

using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.ItemWebApi;
using Sitecore.Publishing;
using Sitecore.Web;

using Sitecore.Sandbox.ItemWebApi.Pipelines.Patch.DTO;

namespace Sitecore.Sandbox.ItemWebApi.Pipelines.Patch
{
    public class PublishScope : PatchProcessor
    {
        private string DefaultTargetDatabase { get; set; }

        public override void Process(PatchArgs arguments)
        {
            Assert.ArgumentNotNull(arguments, "arguments");
            Assert.IsNotNull(arguments.Scope, "The scope is null!");
            PublishItems(arguments.Scope, GetTargetDatabase());
            arguments.Result = GetResult(arguments.Scope);
        }

        private Database GetTargetDatabase()
        {
            return Factory.GetDatabase(GetTargetDatabaseName());
        }

        private string GetTargetDatabaseName()
        {
            string databaseName = WebUtil.GetQueryString("sc_target_database");
            if(!string.IsNullOrWhiteSpace(databaseName))
            {
                return databaseName;
            }

            Assert.IsNotNullOrEmpty(DefaultTargetDatabase, "DefaultTargetDatabase must be set!");
            return DefaultTargetDatabase;
        }

        private static void PublishItems(IEnumerable<Item> items, Database database)
        {
            foreach(Item item in items)
            {
                PublishItem(item, database);
            }
        }

        private static void PublishItem(Item item, Database database)
        {
           PublishOptions publishOptions = new PublishOptions
           (
               item.Database,
               database,
               Sitecore.Publishing.PublishMode.SingleItem,
               item.Language,
               DateTime.Now
           );

            Publisher publisher = new Publisher(publishOptions);
            publisher.Options.RootItem = item;
            publisher.Publish();
        }

        private static Dynamic GetResult(IEnumerable<Item> scope)
        {
            Assert.ArgumentNotNull(scope, "scope");
            Dynamic dynamic = new Dynamic();
            dynamic["statusCode"] = 200;
            dynamic["result"] = GetInnerResult(scope);
            return dynamic;
        }

        private static Dynamic GetInnerResult(IEnumerable<Item> scope)
        {
            Assert.ArgumentNotNull(scope, "scope");
            Dynamic dynamic = new Dynamic();
            dynamic["count"] = scope.Count();
            dynamic["itemIds"] = scope.Select(item => item.ID.ToString());
            return dynamic;
        }
    }
}

The above pipeline processor class serially publishes each item passed to it, and returns a Sitecore.ItemWebApi.Dynamic instance containing information on how many items were published; a collection of IDs of the items that were published; and an OK status code.

If the calling code does not supply a publishing target database via the sc_target_database query string parameter, the processor will use the value defined in DefaultTargetDatabase — this value is set in \App_Config\Include\Sitecore.ItemWebApi.config, which you will see later when I show changes I made to this file.

It had been awhile since I’ve had to publish items in code, so I searched for a refresher on how to do this.

In my quest for some Sitecore API code, I rediscovered this article by Sitecore MVP
Brian Pedersen showing how one can publish Sitecore items programmatically — many thanks to Brian for this article!

If you haven’t read Brian’s article, you should go check it out now. Don’t worry, I’ll wait. 🙂

I then created a new ResolveAction itemWebApiRequest pipeline processor:

using System;

using Sitecore.Diagnostics;
using Sitecore.Exceptions;
using Sitecore.ItemWebApi.Pipelines.Request;
using Sitecore.ItemWebApi.Security;
using Sitecore.Pipelines;

using Sitecore.Sandbox.ItemWebApi.Pipelines.Patch.DTO;

namespace Sitecore.Sandbox.ItemWebApi.Pipelines.Request
{
    public class ResolveAction : Sitecore.ItemWebApi.Pipelines.Request.ResolveAction
    {
        public override void Process(RequestArgs requestArgs)
        {
            Assert.ArgumentNotNull(requestArgs, "requestArgs");
            string method = GetMethod(requestArgs.Context);
            AssertOperation(requestArgs, method);
            
            if(IsCreateRequest(method))
            {
	            ExecuteCreateRequest(requestArgs);
	            return;
            }

            if(IsReadRequest(method))
            {
	            ExecuteReadRequest(requestArgs);
	            return;
            }

            if(IsUpdateRequest(method))
            {
               ExecuteUpdateRequest(requestArgs);
               return;
            }

            if(IsDeleteRequest(method))
            {
	            ExecuteDeleteRequest(requestArgs);
	            return;
            }

            if (IsPatchRequest(method))
            {
	            ExecutePatchRequest(requestArgs);
	            return;
            }
        }

        private static void AssertOperation(RequestArgs requestArgs, string method)
        {
            Assert.ArgumentNotNull(requestArgs, "requestArgs");
            if (requestArgs.Context.Settings.Access == AccessType.ReadOnly && !AreEqual(method, "get"))
            {
                throw new AccessDeniedException("The operation is not allowed.");
            }
        }

        private static bool IsCreateRequest(string method)
        {
            return AreEqual(method, "post");
        }

        private static bool IsReadRequest(string method)
        {
            return AreEqual(method, "get");
        }

        private static bool IsUpdateRequest(string method)
        {
            return AreEqual(method, "put");
        }

        private static bool IsDeleteRequest(string method)
        {
            return AreEqual(method, "delete");
        }

        private static bool IsPatchRequest(string method)
        {
            return AreEqual(method, "patch");
        }

        private static bool AreEqual(string one, string two)
        {
            return string.Equals(one, two, StringComparison.InvariantCultureIgnoreCase);
        }

        protected virtual void ExecutePatchRequest(RequestArgs requestArgs)
        {
            Assert.ArgumentNotNull(requestArgs, "requestArgs");
            PatchArgs patchArgsArgs = new PatchArgs(requestArgs.Scope);
            CorePipeline.Run("itemWebApiPatch", patchArgsArgs);
            requestArgs.Result = patchArgsArgs.Result;
        }

        private string GetMethod(Sitecore.ItemWebApi.Context context)
        {
            Assert.ArgumentNotNull(context, "context");
            return context.HttpContext.Request.HttpMethod.ToLower();
        }
    }
}

The class above contains the same logic as Sitecore.ItemWebApi.Pipelines.Request.ResolveAction, though I refactored it a bit — the nested conditional statements in the Process method were driving me bonkers, and I atomized logic by placing into new methods.

Plus, I added an additional check to see if the request we are to execute is a patch request — this is true when HttpContext.Request.HttpMethod.ToLower() in our Sitecore.ItemWebApi.Context instance is equal to “patch” — and call our new patch pipeline if this is the case.

I then added the new itemWebApiPatch pipeline with its new PublishScope processor, and replaced /configuration/sitecore/pipelines/itemWebApiRequest/processor[@type=”Sitecore.ItemWebApi.Pipelines.Request.ResolveAction, Sitecore.ItemWebApi”] with /configuration/sitecore/pipelines/itemWebApiRequest/processor[@type=”Sitecore.Sandbox.ItemWebApi.Pipelines.Request.ResolveAction, Sitecore.Sandbox”] in \App_Config\Include\Sitecore.ItemWebApi.config:

<?xml version="1.0" encoding="utf-8"?>
	<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
		<sitecore>
			<pipelines>
				<itemWebApiRequest>
					<!-- stuff is defined up here -->
					<processor type="Sitecore.Sandbox.ItemWebApi.Pipelines.Request.ResolveAction, Sitecore.Sandbox" />
					<!-- stuff is defined right here -->
				</itemWebApiRequest>
				<!-- more stuff is defined here -->
				<itemWebApiPatch>
					<processor type="Sitecore.Sandbox.ItemWebApi.Pipelines.Patch.PublishScope, Sitecore.Sandbox">
						<DefaultTargetDatabase>web</DefaultTargetDatabase>
					</processor>
				</itemWebApiPatch>
			</pipelines>
			<!-- there's even more stuff defined down here -->
		</sitecore>
	</configuration>

Let’s test this out, and see how we did.

We’ll be publishing these items:

master-items-to-publish

As you can see, they aren’t in the web database right now:

arent-there-yet-web

I had to modify code in my copy of the console application written by Kern Herskind Nightingale, Director of Technical Services at Sitecore UK, to use the patch request method for the ancestor home item shown above, and set a scope of self and recursive (scope=s|r) — checkout out this post where I added the recursive axe to the Sitecore Item Web API. I am excluding the console application code modification for the sake of brevity.

I then ran the console application above, and saw this:

after-publishing-output

All of these items now live in the web database:

all-in-web-after-publish

If you have any thoughts on this, or ideas on other useful actions for the Sitecore Item Web API, please drop a comment.


4 Comments

  1. […] processor, and another pipeline to execute a different custom operation, have a look at this post where I show how to publish Items using the Sitecore Item Web […]

  2. gorhal says:

    Great stuff mr. It helped me a lot 🙂

  3. […] Publish Items With the Sitecore Item Web API Using a Custom ResolveAction itemWebApiRequest Pipeline…by Mike Reynolds […]

Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.