Home » Web Forms for Marketers

Category Archives: Web Forms for Marketers

Advertisements

Service Locate or Create Objects Defined in a Fully Qualified Type Name Field in Sitecore

<TL;DR>

This is — without a doubt — the longest blog post I have ever written — and hopefully to ever write as it nearly destroyed me 😉 — so will distill the main points in this TL;DR synopsis.

Most bits in Sitecore Experience Forms use objects/service class instances sourced from the Sitecore IoC container but not all. Things not sourced from the Sitecore IoC container are defined on Items in the following folders:

.

Why?

¯\_(ツ)_/¯

This is most likely due to their fully qualified type names being defined in a type field on Items contained in these folders, and sourcing these from the Sitecore IoC is not a thing OOTB in Sitecore as far as I am aware (reflection is used to create them):

Moreover, this is the same paradigm found in Web Forms for Marketers (WFFM) for some of its parts (Save Actions are an example).

Well, this paradigm bothers me a lot — I strongly feel that virtually everything should be sourced from the Sitecore IoC container as it promotes SOLID principles, a discussion I will leave for another time — so went ahead and built a system of Sitecore pipelines and service classes to:

  1. Get the fully qualified type name string out of a field of an Item.
  2. Resolve the Type from the string from #1.
  3. Try to find the Type in the Sitecore IoC container using Service Locator (before whinging about using Service Locator for this, keep in mind that it would be impossible to inject everything from the IoC container into a class instance’s constructor in order to find it). If found, return to the caller. Otherwise, proceed to #4.
  4. Create an instance of the Type using Reflection. Return the result to the caller.

Most of the code in the solution that follows are classes which serve as custom pipeline processors for 5 custom pipelines. Pipelines in Sitecore — each being an embodiment of the chain-of-responsibility pattern — are extremely flexible and extendable, hence the reason for going with this approach.

I plan on putting this solution up on GitHub in coming days (or weeks depending on timing) so it is more easily digestible than in a blost post. For now, Just have a scan of the code below.

Note: This solution is just a Proof of concept (PoC). I have not rigorously tested this solution; have no idea what its performance is nor the performance impact it may have; and definitely will not be held responsible if something goes wrong if you decided to use this code in any of your solutions. Use at your own risk!

</TL;DR>

Now that we have that out of the way, let’s jump right into it.

I first created the following abstract class to serve as the base for all pipeline processors in this solution:

using Sitecore.Pipelines;

namespace Sandbox.Foundation.ObjectResolution.Pipelines
{
	public abstract class ResolveProcessor<TPipelineArgs> where TPipelineArgs : PipelineArgs
	{
		public void Process(TPipelineArgs args)
		{
			if (!CanProcess(args))
			{
				return;
			}

			Execute(args);
		}

		protected virtual bool CanProcess(TPipelineArgs args) => args != null;

		protected virtual void AbortPipeline(TPipelineArgs args) => args?.AbortPipeline();

		protected virtual void Execute(TPipelineArgs args)
		{
		}
	}
}

The Execute() method on all pipeline processors will only run when the processor’s CanProcess() method returns true. Also, pipeline processors have the ability to abort the pipeline where they are called.

I then created the following abstract class for all service classes which call a pipeline to “resolve” a particular thing:

using Sitecore.Abstractions;
using Sitecore.Pipelines;

namespace Sandbox.Foundation.ObjectResolution.Services.Resolvers
{
	public abstract class PipelineObjectResolver<TArguments, TPipelineArguemnts, TResult> where TPipelineArguemnts : PipelineArgs
	{
		private readonly BaseCorePipelineManager _corePipelineManager;

		protected PipelineObjectResolver(BaseCorePipelineManager corePipelineManager)
		{
			_corePipelineManager = corePipelineManager;
		}

		public TResult Resolve(TArguments arguments)
		{
			TPipelineArguemnts args = CreatePipelineArgs(arguments);
			RunPipeline(GetPipelineName(), args);
			return GetObject(args);
		}

		protected abstract TResult GetObject(TPipelineArguemnts args);

		protected abstract TPipelineArguemnts CreatePipelineArgs(TArguments arguments);

		protected abstract string GetPipelineName();

		protected virtual void RunPipeline(string pipelineName, PipelineArgs args) => _corePipelineManager.Run(pipelineName, args);
	}
}

Each service class will “resolve” a particular thing with arguments passed to their Resolve() method — these service class’ Resolve() method will take in a TArguments type which serves as the input arguments for it. They will then delegate to a pipeline via the RunPipeline() method to do the resolving. Each will also parse the results returned by the pipeline via the GetObject() method.

Moving forward in this post, I will group each resolving pipeline with their service classes under a <pipeline name /> section.

<resolveItem />

I then moved on to creating a custom pipeline to “resolve” a Sitecore Item. The following class serves as its arguments data transfer object (DTO):

using System.Collections.Generic;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Globalization;
using Sitecore.Pipelines;

using Sandbox.Foundation.ObjectResolution.Services.Resolvers.ItemResolvers;

namespace Sandbox.Foundation.ObjectResolution.Pipelines.ResolveItem
{
	public class ResolveItemArgs : PipelineArgs
	{
		public Database Database { get; set; }

		public string ItemPath { get; set; }

		public Language Language { get; set; }

		public IList<IItemResolver> ItemResolvers { get; set; } = new List<IItemResolver>();

		public Item Item { get; set; }
	}
}

The resolution of an Item will be done by a collection of IItemResolver instances — these are defined further down in this post — which ultimately do the resolution of the Item.

Next, I created the following arguments class for IItemResolver instances:

using Sitecore.Data;
using Sitecore.Globalization;

namespace Sandbox.Foundation.ObjectResolution.Models.Resolvers.ItemResolvers
{
	public class ItemResolverArguments
	{
		public Database Database { get; set; }

		public Language Language { get; set; }

		public string ItemPath { get; set; }
	}
}

Since I hate calling the “new” keyword directly on classes, I created the following factory interface which will construct the argument objects for both the pipeline and service classes for resolving an Item:

using Sitecore.Data;
using Sitecore.Globalization;

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.ItemResolvers;
using Sandbox.Foundation.ObjectResolution.Pipelines.ResolveItem;
using Sandbox.Foundation.ObjectResolution.Pipelines.ResolveType;

namespace Sandbox.Foundation.ObjectResolution.Services.Factories.Resolvers.ItemResolvers
{
	public interface IItemResolverArgumentsFactory
	{
		ItemResolverArguments CreateItemResolverArguments(ResolveTypeArgs args);

		ItemResolverArguments CreateItemResolverArguments(ResolveItemArgs args);

		ItemResolverArguments CreateItemResolverArguments(Database database = null, Language language = null, string itemPath = null);

		ResolveItemArgs CreateResolveItemArgs(ItemResolverArguments arguments);

		ResolveItemArgs CreateResolveItemArgs(Database database = null, Language language = null, string itemPath = null);
	}
}

Here is the class that implements the interface above:

using Sitecore.Data;
using Sitecore.Globalization;

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.ItemResolvers;
using Sandbox.Foundation.ObjectResolution.Pipelines.ResolveItem;
using Sandbox.Foundation.ObjectResolution.Pipelines.ResolveType;

namespace Sandbox.Foundation.ObjectResolution.Services.Factories.Resolvers.ItemResolvers
{
	public class ItemResolverArgumentsFactory : IItemResolverArgumentsFactory
	{
		public ItemResolverArguments CreateItemResolverArguments(ResolveTypeArgs args)
		{
			if (args == null)
			{
				return null;
			}

			return CreateItemResolverArguments(args.Database, args.Language, args.ItemPath);
		}

		public ItemResolverArguments CreateItemResolverArguments(ResolveItemArgs args)
		{
			if (args == null)
			{
				return null;
			}

			return CreateItemResolverArguments(args.Database, args.Language, args.ItemPath);
		}

		public ItemResolverArguments CreateItemResolverArguments(Database database = null, Language language = null, string itemPath = null)
		{
			return new ItemResolverArguments
			{
				Database = database,
				Language = language,
				ItemPath = itemPath
			};
		}

		public ResolveItemArgs CreateResolveItemArgs(ItemResolverArguments arguments)
		{
			if (arguments == null)
			{
				return null;
			}

			return CreateResolveItemArgs(arguments.Database, arguments.Language, arguments.ItemPath);
		}

		public ResolveItemArgs CreateResolveItemArgs(Database database = null, Language language = null, string itemPath = null)
		{
			return new ResolveItemArgs
			{
				Database = database,
				Language = language,
				ItemPath = itemPath
			};
		}
	}
}

It just creates argument types for the pipeline and service classes.

The following interface is for classes that “resolve” Items based on arguments set on an ItemResolverArguments instance:

using Sitecore.Data.Items;

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.ItemResolvers;

namespace Sandbox.Foundation.ObjectResolution.Services.Resolvers.ItemResolvers
{
	public interface IItemResolver
	{
		Item Resolve(ItemResolverArguments arguments);
	}
}

I created a another interface for an IItemResolver which resolves an Item from a Sitecore Database:

namespace Sandbox.Foundation.ObjectResolution.Services.Resolvers.ItemResolvers
{
	public interface IDatabaseItemResolver : IItemResolver
	{
	}
}

The purpose of this interface is so I can register it and the following class which implements it in the Sitecore IoC container:

using Sitecore.Data.Items;

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.ItemResolvers;

namespace Sandbox.Foundation.ObjectResolution.Services.Resolvers.ItemResolvers
{
	public class DatabaseItemResolver : IDatabaseItemResolver
	{
		public Item Resolve(ItemResolverArguments arguments)
		{
			if (!CanResolveItem(arguments))
			{
				return null;
			}

			if(arguments.Language == null)
			{
				return arguments.Database.GetItem(arguments.ItemPath);
			}

			return arguments.Database.GetItem(arguments.ItemPath, arguments.Language);
		}

		protected virtual bool CanResolveItem(ItemResolverArguments arguments) => arguments != null && arguments.Database != null && !string.IsNullOrWhiteSpace(arguments.ItemPath);
	}
}

The instance of the class above will return a Sitecore Item if a Database and Item path (this can be an Item ID) are supplied via the ItemResolverArguments instance passed to its Reolve() method.

Now, let’s start constructing the processors for the pipeline:

First, I created an interface and class for adding a “default” IItemResolver to a collection of IItemResolver defined on the pipeline’s arguments object:

namespace Sandbox.Foundation.ObjectResolution.Pipelines.ResolveItem.AddDefaultItemResolverProcessor
{
	public interface IAddDefaultItemResolver
	{
		void Process(ResolveItemArgs args);
	}
}
using Sandbox.Foundation.ObjectResolution.Services.Resolvers.ItemResolvers;

namespace Sandbox.Foundation.ObjectResolution.Pipelines.ResolveItem.AddDefaultItemResolverProcessor
{
	public class AddDefaultItemResolver : ResolveProcessor<ResolveItemArgs>, IAddDefaultItemResolver
	{
		private readonly IDatabaseItemResolver _databaseItemResolver;

		public AddDefaultItemResolver(IDatabaseItemResolver databaseItemResolver)
		{
			_databaseItemResolver = databaseItemResolver;
		}

		protected override bool CanProcess(ResolveItemArgs args) => base.CanProcess(args) && args.ItemResolvers != null;

		protected override void Execute(ResolveItemArgs args) => args.ItemResolvers.Add(GetTypeResolver());

		protected virtual IItemResolver GetTypeResolver() => _databaseItemResolver;
	}
}

In the above class, I’m injecting the IDatabaseItemResolver instance — this was shown further up in this post — into the constructor of this class, and then adding it to the collection of resolvers.

I then created the following interface and implementation class to doing the “resolving” of the Item:

namespace Sandbox.Foundation.ObjectResolution.Pipelines.ResolveItem.ResolveItemProcessor
{
	public interface IResolveItem
	{
		void Process(ResolveItemArgs args);
	}
}
using System.Linq;

using Sitecore.Data.Items;

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.ItemResolvers;
using Sandbox.Foundation.ObjectResolution.Services.Factories.Resolvers.ItemResolvers;
using Sandbox.Foundation.ObjectResolution.Services.Resolvers.ItemResolvers;

namespace Sandbox.Foundation.ObjectResolution.Pipelines.ResolveItem.ResolveItemProcessor
{
	public class ResolveItem : ResolveProcessor<ResolveItemArgs>, IResolveItem
	{
		private readonly IItemResolverArgumentsFactory _itemResolverArgumentsFactory;
		

		public ResolveItem(IItemResolverArgumentsFactory itemResolverArgumentsFactory)
		{
			_itemResolverArgumentsFactory = itemResolverArgumentsFactory;
		}

		protected override bool CanProcess(ResolveItemArgs args) => base.CanProcess(args) && args.Database != null && !string.IsNullOrWhiteSpace(args.ItemPath) && args.ItemResolvers.Any();

		protected override void Execute(ResolveItemArgs args) => args.Item = GetItem(args);

		protected virtual Item GetItem(ResolveItemArgs args)
		{
			ItemResolverArguments arguments = CreateItemResolverArguments(args);
			if (arguments == null)
			{
				return null;
			}

			foreach (IItemResolver resolver in args.ItemResolvers)
			{
				Item item = resolver.Resolve(arguments);
				if (item != null)
				{
					return item;
				}
			}

			return null;
		}

		protected virtual ItemResolverArguments CreateItemResolverArguments(ResolveItemArgs args) => _itemResolverArgumentsFactory.CreateItemResolverArguments(args);
	}
}

The class above just iterates over all IItemResolver instances on the PipelineArgs instance; passes an ItemResolverArguments instance the Resolve() method on each — the ItemResolverArguments instance is created from a factory — and returns the first Item found by one of the IItemResolver instances. If none were found, null is returned.

Now, we need to create a service class that calls the custom pipeline. I created the following class to act as a settings class for the service.

namespace Sandbox.Foundation.ObjectResolution.Models.Resolvers.ItemResolvers
{
	public class ItemResolverServiceSettings
	{
		public string ResolveItemPipelineName { get; set; }
	}
}

An instance of this class will be injected into the service — the instance is created by the Sitecore Configuration Factory — and its ResolveItemPipelineName property will contain a value from Sitecore Configuration (see the Sitecore patch configuration file towards the bottom of this blog post).

I then created the following interface for the service — it’s just another IItemResolver — so I can register it in the Sitecore IoC container:

namespace Sandbox.Foundation.ObjectResolution.Services.Resolvers.ItemResolvers
{
	public interface IItemResolverService : IItemResolver
	{
	}
}

The following class implements the interface above:

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.ItemResolvers;
using Sitecore.Abstractions;
using Sitecore.Data.Items;

using Sandbox.Foundation.ObjectResolution.Services.Factories.Resolvers.ItemResolvers;


using Sandbox.Foundation.ObjectResolution.Pipelines.ResolveItem;

namespace Sandbox.Foundation.ObjectResolution.Services.Resolvers.ItemResolvers
{
	public class ItemResolverService : PipelineObjectResolver<ItemResolverArguments, ResolveItemArgs, Item>, IItemResolverService
	{
		private readonly ItemResolverServiceSettings _settings;
		private readonly IItemResolverArgumentsFactory _itemResolverArgumentsFactory;

		public ItemResolverService(ItemResolverServiceSettings settings, IItemResolverArgumentsFactory itemResolverArgumentsFactory, BaseCorePipelineManager corePipelineManager)
			: base(corePipelineManager)
		{
			_settings = settings;
			_itemResolverArgumentsFactory = itemResolverArgumentsFactory;
		}

		protected override Item GetObject(ResolveItemArgs args)
		{
			return args.Item;
		}

		protected override ResolveItemArgs CreatePipelineArgs(ItemResolverArguments arguments) => _itemResolverArgumentsFactory.CreateResolveItemArgs(arguments);

		protected override string GetPipelineName() => _settings.ResolveItemPipelineName;
	}
}

The above class subclasses the abstract PipelineObjectResolver class I had shown further above in this post. Most of the magic happens in that base class — for those interested in design patterns, this is an example of the Template Method pattern if you did not know — and all subsequent custom pipeline wrapping service classes will follow this same pattern.

I’m not going to go much into detail on the above class as it should be self-evident on what’s happening after looking at the PipelineObjectResolver further up in this post.

<resolveType />

I then started code for the next pipeline — a pipeline to resolve Types.

I created the following PipelineArgs subclass class whose instances will serve as arguments to this new pipeline:

using System;
using System.Collections.Generic;

using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Globalization;
using Sitecore.Pipelines;

using Sandbox.Foundation.ObjectResolution.Services.Cachers;
using Sandbox.Foundation.ObjectResolution.Services.Resolvers.ItemResolvers;
using Sandbox.Foundation.ObjectResolution.Services.Resolvers.TypeResolvers;

namespace Sandbox.Foundation.ObjectResolution.Pipelines.ResolveType
{
	public class ResolveTypeArgs : PipelineArgs
	{
		public Database Database { get; set; }

		public string ItemPath { get; set; }

		public Language Language { get; set; }

		public IItemResolver ItemResolver { get; set; }

		public Item Item { get; set; }

		public string TypeFieldName { get; set; }

		public string TypeName { get; set; }

		public IList<ITypeResolver> TypeResolvers { get; set; } = new List<ITypeResolver>();

		public ITypeCacher TypeCacher { get; set; }

		public Type Type { get; set; }

		public bool UseTypeCache { get; set; }
	}
}

I then created the following class to serve as an arguments object for services that will resolve types:

using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Globalization;

namespace Sandbox.Foundation.ObjectResolution.Models.Resolvers.TypeResolvers
{
	public class TypeResolverArguments
	{
		public Database Database { get; set; }

		public Language Language { get; set; }

		public string ItemPath { get; set; }

		public Item Item { get; set; }

		public string TypeFieldName { get; set; }

		public string TypeName { get; set; }

		public bool UseTypeCache { get; set; }
	}
}

As I had done for the previous resolver, I created a factory to create arguments for both the PipelineArgs and arguments used by the service classes. Here is the interface for that factory class:

using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Globalization;

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.TypeResolvers;
using Sandbox.Foundation.ObjectResolution.Pipelines.CreateObject;
using Sandbox.Foundation.ObjectResolution.Pipelines.LocateObject;
using Sandbox.Foundation.ObjectResolution.Pipelines.ResolveObject;
using Sandbox.Foundation.ObjectResolution.Pipelines.ResolveType;

namespace Sandbox.Foundation.ObjectResolution.Services.Factories.Resolvers.TypeResolvers
{
	public interface ITypeResolverArgumentsFactory
	{
		TypeResolverArguments CreateTypeResolverArguments(ResolveObjectArgs args);

		TypeResolverArguments CreateTypeResolverArguments(LocateObjectArgs args);

		TypeResolverArguments CreateTypeResolverArguments(CreateObjectArgs args);

		TypeResolverArguments CreateTypeResolverArguments(ResolveTypeArgs args);

		TypeResolverArguments CreateTypeResolverArguments(Database database = null, Language language = null, string itemPath = null, Item item = null, string typeFieldName = null, string typeName = null, bool useTypeCache = false);

		ResolveTypeArgs CreateResolveTypeArgs(TypeResolverArguments arguments);

		ResolveTypeArgs CreateResolveTypeArgs(Database database = null, Language language = null, string itemPath = null, Item item = null, string typeFieldName = null, string typeName = null, bool useTypeCache = false);
	}
}

The following class implements the interface above:

using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Globalization;

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.TypeResolvers;
using Sandbox.Foundation.ObjectResolution.Pipelines.CreateObject;
using Sandbox.Foundation.ObjectResolution.Pipelines.LocateObject;
using Sandbox.Foundation.ObjectResolution.Pipelines.ResolveObject;
using Sandbox.Foundation.ObjectResolution.Pipelines.ResolveType;

namespace Sandbox.Foundation.ObjectResolution.Services.Factories.Resolvers.TypeResolvers
{
	public class TypeResolverArgumentsFactory : ITypeResolverArgumentsFactory
	{
		public TypeResolverArguments CreateTypeResolverArguments(ResolveObjectArgs args)
		{
			if (args == null)
			{
				return null;
			}

			return CreateTypeResolverArguments(args.Database, args.Language, args.ItemPath, args.Item, args.TypeFieldName, args.TypeName, args.UseTypeCache);
		}
		
		public TypeResolverArguments CreateTypeResolverArguments(LocateObjectArgs args)
		{
			if (args == null)
			{
				return null;
			}

			return CreateTypeResolverArguments(args.Database, args.Language, args.ItemPath, args.Item, args.TypeFieldName, args.TypeName, args.UseTypeCache);
		}

		public TypeResolverArguments CreateTypeResolverArguments(CreateObjectArgs args)
		{
			if (args == null)
			{
				return null;
			}

			return CreateTypeResolverArguments(args.Database, args.Language, args.ItemPath, args.Item, args.TypeFieldName, args.TypeName, args.UseTypeCache);
		}

		public TypeResolverArguments CreateTypeResolverArguments(ResolveTypeArgs args)
		{
			return CreateTypeResolverArguments(args.Database, args.Language, args.ItemPath, args.Item, args.TypeFieldName, args.TypeName, args.UseTypeCache);
		}

		public TypeResolverArguments CreateTypeResolverArguments(Database database = null, Language language = null, string itemPath = null, Item item = null, string typeFieldName = null, string typeName = null, bool useTypeCache = false)
		{
			return new TypeResolverArguments
			{
				Database = database,
				Language = language,
				ItemPath = itemPath,
				Item = item,
				TypeFieldName = typeFieldName,
				TypeName = typeName,
				UseTypeCache = useTypeCache
			};
		}

		public ResolveTypeArgs CreateResolveTypeArgs(TypeResolverArguments arguments)
		{
			if (arguments == null)
			{
				return null;
			}

			return CreateResolveTypeArgs(arguments.Database, arguments.Language, arguments.ItemPath, arguments.Item, arguments.TypeFieldName, arguments.TypeName, arguments.UseTypeCache);
		}

		public ResolveTypeArgs CreateResolveTypeArgs(Database database = null, Language language = null, string itemPath = null, Item item = null, string typeFieldName = null, string typeName = null, bool useTypeCache = false)
		{
			return new ResolveTypeArgs
			{
				Database = database,
				Language = language,
				ItemPath = itemPath,
				Item = item,
				TypeFieldName = typeFieldName,
				TypeName = typeName,
				UseTypeCache = useTypeCache
			};
		}
	}
}

I’m not going to discuss much on the class above — it just creates instances of TypeResolverArguments and ResolveTypeArgs based on a variety of things provided to each method.

I then created the following interface for a pipeline processor to resolve an Item and set it on the passed PipelineArgs instance if one wasn’t provided by the caller or set by another processor:

namespace Sandbox.Foundation.ObjectResolution.Pipelines.ResolveType.SetItemResolverProcessor
{
	public interface ISetItemResolver
	{
		void Process(ResolveTypeArgs args);
	}
}

The following class implements the interface above:

using Sandbox.Foundation.ObjectResolution.Services.Resolvers.ItemResolvers;

namespace Sandbox.Foundation.ObjectResolution.Pipelines.ResolveType.SetItemResolverProcessor
{
	public class SetItemResolver : ResolveProcessor<ResolveTypeArgs>, ISetItemResolver
	{
		private readonly IItemResolverService _itemResolverService;

		public SetItemResolver(IItemResolverService itemResolverService)
		{
			_itemResolverService = itemResolverService;
		}

		protected override bool CanProcess(ResolveTypeArgs args) => base.CanProcess(args) && args.Database != null && !string.IsNullOrWhiteSpace(args.ItemPath);

		protected override void Execute(ResolveTypeArgs args) => args.ItemResolver = GetItemResolver();

		protected virtual IItemResolver GetItemResolver() => _itemResolverService;
	}
}

In the class above, I’m injecting an instance of a IItemResolverService into its constructor, and setting it on the ItemResolver property of the ResolveTypeArgs instance.

Does this IItemResolverService interface look familiar? It should as it’s the IItemResolverService defined further up in this post which calls the <resolveItem /> pipeline.

Now we need a processor to resolve the Item. The following interface and class do this:

namespace Sandbox.Foundation.ObjectResolution.Pipelines.ResolveType.ResolveTypeProcessor
{
	public interface IResolveItem
	{
		void Process(ResolveTypeArgs args);
	}
}
using Sandbox.Foundation.ObjectResolution.Models.Resolvers.ItemResolvers;
using Sandbox.Foundation.ObjectResolution.Services.Factories.Resolvers.ItemResolvers;

namespace Sandbox.Foundation.ObjectResolution.Pipelines.ResolveType.ResolveTypeProcessor
{
	public class ResolveItem : ResolveProcessor<ResolveTypeArgs>, IResolveItem
	{
		private readonly IItemResolverArgumentsFactory _itemResolverArgumentsFactory;

		public ResolveItem(IItemResolverArgumentsFactory itemResolverArgumentsFactory)
		{
			_itemResolverArgumentsFactory = itemResolverArgumentsFactory;
		}

		protected override bool CanProcess(ResolveTypeArgs args) => base.CanProcess(args) && args.Database != null && args.ItemResolver != null;

		protected override void Execute(ResolveTypeArgs args) => args.Item = args.ItemResolver.Resolve(CreateTypeResolverArguments(args));

		protected virtual ItemResolverArguments CreateTypeResolverArguments(ResolveTypeArgs args) => _itemResolverArgumentsFactory.CreateItemResolverArguments(args);
	}
}

The class above just delegates to the IItemResolver instance on the ResolveTypeArgs instance to resolve the Item.

Next, we need a processor to get the fully qualified type name from the Item. The following interface and class are for a processor that does just that:

namespace Sandbox.Foundation.ObjectResolution.Pipelines.ResolveType.SetTypeNameProcessor
{
	public interface ISetTypeName
	{
		void Process(ResolveTypeArgs args);
	}
}
namespace Sandbox.Foundation.ObjectResolution.Pipelines.ResolveType.SetTypeNameProcessor
{
	public class SetTypeName : ResolveProcessor<ResolveTypeArgs>, ISetTypeName
	{
		protected override bool CanProcess(ResolveTypeArgs args) => base.CanProcess(args) && args.Item != null && !string.IsNullOrWhiteSpace(args.TypeFieldName);

		protected override void Execute(ResolveTypeArgs args) => args.TypeName = args.Item[args.TypeFieldName];
	}
}

The class above just gets the value from the field where the fully qualified type is defined — the name of the field where the fully qualified type name is defined should be set by the caller of this pipeline.
I then defined the following interface and class which will sort out what the Type object is based on a fully qualified type name passed to it:

using System;

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.TypeResolvers;

namespace Sandbox.Foundation.ObjectResolution.Services.Resolvers.TypeResolvers
{
	public interface ITypeResolver
	{
		Type Resolve(TypeResolverArguments arguments);
	}
}

I then created the following interface for a service class that will delegate to Sitecore.Reflection.ReflectionUtil to get a Type with a provided fully qualified type name:

using System;

namespace Sandbox.Foundation.ObjectResolution.Services.Reflection
{
	public interface IReflectionUtilService
	{
		Type GetTypeInfo(string type);

		object CreateObject(Type type);

		object CreateObject(Type type, object[] parameters);
	}
}

Here’s the class that implements the interface above:

using System;

using Sitecore.Reflection;

namespace Sandbox.Foundation.ObjectResolution.Services.Reflection
{
	public class ReflectionUtilService : IReflectionUtilService
	{
		public Type GetTypeInfo(string type)
		{
			return ReflectionUtil.GetTypeInfo(type);
		}

		public object CreateObject(Type type)
		{
			return ReflectionUtil.CreateObject(type);
		}

		public object CreateObject(Type type, object[] parameters)
		{
			return ReflectionUtil.CreateObject(type, parameters);
		}
	}
}

The class above also creates objects via the ReflectionUtil static class with a passed type and constructor arguments — this will be used in the <createObject /> pipeline further down in this post.

I then defined the following interface for a class that will leverage the IReflectionUtilService service above:

namespace Sandbox.Foundation.ObjectResolution.Services.Resolvers.TypeResolvers
{
	public interface IReflectionTypeResolver : ITypeResolver
	{
	}
}

This is the class that implements the interface above:

using System;

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.TypeResolvers;
using Sandbox.Foundation.ObjectResolution.Services.Reflection;

namespace Sandbox.Foundation.ObjectResolution.Services.Resolvers.TypeResolvers
{
	public class ReflectionTypeResolver : IReflectionTypeResolver
	{
		private readonly IReflectionUtilService _reflectionUtilService;

		public ReflectionTypeResolver(IReflectionUtilService reflectionUtilService)
		{
			_reflectionUtilService = reflectionUtilService;
		}

		public Type Resolve(TypeResolverArguments arguments)
		{
			if (string.IsNullOrWhiteSpace(arguments?.TypeName))
			{
				return null;
			}

			return GetTypeInfo(arguments.TypeName);
		}

		protected virtual Type GetTypeInfo(string typeName) => _reflectionUtilService.GetTypeInfo(typeName);
	}
}

The class above just delegates to the IReflectionUtilService to get the Type with the supplied fully qualified type name.

I then created the following interface and class to represent a pipeline processor to add the ITypeResolver above to the collection of ITypeResolver on the ResolveTypeArgs instance passed to it:

namespace Sandbox.Foundation.ObjectResolution.Pipelines.ResolveType.AddDefaultTypeResolverProcessor
{
	public interface IAddDefaultTypeResolver
	{
		void Process(ResolveTypeArgs args);
	}
}
using Sandbox.Foundation.ObjectResolution.Services.Resolvers.TypeResolvers;

namespace Sandbox.Foundation.ObjectResolution.Pipelines.ResolveType.AddDefaultTypeResolverProcessor
{
	public class AddDefaultTypeResolver : ResolveProcessor<ResolveTypeArgs>, IAddDefaultTypeResolver
	{
		private readonly IReflectionTypeResolver _reflectionTypeResolver;

		public AddDefaultTypeResolver(IReflectionTypeResolver reflectionTypeResolver)
		{
			_reflectionTypeResolver = reflectionTypeResolver;
		}

		protected override bool CanProcess(ResolveTypeArgs args) => base.CanProcess(args) && args.TypeResolvers != null;

		protected override void Execute(ResolveTypeArgs args) => args.TypeResolvers.Add(GetTypeResolver());

		protected virtual ITypeResolver GetTypeResolver() => _reflectionTypeResolver;
	}
}

There isn’t much going on in the class above. The Execute() method just adds the IReflectionTypeResolver to the TypeResolvers collection.

When fishing through the Sitecore Experience Forms assemblies, I noticed the OOTB code was “caching” Types it had resolved from Type fields.. I decided to employ the same approach, and defined the following interface for an object that caches Types:

using System;

namespace Sandbox.Foundation.ObjectResolution.Services.Cachers
{
	public interface ITypeCacher
	{
		void AddTypeToCache(string typeName, Type type);

		Type GetTypeFromCache(string typeName);
	}
}

Here is the class that implements the interface above:

using System;
using System.Collections.Concurrent;

namespace Sandbox.Foundation.ObjectResolution.Services.Cachers
{
	public class TypeCacher : ITypeCacher
	{
		private static readonly ConcurrentDictionary<string, Type> TypeCache = new ConcurrentDictionary<string, Type>();

		public void AddTypeToCache(string typeName, Type type)
		{
			if (string.IsNullOrWhiteSpace(typeName) || type == null)
			{
				return;
			}

			TypeCache.TryAdd(typeName, type);
		}

		public Type GetTypeFromCache(string typeName)
		{
			if (string.IsNullOrWhiteSpace(typeName))
			{
				return null;
			}

			Type type;
			if (!TypeCache.TryGetValue(typeName, out type))
			{
				return null;
			}

			return type;
		}
	}
}

The AddTypeToCache() method does exactly what the method name says — it will add the supplied Type to cache with the provided type name as the key into the ConcurrentDictionary dictionary on this class.

The GetTypeFromCache() method above tries to get a Type from the ConcurrentDictionary instance on this class, and returns to the caller if it was found. If it wasn’t found, null is returned.

The following interface and class serve as a pipeline processor to set a ITypeCacher instance on the ResolveTypeArgs instance passed to it:

namespace Sandbox.Foundation.ObjectResolution.Pipelines.ResolveType.SetTypeCacherProcessor
{
	public interface ISetTypeCacher
	{
		void Process(ResolveTypeArgs args);
	}
}
using Sandbox.Foundation.ObjectResolution.Services.Cachers;

namespace Sandbox.Foundation.ObjectResolution.Pipelines.ResolveType.SetTypeCacherProcessor
{
	public class SetTypeCacher : ResolveProcessor<ResolveTypeArgs>, ISetTypeCacher
	{
		private readonly ITypeCacher _typeCacher;

		public SetTypeCacher(ITypeCacher typeCacher)
		{
			_typeCacher = typeCacher;
		}

		protected override bool CanProcess(ResolveTypeArgs args) => base.CanProcess(args) && args.UseTypeCache && args.TypeCacher == null;

		protected override void Execute(ResolveTypeArgs args) => args.TypeCacher = GetTypeCacher();

		protected virtual ITypeCacher GetTypeCacher() => _typeCacher;
	}
}

There isn’t much going on in the class above except the injection of the ITypeCacher instance defined further up, and setting that instance on the ResolveTypeArgs instance if it hasn’t already been set.

Now, we need to resolve the Type. The following interface and its implementation class do just that:

namespace Sandbox.Foundation.ObjectResolution.Pipelines.ResolveType.ResolveTypeProcessor
{
	public interface IResolveType
	{
		void Process(ResolveTypeArgs args);
	}
}
using System;
using System.Linq;

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.TypeResolvers;
using Sandbox.Foundation.ObjectResolution.Services.Factories.Resolvers.TypeResolvers;
using Sandbox.Foundation.ObjectResolution.Services.Resolvers.TypeResolvers;

namespace Sandbox.Foundation.ObjectResolution.Pipelines.ResolveType.ResolveTypeProcessor
{
	public class ResolveType : ResolveProcessor<ResolveTypeArgs>, IResolveType
	{
		private readonly ITypeResolverArgumentsFactory _typeResolverArgumentsFactory;

		public ResolveType(ITypeResolverArgumentsFactory typeResolverArgumentsFactory)
		{
			_typeResolverArgumentsFactory = typeResolverArgumentsFactory;
		}

		protected override bool CanProcess(ResolveTypeArgs args) => base.CanProcess(args) && args.TypeResolvers != null && args.TypeResolvers.Any() && !string.IsNullOrWhiteSpace(args.TypeName);

		protected override void Execute(ResolveTypeArgs args) => args.Type = Resolve(args);

		protected virtual Type Resolve(ResolveTypeArgs args)
		{
			Type type = null;
			if (args.UseTypeCache)
			{
				type = GetTypeFromCache(args);
			}

			if (type == null)
			{
				type = GetTypeInfo(args);
			}

			return type;
		}

		protected virtual Type GetTypeInfo(ResolveTypeArgs args)
		{
			TypeResolverArguments arguments = CreateTypeResolverArguments(args);
			if (arguments == null)
			{
				return null;
			}

			foreach (ITypeResolver typeResolver in args.TypeResolvers)
			{
				Type type = typeResolver.Resolve(arguments);
				if (type != null)
				{
					return type;
				}
			}

			return null;
		}

		protected virtual Type GetTypeFromCache(ResolveTypeArgs args) => args.TypeCacher.GetTypeFromCache(args.TypeName);

		protected virtual TypeResolverArguments CreateTypeResolverArguments(ResolveTypeArgs args) => _typeResolverArgumentsFactory.CreateTypeResolverArguments(args);
	}
}

Just as I had done in the <resolveItem /> pipeline further up in this post, the above processor class will iterate over a collection of “resolvers” on the PipelineArgs instance — in this case it’s the TypeResolvers — and pass an arguments instance to each’s Resolve() method. This arguments instance is created from a factory defined further up in this post.

I then created the following settings class for the service class that will wrap the <resolveType /> pipeline:

namespace Sandbox.Foundation.ObjectResolution.Models.Resolvers.TypeResolvers
{
	public class TypeResolverServiceSettings
	{
		public string ResolveTypePipelineName { get; set; }
	}
}

The value on the ResolveTypePipelineName property will come from the Sitecore patch file towards the bottom of this post.

I then created the following interface for the service class that will wrap the pipeline — if you are a design patterns buff, this is an example of the adapter pattern:

namespace Sandbox.Foundation.ObjectResolution.Services.Resolvers.TypeResolvers
{
	public interface ITypeResolverService : ITypeResolver
	{
	}
}

The following class implements the interface above:

using System;

using Sitecore.Abstractions;

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.TypeResolvers;
using Sandbox.Foundation.ObjectResolution.Pipelines.ResolveType;
using Sandbox.Foundation.ObjectResolution.Services.Factories.Resolvers.TypeResolvers;

namespace Sandbox.Foundation.ObjectResolution.Services.Resolvers.TypeResolvers
{
	public class TypeResolverService : PipelineObjectResolver<TypeResolverArguments, ResolveTypeArgs, Type>, ITypeResolverService
	{
		private readonly TypeResolverServiceSettings _settings;
		private readonly ITypeResolverArgumentsFactory _typeResolverArgumentsFactory;

		public TypeResolverService(TypeResolverServiceSettings settings, ITypeResolverArgumentsFactory typeResolverArgumentsFactory, BaseCorePipelineManager corePipelineManager)
			: base(corePipelineManager)
		{
			_settings = settings;
			_typeResolverArgumentsFactory = typeResolverArgumentsFactory;
		}

		protected override Type GetObject(ResolveTypeArgs args)
		{
			return args.Type;
		}

		protected override ResolveTypeArgs CreatePipelineArgs(TypeResolverArguments arguments) => _typeResolverArgumentsFactory.CreateResolveTypeArgs(arguments);

		protected override string GetPipelineName() => _settings.ResolveTypePipelineName;
	}
}

I’m not going to go into details about the class above as it’s just like the other service class which wraps the <resolveItem /> defined further above in this post.

Still following? We’re almost there. 😉

<locateObject />

So we now have a way to resolve Items and Types, we now need to find a Type from an Item in the IoC container. I created a PipelineArgs class for a pipeline that does just that:

using System;
using System.Collections.Generic;

using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Globalization;
using Sitecore.Pipelines;

using Sandbox.Foundation.ObjectResolution.Services.Resolvers.ObjectLocators;
using Sandbox.Foundation.ObjectResolution.Services.Resolvers.TypeResolvers;

namespace Sandbox.Foundation.ObjectResolution.Pipelines.LocateObject
{
	public class LocateObjectArgs : PipelineArgs
	{
		public Database Database { get; set; }

		public string ItemPath { get; set; }

		public Language Language { get; set; }

		public Item Item { get; set; }

		public string TypeFieldName { get; set; }

		public string TypeName { get; set; }

		public bool UseTypeCache { get; set; }

		public ITypeResolver TypeResolver { get; set; }

		public Type Type { get; set; }

		public IList<IObjectLocator> Locators { get; set; } = new List<IObjectLocator>();

		public object Object { get; set; }
	}
}

In reality, this next pipeline can supply an object from anywhere — it doesn’t have to be from an IoC container but that’s what I’m doing here. I did, however, make it extendable so you can source an object from wherever you want, even from the Post Office. 😉

I then created the following arguments object for service classes that will “locate” objects:

using System;

using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Globalization;

namespace Sandbox.Foundation.ObjectResolution.Models.Resolvers.ObjectLocators
{
	public class ObjectLocatorArguments
	{
		public Database Database { get; set; }

		public Language Language { get; set; }

		public string ItemPath { get; set; }

		public Item Item { get; set; }

		public string TypeFieldName { get; set; }

		public string TypeName { get; set; }

		public Type Type { get; set; }

		public bool UseTypeCache { get; set; }
	}
}

As I had done for the previous two “resolvers”, I created a factory to create arguments objects — both for the pipeline and service classes:

using System;

using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Globalization;

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.ObjectLocators;
using Sandbox.Foundation.ObjectResolution.Pipelines.LocateObject;
using Sandbox.Foundation.ObjectResolution.Pipelines.ResolveObject;

namespace Sandbox.Foundation.ObjectResolution.Services.Factories.Resolvers.ObjectLocators
{
	public interface IObjectLocatorArgumentsFactory
	{
		ObjectLocatorArguments CreateObjectLocatorArguments(ResolveObjectArgs args);
		
		ObjectLocatorArguments CreateObjectLocatorArguments(LocateObjectArgs args);

		ObjectLocatorArguments CreateObjectLocatorArguments(Database database = null, Language language = null, string itemPath = null, Item item = null, string typeFieldName = null, string typeName = null, Type type = null, bool useTypeCache = false);

		LocateObjectArgs CreateLocateObjectArgs(ObjectLocatorArguments arguments);

		LocateObjectArgs CreateLocateObjectArgs(Database database = null, Language language = null, string itemPath = null, Item item = null, string typeFieldName = null, string typeName = null, Type type = null, bool useTypeCache = false);
	}
}
using System;

using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Globalization;

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.ObjectLocators;
using Sandbox.Foundation.ObjectResolution.Pipelines.LocateObject;
using Sandbox.Foundation.ObjectResolution.Pipelines.ResolveObject;

namespace Sandbox.Foundation.ObjectResolution.Services.Factories.Resolvers.ObjectLocators
{
	public class ObjectLocatorArgumentsFactory : IObjectLocatorArgumentsFactory
	{
		public ObjectLocatorArguments CreateObjectLocatorArguments(ResolveObjectArgs args)
		{
			if (args == null)
			{
				return null;
			}

			return CreateObjectLocatorArguments(args.Database, args.Language, args.ItemPath, args.Item, args.TypeFieldName, args.TypeName, args.Type, args.UseTypeCache);
		}

		public ObjectLocatorArguments CreateObjectLocatorArguments(LocateObjectArgs args)
		{
			if (args == null)
			{
				return null;
			}

			return CreateObjectLocatorArguments(args.Database, args.Language, args.ItemPath, args.Item, args.TypeFieldName, args.TypeName, args.Type, args.UseTypeCache);
		}

		public ObjectLocatorArguments CreateObjectLocatorArguments(Database database = null, Language language = null, string itemPath = null, Item item = null, string typeFieldName = null, string typeName = null, Type type = null, bool useTypeCache = false)
		{
			return new ObjectLocatorArguments
			{
				Database = database,
				Language = language,
				ItemPath = itemPath,
				Item = item,
				TypeFieldName = typeFieldName,
				TypeName = typeName,
				Type = type
			};
		}

		public LocateObjectArgs CreateLocateObjectArgs(ObjectLocatorArguments arguments)
		{
			if (arguments == null)
			{
				return null;
			}

			return CreateLocateObjectArgs(arguments.Database, arguments.Language, arguments.ItemPath, arguments.Item, arguments.TypeFieldName, arguments.TypeName, arguments.Type, arguments.UseTypeCache);
		}

		public LocateObjectArgs CreateLocateObjectArgs(Database database = null, Language language = null, string itemPath = null, Item item = null, string typeFieldName = null, string typeName = null, Type type = null, bool useTypeCache = false)
		{
			return new LocateObjectArgs
			{
				Database = database,
				Language = language,
				ItemPath = itemPath,
				Item = item,
				TypeFieldName = typeFieldName,
				TypeName = typeName,
				Type = type
			};
		}
	}
}

The above class implements the interface above. It just creates arguments for both the pipeline and service classes.

I then defined the following interface for a pipeline processor to set the ITypeResolver (defined way up above in this post):

namespace Sandbox.Foundation.ObjectResolution.Pipelines.LocateObject.SetTypeResolverProcessor
{
	public interface ISetTypeResolver
	{
		void Process(LocateObjectArgs args);
	}
}

This class implements the interface above:

using Sandbox.Foundation.ObjectResolution.Services.Resolvers.TypeResolvers;

namespace Sandbox.Foundation.ObjectResolution.Pipelines.LocateObject.SetTypeResolverProcessor
{
	public class SetTypeResolver : ResolveProcessor<LocateObjectArgs>, ISetTypeResolver
	{
		private readonly ITypeResolverService _typeResolverService;

		public SetTypeResolver(ITypeResolverService typeResolverService)
		{
			_typeResolverService = typeResolverService;
		}

		protected override bool CanProcess(LocateObjectArgs args) => base.CanProcess(args) && args.TypeResolver == null;

		protected override void Execute(LocateObjectArgs args)
		{
			args.TypeResolver = GetTypeResolver();
		}

		protected virtual ITypeResolver GetTypeResolver() => _typeResolverService;
	}
}

In the class above, I’m injecting the ITypeResolverService into its constructor — this is the service class that wraps the <resolveType /> pipeline defined further up — and set it on the LocateObjectArgs instance if it’s not already set.

Next, I created the following interface for a processor that will “resolve” the type from the TypeResolver set on the LocateObjectArgs instance:

namespace Sandbox.Foundation.ObjectResolution.Pipelines.LocateObject.ResolveTypeProcessor
{
	public interface IResolveType
	{
		void Process(LocateObjectArgs args);
	}
}

The following class implements the interface above:

using System;

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.TypeResolvers;
using Sandbox.Foundation.ObjectResolution.Services.Factories.Resolvers.TypeResolvers;

namespace Sandbox.Foundation.ObjectResolution.Pipelines.LocateObject.ResolveTypeProcessor
{
	public class ResolveType : ResolveProcessor<LocateObjectArgs>, IResolveType
	{
		private readonly ITypeResolverArgumentsFactory _typeResolverArgumentsFactory;

		public ResolveType(ITypeResolverArgumentsFactory typeResolverArgumentsFactory)
		{
			_typeResolverArgumentsFactory = typeResolverArgumentsFactory;
		}

		protected override bool CanProcess(LocateObjectArgs args) => base.CanProcess(args) && args.Type == null && args.TypeResolver != null;

		protected override void Execute(LocateObjectArgs args)
		{
			args.Type = Resolve(args);
		}

		protected virtual Type Resolve(LocateObjectArgs args)
		{
			TypeResolverArguments arguments = CreateTypeResolverArguments(args);
			if (arguments == null)
			{
				return null;
			}

			return args.TypeResolver.Resolve(arguments);
		}

		protected virtual TypeResolverArguments CreateTypeResolverArguments(LocateObjectArgs args) => _typeResolverArgumentsFactory.CreateTypeResolverArguments(args);
	}
}

The class above just “resolves” the type from the TypeResolver set on the LocateObjectArgs instance. Nothing more to see. 😉

I then defined the following interface for a family of classes that “locate” objects from somewhere (perhaps a magical place. 😉 ):

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.ObjectLocators;

namespace Sandbox.Foundation.ObjectResolution.Services.Resolvers.ObjectLocators
{
	public interface IObjectLocator
	{
		object Resolve(ObjectLocatorArguments arguments);
	}
}

Well, we can’t use much magic in this solution, so I’m going to “locate” things in the Sitecore IoC container, so defined the following interface for a class that will employ Service Locator to find it in the Sitecore IoC container:

namespace Sandbox.Foundation.ObjectResolution.Services.Resolvers.ObjectLocators
{
	public interface IServiceProviderLocator : IObjectLocator
	{
	}
}

This class implements the interface above:

using System;

using Sitecore.DependencyInjection;

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.ObjectLocators;

namespace Sandbox.Foundation.ObjectResolution.Services.Resolvers.ObjectLocators
{
	public class ServiceProviderLocator : IServiceProviderLocator
	{
		private readonly IServiceProvider _serviceProvider;

		public ServiceProviderLocator()
		{
			_serviceProvider = GetServiceProvider();
		}

		protected virtual IServiceProvider GetServiceProvider()
		{
			return ServiceLocator.ServiceProvider;
		}

		public object Resolve(ObjectLocatorArguments arguments)
		{
			if (arguments == null || arguments.Type == null)
			{
				return null;
			}

			return GetService(arguments.Type);
		}

		protected virtual object GetService(Type type) => _serviceProvider.GetService(type);
	}
}

In the class above, I’m just passing a type to the System.IServiceProvider’s GetService() method — the IServiceProvider instance is grabbed from the ServiceProvider static member on Sitecore.DependencyInjection.ServiceLocator static class.

Next, I need a processor class to add an instance of the Service Locator IObjectLocator class above to the collection of IObjectLocator instances on the LocateObjectArgs instance, so I defined the following interface for a processor class that does just that:

namespace Sandbox.Foundation.ObjectResolution.Pipelines.LocateObject.AddDefaultObjectLocatorProcessor
{
	public interface IAddDefaultObjectLocator
	{
		void Process(LocateObjectArgs args);
	}
}

Here’s the implementation class for the interface above:

using Sandbox.Foundation.ObjectResolution.Services.Resolvers.ObjectLocators;

namespace Sandbox.Foundation.ObjectResolution.Pipelines.LocateObject.AddDefaultObjectLocatorProcessor
{
	public class AddDefaultObjectLocator : ResolveProcessor<LocateObjectArgs>, IAddDefaultObjectLocator
	{
		private readonly IServiceProviderLocator _serviceProviderLocator;

		public AddDefaultObjectLocator(IServiceProviderLocator serviceProviderLocator)
		{
			_serviceProviderLocator = serviceProviderLocator;
		}

		protected override bool CanProcess(LocateObjectArgs args) => base.CanProcess(args) && args.Locators != null;

		protected override void Execute(LocateObjectArgs args) => args.Locators.Add(GetObjectLocator());

		protected virtual IObjectLocator GetObjectLocator() => _serviceProviderLocator;
	}
}

It’s just adding the IServiceProviderLocator instance to the collection of Locators set on the LocateObjectArgs instance.

Great, so we have things that can “locate” objects but need to have a processor that does the execution of that step to actually find those objects. The following interface is for a processor class that does just that:

namespace Sandbox.Foundation.ObjectResolution.Pipelines.LocateObject.LocateObjectProcessor
{
	public interface ILocateObject
	{
		void Process(LocateObjectArgs args);
	}
}

And here’s its implementation class:

using System.Linq;

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.ObjectLocators;
using Sandbox.Foundation.ObjectResolution.Services.Factories.Resolvers.ObjectLocators;
using Sandbox.Foundation.ObjectResolution.Services.Resolvers.ObjectLocators;

namespace Sandbox.Foundation.ObjectResolution.Pipelines.LocateObject.LocateObjectProcessor
{
	public class LocateObject : ResolveProcessor<LocateObjectArgs>, ILocateObject
	{
		private readonly IObjectLocatorArgumentsFactory _objectLocatorArgumentsFactory;

		public LocateObject(IObjectLocatorArgumentsFactory objectLocatorArgumentsFactory)
		{
			_objectLocatorArgumentsFactory = objectLocatorArgumentsFactory;
		}

		protected override bool CanProcess(LocateObjectArgs args) => base.CanProcess(args) && args.Locators != null && args.Locators.Any() && args.Type != null;

		protected override void Execute(LocateObjectArgs args) => args.Object = Resolve(args);

		protected virtual object Resolve(LocateObjectArgs args)
		{
			ObjectLocatorArguments arguments = CreateObjectLocatorArguments(args);
			if (arguments == null)
			{
				return null;
			}

			foreach (IObjectLocator objectLocator in args.Locators)
			{
				object obj = objectLocator.Resolve(arguments);
				if (obj != null)
				{
					return obj;
				}
			}

			return null;
		}

		protected virtual ObjectLocatorArguments CreateObjectLocatorArguments(LocateObjectArgs args) => _objectLocatorArgumentsFactory.CreateObjectLocatorArguments(args);
	}
}

As I had done in the previous pipelines, I’m just iterating over a collection of classes that “resolve” for a particular thing — here I’m iterating over all IObjectLocator instances set on the LocateObjectArgs instance. If one of them find the object we are looking for, we just set it on the LocateObjectArgs instance.

As I had done for the other pipelines, I created a service class that wraps the new pipeline I am creating. The following class serves as a settings class for that service class:

namespace Sandbox.Foundation.ObjectResolution.Models.Resolvers.ObjectLocators
{
	public class ObjectLocatorServiceSettings
	{
		public string LocateObjectPipelineName { get; set; }
	}
}

An instance of the class above will be created by the Sitecore Configuration Factory, and its LocateObjectPipelineName property will contain a value defined in the Sitecore patch file further down in this post.

I then created the following interface for the service class that will wrap this new pipeline:

namespace Sandbox.Foundation.ObjectResolution.Services.Resolvers.ObjectLocators
{
	public interface IObjectLocatorService : IObjectLocator
	{
	}
}

Here’s the class that implements the interface above:

using Sitecore.Abstractions;

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.ObjectLocators;
using Sandbox.Foundation.ObjectResolution.Pipelines.LocateObject;
using Sandbox.Foundation.ObjectResolution.Services.Factories.Resolvers.ObjectLocators;

namespace Sandbox.Foundation.ObjectResolution.Services.Resolvers.ObjectLocators
{
	public class ObjectLocatorService : PipelineObjectResolver<ObjectLocatorArguments, LocateObjectArgs, object>, IObjectLocatorService
	{
		private readonly ObjectLocatorServiceSettings _settings;
		private readonly IObjectLocatorArgumentsFactory _objectLocatorArgumentsFactory;

		public ObjectLocatorService(ObjectLocatorServiceSettings settings, IObjectLocatorArgumentsFactory objectLocatorArgumentsFactory, BaseCorePipelineManager corePipelineManager)
			: base(corePipelineManager)
		{
			_settings = settings;
			_objectLocatorArgumentsFactory = objectLocatorArgumentsFactory;
		}

		protected override object GetObject(LocateObjectArgs args)
		{
			return args.Object;
		}

		protected override LocateObjectArgs CreatePipelineArgs(ObjectLocatorArguments arguments) => _objectLocatorArgumentsFactory.CreateLocateObjectArgs(arguments);

		protected override string GetPipelineName() => _settings.LocateObjectPipelineName;
	}
}

I’m not going talk much about the class above — it’s following the same pattern as the other classes that wrap their respective pipelines.

<createObject />

So what happens when we cannot find an object via the <locateObject /> pipeline? Well, let’s create it.

I defined the following PipelineArgs class for a new pipeline that creates objects:

using System;
using System.Collections.Generic;

using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Globalization;
using Sitecore.Pipelines;

using Sandbox.Foundation.ObjectResolution.Services.Cachers;
using Sandbox.Foundation.ObjectResolution.Services.Resolvers.ObjectCreators;
using Sandbox.Foundation.ObjectResolution.Services.Resolvers.TypeResolvers;

namespace Sandbox.Foundation.ObjectResolution.Pipelines.CreateObject
{
	public class CreateObjectArgs : PipelineArgs
	{
		public Database Database { get; set; }

		public string ItemPath { get; set; }

		public Language Language { get; set; }

		public Item Item { get; set; }

		public string TypeFieldName { get; set; }

		public string TypeName { get; set; }

		public ITypeResolver TypeResolver { get; set; }

		public Type Type { get; set; }

		public object[] Parameters { get; set; }

		public IList<IObjectCreator> Creators { get; set; } = new List<IObjectCreator>();

		public object Object { get; set; }

		public bool UseTypeCache { get; set; }

		public ITypeCacher TypeCacher { get; set; }
	}
}

I then defined the following class for service classes that create objects:

using System;

using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Globalization;

namespace Sandbox.Foundation.ObjectResolution.Models.Resolvers.ObjectCreators
{
	public class ObjectCreatorArguments
	{
		public Database Database { get; set; }

		public Language Language { get; set; }

		public string ItemPath { get; set; }

		public Item Item { get; set; }

		public string TypeFieldName { get; set; }

		public string TypeName { get; set; }

		public Type Type { get; set; }

		public bool UseTypeCache { get; set; }

		public object[] Parameters { get; set; }
	}
}

Since the “new” keyword promotes tight coupling between classes, I created the following factory interface for classes that create the two arguments types shown above:

using System;

using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Globalization;

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.ObjectCreators;
using Sandbox.Foundation.ObjectResolution.Pipelines.CreateObject;
using Sandbox.Foundation.ObjectResolution.Pipelines.ResolveObject;

namespace Sandbox.Foundation.ObjectResolution.Services.Factories.Resolvers.ObjectCreators
{
	public interface IObjectCreatorArgumentsFactory
	{
		ObjectCreatorArguments CreateObjectCreatorArguments(ResolveObjectArgs args);

		ObjectCreatorArguments CreateObjectCreatorArguments(CreateObjectArgs args);

		ObjectCreatorArguments CreateObjectCreatorArguments(Database database = null, Language language = null, string itemPath = null, Item item = null, string typeFieldName = null, string typeName = null, Type type = null, bool useTypeCache = false, object[] parameters = null);

		CreateObjectArgs CreateCreateObjectArgs(ObjectCreatorArguments arguments);

		CreateObjectArgs CreateCreateObjectArgs(Database database = null, Language language = null, string itemPath = null, Item item = null, string typeFieldName = null, string typeName = null, Type type = null, bool useTypeCache = false, object[] parameters = null);

	}
}

The following class implements the interface above:

using System;

using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Globalization;

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.ObjectCreators;
using Sandbox.Foundation.ObjectResolution.Pipelines.CreateObject;
using Sandbox.Foundation.ObjectResolution.Pipelines.ResolveObject;

namespace Sandbox.Foundation.ObjectResolution.Services.Factories.Resolvers.ObjectCreators
{
	public class ObjectCreatorArgumentsFactory : IObjectCreatorArgumentsFactory
	{
		public ObjectCreatorArguments CreateObjectCreatorArguments(ResolveObjectArgs args)
		{
			if (args == null)
			{
				return null;
			}

			return CreateObjectCreatorArguments(args.Database, args.Language, args.ItemPath, args.Item, args.TypeFieldName, args.TypeName, args.Type, args.UseTypeCache, args.ObjectCreationParameters);
		}

		public ObjectCreatorArguments CreateObjectCreatorArguments(CreateObjectArgs args)
		{
			if (args == null)
			{
				return null;
			}

			return CreateObjectCreatorArguments(args.Database, args.Language, args.ItemPath, args.Item, args.TypeFieldName, args.TypeName, args.Type, args.UseTypeCache, args.Parameters);
		}

		public ObjectCreatorArguments CreateObjectCreatorArguments(Database database = null, Language language = null, string itemPath = null, Item item = null, string typeFieldName = null, string typeName = null, Type type = null, bool useTypeCache = false, object[] parameters = null)
		{
			return new ObjectCreatorArguments
			{
				Database = database,
				Language = language,
				ItemPath = itemPath,
				Item = item,
				TypeFieldName = typeFieldName,
				TypeName = typeName,
				Type = type,
				Parameters = parameters
			};
		}

		public CreateObjectArgs CreateCreateObjectArgs(ObjectCreatorArguments arguments)
		{
			if (arguments == null)
			{
				return null;
			}

			return CreateCreateObjectArgs(arguments.Database, arguments.Language, arguments.ItemPath, arguments.Item, arguments.TypeFieldName, arguments.TypeName, arguments.Type, arguments.UseTypeCache, arguments.Parameters);
		}

		public CreateObjectArgs CreateCreateObjectArgs(Database database = null, Language language = null, string itemPath = null, Item item = null, string typeFieldName = null, string typeName = null, Type type = null, bool useTypeCache = false, object[] parameters = null)
		{
			return new CreateObjectArgs
			{
				Database = database,
				Language = language,
				ItemPath = itemPath,
				Item = item,
				TypeFieldName = typeFieldName,
				TypeName = typeName,
				Type = type,
				UseTypeCache = useTypeCache,
				Parameters = parameters
			};
		}
	}
}

The class above just creates CreateObjectArgs and ObjectCreatorArguments instances.

Let’s jump into the bits that comprise the new pipeline.

The following interface is for a processor class that sets the ITypeResolver on the CreateObjectArgs instance:

namespace Sandbox.Foundation.ObjectResolution.Pipelines.CreateObject.SetTypeResolverProcessor
{
	public interface ISetTypeResolver
	{
		void Process(CreateObjectArgs args);
	}
}

Here’s the processor class that implements the interface above:

using Sandbox.Foundation.ObjectResolution.Services.Resolvers.TypeResolvers;

namespace Sandbox.Foundation.ObjectResolution.Pipelines.CreateObject.SetTypeResolverProcessor
{
	public class SetTypeResolver : ResolveProcessor<CreateObjectArgs>, ISetTypeResolver
	{
		private readonly ITypeResolverService _typeResolverService;

		public SetTypeResolver(ITypeResolverService typeResolverService)
		{
			_typeResolverService = typeResolverService;
		}

		protected override bool CanProcess(CreateObjectArgs args) => base.CanProcess(args) && args.Type == null && args.TypeResolver == null;

		protected override void Execute(CreateObjectArgs args)
		{
			args.TypeResolver = GetTypeResolver();
		}

		protected virtual ITypeResolver GetTypeResolver() => _typeResolverService;
	}
}

Nothing special going on — we’ve seen something like this before further up in this post.

Now, we need a processor to “resolve” types. The following interface is for a processor class which does just that:

namespace Sandbox.Foundation.ObjectResolution.Pipelines.CreateObject.ResolveTypeProcessor
{
	public interface IResolveType
	{
		void Process(CreateObjectArgs args);
	}
}

And here is the processor class that implements the interface above:

using System;

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.TypeResolvers;
using Sandbox.Foundation.ObjectResolution.Services.Factories.Resolvers.TypeResolvers;

namespace Sandbox.Foundation.ObjectResolution.Pipelines.CreateObject.ResolveTypeProcessor
{
	public class ResolveType : ResolveProcessor<CreateObjectArgs>, IResolveType
	{
		private readonly ITypeResolverArgumentsFactory _typeResolverArgumentsFactory;

		public ResolveType(ITypeResolverArgumentsFactory typeResolverArgumentsFactory)
		{
			_typeResolverArgumentsFactory = typeResolverArgumentsFactory;
		}

		protected override bool CanProcess(CreateObjectArgs args) => base.CanProcess(args) && args.Type == null && args.TypeResolver != null;

		protected override void Execute(CreateObjectArgs args)
		{
			args.Type = Resolve(args);
		}

		protected virtual Type Resolve(CreateObjectArgs args)
		{
			TypeResolverArguments arguments = CreateTypeResolverArguments(args);
			if (arguments == null)
			{
				return null;
			}

			return args.TypeResolver.Resolve(arguments);
		}

		protected virtual TypeResolverArguments CreateTypeResolverArguments(CreateObjectArgs args) => _typeResolverArgumentsFactory.CreateTypeResolverArguments(args);
	}
}

I’m not going to discuss much on this as we’ve already seen something like this further up in this post.

I then defined the following interface for a family of classes that create objects:

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.ObjectCreators;

namespace Sandbox.Foundation.ObjectResolution.Services.Resolvers.ObjectCreators
{
	public interface IObjectCreator
	{
		object Resolve(ObjectCreatorArguments arguments);
	}
}

Since I’m not good at arts and crafts, we’ll have to use reflection to create objects. The following interface is for a class that uses reflection to create objects:

namespace Sandbox.Foundation.ObjectResolution.Services.Resolvers.ObjectCreators
{
	public interface IReflectionObjectCreator : IObjectCreator
	{
	}
}

The following class implements the interface above:

using System.Linq;

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.ObjectCreators;
using Sandbox.Foundation.ObjectResolution.Services.Reflection;

namespace Sandbox.Foundation.ObjectResolution.Services.Resolvers.ObjectCreators
{
	public class ReflectionObjectCreator : IReflectionObjectCreator
	{
		private readonly IReflectionUtilService _reflectionUtilService;

		public ReflectionObjectCreator(IReflectionUtilService reflectionUtilService)
		{
			_reflectionUtilService = reflectionUtilService;
		}

		public object Resolve(ObjectCreatorArguments arguments)
		{
			if (arguments == null || arguments.Type == null)
			{
				return null;
			}

			if (arguments.Parameters == null || !arguments.Parameters.Any())
			{
				return _reflectionUtilService.CreateObject(arguments.Type);
			}

			return _reflectionUtilService.CreateObject(arguments.Type, arguments.Parameters);
		}
	}
}

This class above just delegates to the IReflectionUtilService instance — this is defined way up above in this post — injected into it for creating objects.

Now we need to put this IReflectionObjectCreator somewhere so it can be used to create objects. The following interface is for a processor class that adds this to a collection of other IObjectCreator defined on the CreateObjectArgs instance:

namespace Sandbox.Foundation.ObjectResolution.Pipelines.CreateObject.AddDefaultObjectCreatorProcessor
{
	public interface IAddDefaultObjectCreator
	{
		void Process(CreateObjectArgs args);
	}
}

And here is the magic behind the interface above:

using Sandbox.Foundation.ObjectResolution.Services.Resolvers.ObjectCreators;

namespace Sandbox.Foundation.ObjectResolution.Pipelines.CreateObject.AddDefaultObjectCreatorProcessor
{
	public class AddDefaultObjectCreator : ResolveProcessor<CreateObjectArgs>, IAddDefaultObjectCreator
	{
		private readonly IReflectionObjectCreator _reflectionObjectCreator;

		public AddDefaultObjectCreator(IReflectionObjectCreator reflectionObjectCreator)
		{
			_reflectionObjectCreator = reflectionObjectCreator;
		}

		protected override bool CanProcess(CreateObjectArgs args) => base.CanProcess(args) && args.Creators != null;

		protected override void Execute(CreateObjectArgs args) => args.Creators.Add(GetObjectLocator());

		protected virtual IObjectCreator GetObjectLocator() => _reflectionObjectCreator;
	}
}

We are just adding the IReflectionObjectCreator instance to the Creators collection on the CreateObjectArgs instance.

Now, we need a processor that delegates to each IObjectCreator instance in the collection on the CreateObjectArgs instance. The following interface is for a processor that does that:

namespace Sandbox.Foundation.ObjectResolution.Pipelines.CreateObject.CreateObjectProcessor
{
	public interface ICreateObject
	{
		void Process(CreateObjectArgs args);
	}
}

Here’s the above interface’s implementation class:

using System.Linq;

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.ObjectCreators;
using Sandbox.Foundation.ObjectResolution.Pipelines.ResolveObject;
using Sandbox.Foundation.ObjectResolution.Services.Factories.Resolvers.ObjectCreators;
using Sandbox.Foundation.ObjectResolution.Services.Resolvers.ObjectCreators;

namespace Sandbox.Foundation.ObjectResolution.Pipelines.CreateObject.CreateObjectProcessor
{
	public class CreateObject : ResolveProcessor<CreateObjectArgs>, ICreateObject
	{
		private readonly IObjectCreatorArgumentsFactory _objectCreatorArgumentsFactory;

		public CreateObject(IObjectCreatorArgumentsFactory objectCreatorArgumentsFactory)
		{
			_objectCreatorArgumentsFactory = objectCreatorArgumentsFactory;
		}

		protected override bool CanProcess(CreateObjectArgs args) => base.CanProcess(args) && args.Creators.Any();

		protected override void Execute(CreateObjectArgs args)
		{
			args.Object = CreateObjectFromArguments(args);
		}

		protected virtual object CreateObjectFromArguments(CreateObjectArgs args)
		{
			ObjectCreatorArguments arguments = CreateObjectCreatorArguments(args);
			if (arguments == null)
			{
				return null;
			}

			foreach (IObjectCreator objectCreator in args.Creators)
			{
				object result = CreateObjectFromArguments(objectCreator, arguments);
				if (result != null)
				{
					return result;
				}
			}

			return null;
		}

		protected virtual ObjectCreatorArguments CreateObjectCreatorArguments(CreateObjectArgs args) => _objectCreatorArgumentsFactory.CreateObjectCreatorArguments(args);

		protected virtual object CreateObjectFromArguments(IObjectCreator objectCreator, ObjectCreatorArguments arguments) => objectCreator.Resolve(arguments);
	}
}

The above class just iterates over the IObjectCreator collection on the CreateObjectArgs instance, and tries to create an object using each. The IObjectCreatorArgumentsFactory instance assists in creating the ObjectCreatorArguments instance from the CreateObjectArgs instance so it can make such calls on each IObjectCreator instance.

If an object is created from one them, it just uses that and stops the iteration.

It’s probably a good idea to only cache Types when an object was actually created from the Type. The following interface is for a processor that sets a ITypeCacher on the CreateObjectArgs instance — this class will add the Type to a cache (perhaps in a bank somewhere on the Cayman Islands? 😉 ):

namespace Sandbox.Foundation.ObjectResolution.Pipelines.CreateObject.SetTypeCacherProcessor
{
	public interface ISetTypeCacher
	{
		void Process(CreateObjectArgs args);
	}
}

Here’s the implementation class for the interface above:

using Sandbox.Foundation.ObjectResolution.Services.Cachers;

namespace Sandbox.Foundation.ObjectResolution.Pipelines.CreateObject.SetTypeCacherProcessor
{
	public class SetTypeCacher : ResolveProcessor<CreateObjectArgs>, ISetTypeCacher
	{
		private readonly ITypeCacher _typeCacher;

		public SetTypeCacher(ITypeCacher typeCacher)
		{
			_typeCacher = typeCacher;
		}

		protected override bool CanProcess(CreateObjectArgs args) => base.CanProcess(args) && args.UseTypeCache && args.TypeCacher == null;

		protected override void Execute(CreateObjectArgs args) => args.TypeCacher = _typeCacher;
	}
}

It’s just setting the injected ITypeCacher — the implementation class is defined further up in this post — on the CreateObjectArgs instance.

Now, we need to use the ITypeCacher to cache the type. The following interface is for a processor class delegates to the ITypeCacher instance to cache the Type:

namespace Sandbox.Foundation.ObjectResolution.Pipelines.CreateObject.CacheTypeProcessor
{
	public interface ICacheType
	{
		void Process(CreateObjectArgs args);
	}
}

Here is the process class which implements the interface above:

namespace Sandbox.Foundation.ObjectResolution.Pipelines.CreateObject.CacheTypeProcessor
{
	public class CacheType : ResolveProcessor<CreateObjectArgs>, ICacheType
	{
		protected override bool CanProcess(CreateObjectArgs args) => base.CanProcess(args) && !string.IsNullOrWhiteSpace(args.TypeName) && args.Type != null && args.UseTypeCache && args.TypeCacher != null;

		protected override void Execute(CreateObjectArgs args) => AddTypeToCache(args);

		protected virtual void AddTypeToCache(CreateObjectArgs args) => args.TypeCacher.AddTypeToCache(args.TypeName, args.Type);
	}
}

It should be self-explanatory what’s happening here. If not, please drop a comment below.

Now, we need a service class that wraps this new pipeline. I created the following settings class for that service:

namespace Sandbox.Foundation.ObjectResolution.Models.Resolvers.ObjectCreators
{
	public class ObjectCreatorServiceSettings
	{
		public string CreateObjectPipelineName { get; set; }
	}
}

An instance of this class is created by the Sitecore Configuration Factory just as the other ones in this post are.

I then defined the following interface for the service class that will wrap this new pipeline — it’s just another IObjectCreator:

namespace Sandbox.Foundation.ObjectResolution.Services.Resolvers.ObjectCreators
{
	public interface IObjectCreatorService : IObjectCreator
	{
	}
}

This class implements the interface above:

using Sitecore.Abstractions;

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.ObjectCreators;
using Sandbox.Foundation.ObjectResolution.Pipelines.CreateObject;
using Sandbox.Foundation.ObjectResolution.Services.Factories.Resolvers.ObjectCreators;

namespace Sandbox.Foundation.ObjectResolution.Services.Resolvers.ObjectCreators
{
	public class ObjectCreatorService : PipelineObjectResolver<ObjectCreatorArguments, CreateObjectArgs, object>, IObjectCreatorService
	{
		private readonly ObjectCreatorServiceSettings _settings;
		private readonly IObjectCreatorArgumentsFactory _objectCreatorArgumentsFactory;

		public ObjectCreatorService(ObjectCreatorServiceSettings settings, IObjectCreatorArgumentsFactory objectCreatorArgumentsFactory, BaseCorePipelineManager corePipelineManager)
			: base(corePipelineManager)
		{
			_settings = settings;
			_objectCreatorArgumentsFactory = objectCreatorArgumentsFactory;
		}

		protected override object GetObject(CreateObjectArgs args)
		{
			return args.Object;
		}

		protected override CreateObjectArgs CreatePipelineArgs(ObjectCreatorArguments arguments) => _objectCreatorArgumentsFactory.CreateCreateObjectArgs(arguments);

		protected override string GetPipelineName() => _settings.CreateObjectPipelineName;
	}
}

I’m not going to go into details on the above as you have seen this pattern further above in this post.

<resolveObject />

Now we need a way to glue together all pipelines created above. The following PipelineArgs class is for — yet another pipeline 😉 — that glues everything together:

using System;

using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Globalization;
using Sitecore.Pipelines;

using Sandbox.Foundation.ObjectResolution.Services.Resolvers.ObjectCreators;
using Sandbox.Foundation.ObjectResolution.Services.Resolvers.ObjectLocators;
using Sandbox.Foundation.ObjectResolution.Services.Resolvers.TypeResolvers;

namespace Sandbox.Foundation.ObjectResolution.Pipelines.ResolveObject
{
	public class ResolveObjectArgs : PipelineArgs
	{
		public Database Database { get; set; }

		public string ItemPath { get; set; }

		public Language Language { get; set; }

		public Item Item { get; set; }

		public string TypeFieldName { get; set; }

		public string TypeName { get; set; }

		public ITypeResolver TypeResolver { get; set; }

		public Type Type { get; set; }

		public IObjectLocator ObjectLocator;

		public bool FoundInContainer { get; set; }

		public IObjectCreator ObjectCreator;

		public bool UseTypeCache { get; set; }

		public object[] ObjectCreationParameters { get; set; }

		public object Object { get; set; }
	}
}

I also created the following class for the service class that will wrap this new pipeline:

using System;

using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Globalization;

namespace Sandbox.Foundation.ObjectResolution.Models.Resolvers.ObjectResolvers
{
	public class ObjectResolverArguments
	{
		public Database Database { get; set; }

		public Language Language { get; set; }

		public string ItemPath { get; set; }

		public Item Item { get; set; }

		public string TypeFieldName { get; set; }

		public string TypeName { get; set; }

		public Type Type { get; set; }

		public bool UseTypeCache { get; set; }

		public object[] ObjectCreationParameters { get; set; }
	}
}

I bet you are guessing that I’m going to create another factory for these two classes above. Yep, you are correct. Here is the interface for that factory:

using System;

using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Globalization;

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.ObjectResolvers;
using Sandbox.Foundation.ObjectResolution.Pipelines.ResolveObject;

namespace Sandbox.Foundation.ObjectResolution.Services.Factories.Resolvers.ObjectResolvers
{
	public interface IObjectResolverArgumentsFactory
	{
		ObjectResolverArguments CreateObjectResolverArguments(Database database, string itemPath, string typeFieldName, Language language, object[] objectCreationParameters);

		ObjectResolverArguments CreateObjectResolverArguments(Item item, string typeFieldName, bool useTypeCache, object[] objectCreationParameters);

		ResolveObjectArgs CreateResolveObjectArgs(ObjectResolverArguments arguments);

		ResolveObjectArgs CreateResolveObjectArgs(Database database = null, string itemPath = null, Language language = null, Item item = null, string typeFieldName = null, string typeName = null, Type type = null, bool useTypeCache = false, object[] objectCreationParameters = null);

	}
}

The following class implements the factory interface above:

using System;

using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Globalization;

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.ObjectResolvers;
using Sandbox.Foundation.ObjectResolution.Pipelines.ResolveObject;

namespace Sandbox.Foundation.ObjectResolution.Services.Factories.Resolvers.ObjectResolvers
{
	public class ObjectResolverArgumentsFactory : IObjectResolverArgumentsFactory
	{
		public ObjectResolverArguments CreateObjectResolverArguments(Database database, string itemPath, string typeFieldName, Language language, object[] objectCreationParameters)
		{
			return new ObjectResolverArguments
			{
				Database = database,
				ItemPath = itemPath,
				TypeFieldName = typeFieldName,
				Language = language,
				ObjectCreationParameters = objectCreationParameters
			};
		}

		public ObjectResolverArguments CreateObjectResolverArguments(Item item, string typeFieldName, bool useTypeCache, object[] objectCreationParameters)
		{
			return new ObjectResolverArguments
			{
				Item = item,
				TypeFieldName = typeFieldName,
				UseTypeCache = useTypeCache,
				ObjectCreationParameters = objectCreationParameters
			};
		}

		public ResolveObjectArgs CreateResolveObjectArgs(ObjectResolverArguments arguments)
		{
			if (arguments == null)
			{
				return null;
			}

			return CreateResolveObjectArgs(arguments.Database, arguments.ItemPath, arguments.Language, arguments.Item, arguments.TypeFieldName, arguments.TypeName, arguments.Type, arguments.UseTypeCache, arguments.ObjectCreationParameters);
		}

		public ResolveObjectArgs CreateResolveObjectArgs(Item item, string typeFieldName, object[] objectCreationParameters)
		{
			return new ResolveObjectArgs
			{
				Item = item,
				TypeFieldName = typeFieldName,
				ObjectCreationParameters = objectCreationParameters
			};
		}

		public ResolveObjectArgs CreateResolveObjectArgs(Database database = null, string itemPath = null, Language language = null, Item item = null, string typeFieldName = null, string typeName = null, Type type = null, bool useTypeCache = false, object[] objectCreationParameters = null)
		{
			return new ResolveObjectArgs
			{
				Database = database,
				ItemPath = itemPath,
				Language = language,
				Item = item,
				TypeFieldName = typeFieldName,
				TypeName = typeName,
				Type= type,
				UseTypeCache = useTypeCache,
				ObjectCreationParameters = objectCreationParameters
			};
		}
	}
}

The following interface is for a processor class that sets the ITypeResolver on the ResolveObjectArgs instance:

namespace Sandbox.Foundation.ObjectResolution.Pipelines.ResolveObject
{
	public interface ISetTypeResolver
	{
		void Process(ResolveObjectArgs args);
	}
}

Here’s its implementation class:

using Sandbox.Foundation.ObjectResolution.Services.Resolvers.TypeResolvers;

namespace Sandbox.Foundation.ObjectResolution.Pipelines.ResolveObject
{
	public class SetTypeResolver : ResolveProcessor<ResolveObjectArgs>, ISetTypeResolver
	{
		private readonly ITypeResolverService _typeResolverService;

		public SetTypeResolver(ITypeResolverService typeResolverService)
		{
			_typeResolverService = typeResolverService;
		}

		protected override bool CanProcess(ResolveObjectArgs args) => base.CanProcess(args) && args.TypeResolver == null;

		protected override void Execute(ResolveObjectArgs args)
		{
			args.TypeResolver = GetTypeResolver();
		}

		protected virtual ITypeResolver GetTypeResolver() => _typeResolverService;
	}
}

We have already seen this twice, so I won’t discuss it again. 😉

Next, we need to resolve the type. The following interface is for a processor class that does that type resolution:

namespace Sandbox.Foundation.ObjectResolution.Pipelines.ResolveObject.ResolveTypeProcessor
{
	public interface IResolveType
	{
		void Process(ResolveObjectArgs args);
	}
}

Here’s the class that implements the interface above:

using System;

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.TypeResolvers;
using Sandbox.Foundation.ObjectResolution.Services.Factories.Resolvers.TypeResolvers;

namespace Sandbox.Foundation.ObjectResolution.Pipelines.ResolveObject.ResolveTypeProcessor
{
	public class ResolveType : ResolveProcessor<ResolveObjectArgs>, IResolveType
	{
		private readonly ITypeResolverArgumentsFactory _typeResolverArgumentsFactory;

		public ResolveType(ITypeResolverArgumentsFactory typeResolverArgumentsFactory)
		{
			_typeResolverArgumentsFactory = typeResolverArgumentsFactory;
		}

		protected override bool CanProcess(ResolveObjectArgs args) => base.CanProcess(args) && args.Type == null && args.TypeResolver != null;

		protected override void Execute(ResolveObjectArgs args)
		{
			args.Type = Resolve(args);
		}

		protected virtual Type Resolve(ResolveObjectArgs args)
		{
			TypeResolverArguments arguments = CreateTypeResolverArguments(args);
			if (arguments == null)
			{
				return null;
			}

			return args.TypeResolver.Resolve(arguments);
		}

		protected virtual TypeResolverArguments CreateTypeResolverArguments(ResolveObjectArgs args) => _typeResolverArgumentsFactory.CreateTypeResolverArguments(args);
	}
}

I’m also not going to discuss this as I’ve done this somewhere up above. 😉

Now we need a processor to “locate” objects in the Sitecore IoC container. The following interface is for a processor class that does just that:

namespace Sandbox.Foundation.ObjectResolution.Pipelines.ResolveObject.SetObjectLocatorProcessor
{
	public interface ISetObjectLocator
	{
		void Process(ResolveObjectArgs args);
	}
}

The following class implements the interface above:

using Sandbox.Foundation.ObjectResolution.Services.Resolvers.ObjectLocators;

namespace Sandbox.Foundation.ObjectResolution.Pipelines.ResolveObject.SetObjectLocatorProcessor
{
	public class SetObjectLocator : ResolveProcessor<ResolveObjectArgs>, ISetObjectLocator
	{
		private readonly IObjectLocatorService _objectLocatorService;

		public SetObjectLocator(IObjectLocatorService objectLocatorService)
		{
			_objectLocatorService = objectLocatorService;
		}

		protected override bool CanProcess(ResolveObjectArgs args) => base.CanProcess(args) && args.ObjectLocator == null;

		protected override void Execute(ResolveObjectArgs args) => args.ObjectLocator = GetObjectLocator();

		protected virtual IObjectLocator GetObjectLocator() => _objectLocatorService;
	}
}

The class above just sets the IObjectLocatorService — this is the service class which wraps the <locateObject /> pipeline defined further up in this post — on the ResolveObjectArgs instance.

I then created the following interface to delegate to the ObjectLocator property on the ResolveObjectArgs to “locate” the object:

namespace Sandbox.Foundation.ObjectResolution.Pipelines.ResolveObject.LocateObjectProcessor
{
	public interface ILocateObject
	{
		void Process(ResolveObjectArgs args);
	}
}

And here’s the class that implements this interface above:

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.ObjectLocators;
using Sandbox.Foundation.ObjectResolution.Services.Factories.Resolvers.ObjectLocators;

namespace Sandbox.Foundation.ObjectResolution.Pipelines.ResolveObject.LocateObjectProcessor
{
	public class LocateObject : ResolveProcessor<ResolveObjectArgs>, ILocateObject
	{
		private readonly IObjectLocatorArgumentsFactory _objectLocatorArgumentsFactory;

		public LocateObject(IObjectLocatorArgumentsFactory objectLocatorArgumentsFactory)
		{
			_objectLocatorArgumentsFactory = objectLocatorArgumentsFactory;
		}

		protected override bool CanProcess(ResolveObjectArgs args) => base.CanProcess(args) && args.Object == null && args.ObjectLocator != null;

		protected override void Execute(ResolveObjectArgs args)
		{
			args.Object = Locate(args);
			args.FoundInContainer = args.Object != null;
			if (!args.FoundInContainer)
			{
				return;
			}

			AbortPipeline(args);
		}

		protected virtual object Locate(ResolveObjectArgs args) => args.ObjectLocator.Resolve(CreateObjectLocatorArguments(args));

		protected virtual ObjectLocatorArguments CreateObjectLocatorArguments(ResolveObjectArgs args) => _objectLocatorArgumentsFactory.CreateObjectLocatorArguments(args);
	}
}

The above class just tries to “locate” the object using the ObjectLocator set on the ResolveObjectArgs instance.

In the event we can’t find the object via the IObjectLocator, we should create the object instead. I created the following interface to set an IObjectCreator instance on the ResolveObjectArgs instance:

namespace Sandbox.Foundation.ObjectResolution.Pipelines.ResolveObject.SetObjectCreatorProcessor
{
	public interface ISetObjectCreator
	{
		void Process(ResolveObjectArgs args);
	}
}

And here’s its implementation class:

using Sandbox.Foundation.ObjectResolution.Services.Resolvers.ObjectCreators;

namespace Sandbox.Foundation.ObjectResolution.Pipelines.ResolveObject.SetObjectCreatorProcessor
{
	public class SetObjectCreator : ResolveProcessor<ResolveObjectArgs>, ISetObjectCreator
	{
		private readonly IObjectCreatorService _objectCreatorService;

		public SetObjectCreator(IObjectCreatorService objectCreatorService)
		{
			_objectCreatorService = objectCreatorService;
		}

		protected override bool CanProcess(ResolveObjectArgs args) => base.CanProcess(args) && args.ObjectCreator == null;

		protected override void Execute(ResolveObjectArgs args) => args.ObjectCreator = GetObjectCreator();

		protected virtual IObjectCreator GetObjectCreator() => _objectCreatorService;
	}
}

The class above just sets the IObjectCreatorService — this is the service class which wraps the <createObject /> pipeline defined further up in this post — on the ResolveObjectArgs instance.

Next, we need to delegate to this IObjectCreator to create the object. The following interface is for a class that creates objects:

namespace Sandbox.Foundation.ObjectResolution.Pipelines.ResolveObject.CreateObjectProcessor
{
	public interface ICreateObject
	{
		void Process(ResolveObjectArgs args);
	}
}

And here’s the implementation of the interface above:

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.ObjectCreators;
using Sandbox.Foundation.ObjectResolution.Services.Factories.Resolvers.ObjectCreators;

namespace Sandbox.Foundation.ObjectResolution.Pipelines.ResolveObject.CreateObjectProcessor
{
	public class CreateObject : ResolveProcessor<ResolveObjectArgs>, ICreateObject
	{
		private readonly IObjectCreatorArgumentsFactory _objectCreatorArgumentsFactory;

		public CreateObject(IObjectCreatorArgumentsFactory objectCreatorArgumentsFactory)
		{
			_objectCreatorArgumentsFactory = objectCreatorArgumentsFactory;
		}
		protected override bool CanProcess(ResolveObjectArgs args) => base.CanProcess(args) && args.Object == null && args.ObjectCreator != null;

		protected override void Execute(ResolveObjectArgs args) => args.Object = Resolve(args);

		protected virtual object Resolve(ResolveObjectArgs args) => args.ObjectCreator.Resolve(CreateObjectCreatorArguments(args));

		protected virtual ObjectCreatorArguments CreateObjectCreatorArguments(ResolveObjectArgs args) => _objectCreatorArgumentsFactory.CreateObjectCreatorArguments(args);
	}
}

The class above just delegates to the IObjectCreator instance of the ResolveObjectArgs instance to create the object

Like the other 4 pipelines — holy cannoli, Batman, there are 5 pipelines in total in this solution! — I created a service class that wraps this new pipeline. The following class serves as a settings class for this service:

namespace Sandbox.Foundation.ObjectResolution.Models.Resolvers.ObjectResolvers
{
	public class ObjectResolverServiceSettings
	{
		public string ResolveObjectPipelineName { get; set; }

		public bool UseTypeCache { get; set; }
	}
}

An instance of the above is created by the Sitecore Configuration Factory.

The following interface defines a family of classes that “resolve” objects. Unlike the other pipelines, we will only have one class that implements this interface:

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.ObjectResolvers;

namespace Sandbox.Foundation.ObjectResolution.Services.Resolvers.ObjectResolvers
{
	public interface IObjectResolver
	{
		TObject Resolve<TObject>(ObjectResolverArguments arguments) where TObject : class;

		object Resolve(ObjectResolverArguments arguments);
	}
}

The following class implements the interface above:

using Sitecore.Abstractions;

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.ObjectResolvers;
using Sandbox.Foundation.ObjectResolution.Pipelines.ResolveObject;
using Sandbox.Foundation.ObjectResolution.Services.Factories.Resolvers.ObjectResolvers;

namespace Sandbox.Foundation.ObjectResolution.Services.Resolvers.ObjectResolvers
{
	public class ObjectResolverService : PipelineObjectResolver<ObjectResolverArguments, ResolveObjectArgs, object>, IObjectResolver
	{
		private readonly ObjectResolverServiceSettings _settings;
		private readonly IObjectResolverArgumentsFactory _objectResolverArgumentsFactory;

		public ObjectResolverService(ObjectResolverServiceSettings settings, IObjectResolverArgumentsFactory objectResolverArgumentsFactory, BaseCorePipelineManager corePipelineManager)
			: base(corePipelineManager)
		{
			_settings = settings;
			_objectResolverArgumentsFactory = objectResolverArgumentsFactory;
		}

		public TObject Resolve<TObject>(ObjectResolverArguments arguments) where TObject : class
		{
			return Resolve(arguments) as TObject;
		}

		protected override object GetObject(ResolveObjectArgs args)
		{
			return args.Object;
		}

		protected override ResolveObjectArgs CreatePipelineArgs(ObjectResolverArguments arguments) => _objectResolverArgumentsFactory.CreateResolveObjectArgs(arguments);

		protected override string GetPipelineName() => _settings.ResolveObjectPipelineName;
	}
}

I then register every single thing above — and I mean EVERYTHING — in the Sitecore IoC via the following IServicesConfigurator class:

using System;

using Microsoft.Extensions.DependencyInjection;

using Sitecore.Abstractions;
using Sitecore.DependencyInjection;

using Sandbox.Foundation.ObjectResolution.Models.Resolvers.ItemResolvers;
using Sandbox.Foundation.ObjectResolution.Models.Resolvers.ObjectCreators;
using Sandbox.Foundation.ObjectResolution.Models.Resolvers.ObjectLocators;
using Sandbox.Foundation.ObjectResolution.Models.Resolvers.ObjectResolvers;
using Sandbox.Foundation.ObjectResolution.Models.Resolvers.TypeResolvers;
using Sandbox.Foundation.ObjectResolution.Pipelines.CreateObject.AddDefaultObjectCreatorProcessor;
using Sandbox.Foundation.ObjectResolution.Pipelines.CreateObject.CacheTypeProcessor;
using Sandbox.Foundation.ObjectResolution.Pipelines.CreateObject.CreateObjectProcessor;
using Sandbox.Foundation.ObjectResolution.Pipelines.CreateObject.ResolveTypeProcessor;
using Sandbox.Foundation.ObjectResolution.Pipelines.CreateObject.SetTypeCacherProcessor;
using Sandbox.Foundation.ObjectResolution.Pipelines.CreateObject.SetTypeResolverProcessor;
using Sandbox.Foundation.ObjectResolution.Pipelines.LocateObject.AddDefaultObjectLocatorProcessor;
using Sandbox.Foundation.ObjectResolution.Pipelines.LocateObject.LocateObjectProcessor;
using Sandbox.Foundation.ObjectResolution.Pipelines.ResolveItem.AddDefaultItemResolverProcessor;
using Sandbox.Foundation.ObjectResolution.Pipelines.ResolveItem.ResolveItemProcessor;
using Sandbox.Foundation.ObjectResolution.Pipelines.ResolveObject.SetObjectCreatorProcessor;
using Sandbox.Foundation.ObjectResolution.Pipelines.ResolveObject.SetObjectLocatorProcessor;
using Sandbox.Foundation.ObjectResolution.Pipelines.ResolveType.AddDefaultTypeResolverProcessor;
using Sandbox.Foundation.ObjectResolution.Pipelines.ResolveType.SetItemResolverProcessor;
using Sandbox.Foundation.ObjectResolution.Pipelines.ResolveType.SetTypeNameProcessor;
using Sandbox.Foundation.ObjectResolution.Services.Cachers;
using Sandbox.Foundation.ObjectResolution.Services.Factories.Resolvers.ItemResolvers;
using Sandbox.Foundation.ObjectResolution.Services.Factories.Resolvers.ObjectCreators;
using Sandbox.Foundation.ObjectResolution.Services.Factories.Resolvers.ObjectLocators;
using Sandbox.Foundation.ObjectResolution.Services.Factories.Resolvers.ObjectResolvers;
using Sandbox.Foundation.ObjectResolution.Services.Factories.Resolvers.TypeResolvers;
using Sandbox.Foundation.ObjectResolution.Services.Reflection;
using Sandbox.Foundation.ObjectResolution.Services.Resolvers.ItemResolvers;
using Sandbox.Foundation.ObjectResolution.Services.Resolvers.ObjectCreators;
using Sandbox.Foundation.ObjectResolution.Services.Resolvers.ObjectLocators;
using Sandbox.Foundation.ObjectResolution.Services.Resolvers.ObjectResolvers;
using Sandbox.Foundation.ObjectResolution.Services.Resolvers.TypeResolvers;

namespace Sandbox.Foundation.ObjectResolution
{
	public class ObjectResolutionConfigurator : IServicesConfigurator
	{
		public void Configure(IServiceCollection serviceCollection)
		{
			ConfigureCachers(serviceCollection);
			ConfigureFactories(serviceCollection);
			ConfigureItemResolvers(serviceCollection);
			ConfigureTypeResolvers(serviceCollection);
			ConfigureObjectCreators(serviceCollection);
			ConfigureObjectLocators(serviceCollection);
			ConfigureObjectResolvers(serviceCollection);
			ConfigureResolveItemPipelineProcessors(serviceCollection);
			ConfigureResolveTypePipelineProcessors(serviceCollection);
			ConfigureLocateObjectPipelineProcessors(serviceCollection);
			ConfigureCreateObjectPipelineProcessors(serviceCollection);
			ConfigureResolveObjectPipelineProcessors(serviceCollection);
			ConfigureOtherServices(serviceCollection);
		}

		private void ConfigureCachers(IServiceCollection serviceCollection)
		{
			serviceCollection.AddSingleton<ITypeCacher, TypeCacher>();
		}

		private void ConfigureFactories(IServiceCollection serviceCollection)
		{
			serviceCollection.AddSingleton<IItemResolverArgumentsFactory, ItemResolverArgumentsFactory>();
			serviceCollection.AddSingleton<ITypeResolverArgumentsFactory, TypeResolverArgumentsFactory>();
			serviceCollection.AddSingleton<IObjectLocatorArgumentsFactory, ObjectLocatorArgumentsFactory>();
			serviceCollection.AddSingleton<IObjectCreatorArgumentsFactory, ObjectCreatorArgumentsFactory>();
			serviceCollection.AddSingleton<IObjectResolverArgumentsFactory, ObjectResolverArgumentsFactory>();
		}

		private void ConfigureItemResolvers(IServiceCollection serviceCollection)
		{
			serviceCollection.AddSingleton<IDatabaseItemResolver, DatabaseItemResolver>();
			serviceCollection.AddSingleton(GetItemResolverServiceSetting);
			serviceCollection.AddSingleton<IItemResolverService, ItemResolverService>();
		}

		private ItemResolverServiceSettings GetItemResolverServiceSetting(IServiceProvider provider)
		{
			return CreateConfigObject<ItemResolverServiceSettings>(provider, "moduleSettings/foundation/objectResolution/itemResolverServiceSettings");
		}

		private void ConfigureTypeResolvers(IServiceCollection serviceCollection)
		{
			serviceCollection.AddSingleton<IReflectionTypeResolver, ReflectionTypeResolver>();
			serviceCollection.AddSingleton(GetTypeResolverServiceSettings);
			serviceCollection.AddSingleton<ITypeResolverService, TypeResolverService>();
		}

		private TypeResolverServiceSettings GetTypeResolverServiceSettings(IServiceProvider provider)
		{
			return CreateConfigObject<TypeResolverServiceSettings>(provider, "moduleSettings/foundation/objectResolution/typeResolverServiceSettings");
		}

		private void ConfigureObjectCreators(IServiceCollection serviceCollection)
		{
			serviceCollection.AddSingleton<IReflectionObjectCreator, ReflectionObjectCreator>();
			serviceCollection.AddSingleton(GetObjectCreatorServiceSettings);
			serviceCollection.AddSingleton<IObjectCreatorService, ObjectCreatorService>();
		}

		private ObjectCreatorServiceSettings GetObjectCreatorServiceSettings(IServiceProvider provider)
		{
			return CreateConfigObject<ObjectCreatorServiceSettings>(provider, "moduleSettings/foundation/objectResolution/objectCreatorServiceSettings");
		}

		private void ConfigureObjectLocators(IServiceCollection serviceCollection)
		{
			serviceCollection.AddSingleton<IServiceProviderLocator, ServiceProviderLocator>();
			serviceCollection.AddSingleton(GetObjectLocatorServiceSettings);
			serviceCollection.AddSingleton<IObjectLocatorService, ObjectLocatorService>();
		}

		private ObjectLocatorServiceSettings GetObjectLocatorServiceSettings(IServiceProvider provider)
		{
			return CreateConfigObject<ObjectLocatorServiceSettings>(provider, "moduleSettings/foundation/objectResolution/objectLocatorServiceSettings");
		}

		private void ConfigureObjectResolvers(IServiceCollection serviceCollection)
		{
			serviceCollection.AddSingleton<IReflectionObjectCreator, ReflectionObjectCreator>();
			serviceCollection.AddSingleton(GetObjectResolverServiceSettings);
			serviceCollection.AddSingleton<IObjectResolver, ObjectResolverService>();
		}

		private ObjectResolverServiceSettings GetObjectResolverServiceSettings(IServiceProvider provider)
		{
			return CreateConfigObject<ObjectResolverServiceSettings>(provider, "moduleSettings/foundation/objectResolution/objectResolverServiceSettings");
		}

		private void ConfigureResolveItemPipelineProcessors(IServiceCollection serviceCollection)
		{
			serviceCollection.AddSingleton<IAddDefaultItemResolver, AddDefaultItemResolver>();
			serviceCollection.AddSingleton<IResolveItem, ResolveItem>();
		}

		private void ConfigureResolveTypePipelineProcessors(IServiceCollection serviceCollection)
		{
			serviceCollection.AddSingleton<ISetItemResolver, SetItemResolver>();
			serviceCollection.AddSingleton<Pipelines.ResolveType.ResolveTypeProcessor.IResolveItem, Pipelines.ResolveType.ResolveTypeProcessor.ResolveItem>();
			serviceCollection.AddSingleton<ISetTypeName, SetTypeName>();
			serviceCollection.AddSingleton<IAddDefaultTypeResolver, AddDefaultTypeResolver>();
			serviceCollection.AddSingleton<Pipelines.ResolveType.SetTypeCacherProcessor.ISetTypeCacher, Pipelines.ResolveType.SetTypeCacherProcessor.SetTypeCacher>();
			serviceCollection.AddSingleton<Pipelines.ResolveType.ResolveTypeProcessor.IResolveType, Pipelines.ResolveType.ResolveTypeProcessor.ResolveType>();
		}

		private void ConfigureLocateObjectPipelineProcessors(IServiceCollection serviceCollection)
		{
			serviceCollection.AddSingleton<Pipelines.LocateObject.SetTypeResolverProcessor.ISetTypeResolver, Pipelines.LocateObject.SetTypeResolverProcessor.SetTypeResolver>();
			serviceCollection.AddSingleton<Pipelines.LocateObject.ResolveTypeProcessor.IResolveType, Pipelines.LocateObject.ResolveTypeProcessor.ResolveType>();
			serviceCollection.AddSingleton<IAddDefaultObjectLocator, AddDefaultObjectLocator>();
			serviceCollection.AddSingleton<ILocateObject, LocateObject>();
		}

		private void ConfigureCreateObjectPipelineProcessors(IServiceCollection serviceCollection)
		{
			serviceCollection.AddSingleton<ISetTypeResolver, SetTypeResolver>();
			serviceCollection.AddSingleton<IResolveType, ResolveType>();
			serviceCollection.AddSingleton<IAddDefaultObjectCreator, AddDefaultObjectCreator>();
			serviceCollection.AddSingleton<ICreateObject, CreateObject>();
			serviceCollection.AddSingleton<ISetTypeCacher, SetTypeCacher>();
			serviceCollection.AddSingleton<ICacheType, CacheType>();
		}

		private void ConfigureResolveObjectPipelineProcessors(IServiceCollection serviceCollection)
		{
			serviceCollection.AddSingleton<Pipelines.ResolveObject.ISetTypeResolver, Pipelines.ResolveObject.SetTypeResolver>();
			serviceCollection.AddSingleton<Pipelines.ResolveObject.ResolveTypeProcessor.IResolveType, Pipelines.ResolveObject.ResolveTypeProcessor.ResolveType>();
			serviceCollection.AddSingleton<ISetObjectLocator, SetObjectLocator>();
			serviceCollection.AddSingleton<Pipelines.ResolveObject.LocateObjectProcessor.ILocateObject, Pipelines.ResolveObject.LocateObjectProcessor.LocateObject>();
			serviceCollection.AddSingleton<ISetObjectCreator, SetObjectCreator>();
			serviceCollection.AddSingleton<Pipelines.ResolveObject.CreateObjectProcessor.ICreateObject, Pipelines.ResolveObject.CreateObjectProcessor.CreateObject>();
		}

		private void ConfigureOtherServices(IServiceCollection serviceCollection)
		{
			serviceCollection.AddSingleton<IReflectionUtilService, ReflectionUtilService>();
		}

		private TConfigObject CreateConfigObject<TConfigObject>(IServiceProvider provider, string path) where TConfigObject : class
		{
			BaseFactory factory = GetService<BaseFactory>(provider);
			return factory.CreateObject(path, true) as TConfigObject;
		}

		private TService GetService<TService>(IServiceProvider provider)
		{
			return provider.GetService<TService>();
		}
	}
}

Finally, I strung all the pieces together using the following Sitecore patch config file:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
	<sitecore>
		<services>
			<configurator type="Sandbox.Foundation.ObjectResolution.ObjectResolutionConfigurator, Sandbox.Foundation.ObjectResolution" />
		</services>

		<pipelines>
			<resolveItem>
				<processor type="Sandbox.Foundation.ObjectResolution.Pipelines.ResolveItem.AddDefaultItemResolverProcessor.IAddDefaultItemResolver, Sandbox.Foundation.ObjectResolution" resolve="true" />
				<processor type="Sandbox.Foundation.ObjectResolution.Pipelines.ResolveItem.ResolveItemProcessor.IResolveItem, Sandbox.Foundation.ObjectResolution" resolve="true" />
			</resolveItem>
			<resolveType>
				<processor type="Sandbox.Foundation.ObjectResolution.Pipelines.ResolveType.SetItemResolverProcessor.ISetItemResolver, Sandbox.Foundation.ObjectResolution" resolve="true" />
				<processor type="Sandbox.Foundation.ObjectResolution.Pipelines.ResolveType.ResolveTypeProcessor.IResolveItem, Sandbox.Foundation.ObjectResolution" resolve="true" />
				<processor type="Sandbox.Foundation.ObjectResolution.Pipelines.ResolveType.SetTypeNameProcessor.ISetTypeName, Sandbox.Foundation.ObjectResolution" resolve="true" />
				<processor type="Sandbox.Foundation.ObjectResolution.Pipelines.ResolveType.AddDefaultTypeResolverProcessor.IAddDefaultTypeResolver, Sandbox.Foundation.ObjectResolution" resolve="true" />
				<processor type="Sandbox.Foundation.ObjectResolution.Pipelines.ResolveType.SetTypeCacherProcessor.ISetTypeCacher, Sandbox.Foundation.ObjectResolution" resolve="true" />
				<processor type="Sandbox.Foundation.ObjectResolution.Pipelines.ResolveType.ResolveTypeProcessor.IResolveType, Sandbox.Foundation.ObjectResolution" resolve="true" />
			</resolveType>
			<locateObject>
				<processor type="Sandbox.Foundation.ObjectResolution.Pipelines.LocateObject.SetTypeResolverProcessor.ISetTypeResolver, Sandbox.Foundation.ObjectResolution" resolve="true" />
				<processor type="Sandbox.Foundation.ObjectResolution.Pipelines.LocateObject.ResolveTypeProcessor.IResolveType, Sandbox.Foundation.ObjectResolution" resolve="true" />
				<processor type="Sandbox.Foundation.ObjectResolution.Pipelines.LocateObject.AddDefaultObjectLocatorProcessor.IAddDefaultObjectLocator, Sandbox.Foundation.ObjectResolution" resolve="true" />
				<processor type="Sandbox.Foundation.ObjectResolution.Pipelines.LocateObject.LocateObjectProcessor.ILocateObject, Sandbox.Foundation.ObjectResolution" resolve="true" />
			</locateObject>
			<createObject>
				<processor type="Sandbox.Foundation.ObjectResolution.Pipelines.CreateObject.SetTypeResolverProcessor.ISetTypeResolver, Sandbox.Foundation.ObjectResolution" resolve="true" />
				<processor type="Sandbox.Foundation.ObjectResolution.Pipelines.CreateObject.ResolveTypeProcessor.IResolveType, Sandbox.Foundation.ObjectResolution" resolve="true" />
				<processor type="Sandbox.Foundation.ObjectResolution.Pipelines.CreateObject.AddDefaultObjectCreatorProcessor.IAddDefaultObjectCreator, Sandbox.Foundation.ObjectResolution" resolve="true" />
				<processor type="Sandbox.Foundation.ObjectResolution.Pipelines.CreateObject.CreateObjectProcessor.ICreateObject, Sandbox.Foundation.ObjectResolution" resolve="true" />
				<processor type="Sandbox.Foundation.ObjectResolution.Pipelines.CreateObject.SetTypeCacherProcessor.ISetTypeCacher, Sandbox.Foundation.ObjectResolution" resolve="true" />
				<processor type="Sandbox.Foundation.ObjectResolution.Pipelines.CreateObject.CacheTypeProcessor.ICacheType, Sandbox.Foundation.ObjectResolution" resolve="true" />
			</createObject>
			<resolveObject>
				<processor type="Sandbox.Foundation.ObjectResolution.Pipelines.ResolveObject.ISetTypeResolver, Sandbox.Foundation.ObjectResolution" resolve="true" />
				<processor type="Sandbox.Foundation.ObjectResolution.Pipelines.ResolveObject.ResolveTypeProcessor.IResolveType, Sandbox.Foundation.ObjectResolution" resolve="true" />
				<processor type="Sandbox.Foundation.ObjectResolution.Pipelines.ResolveObject.SetObjectLocatorProcessor.ISetObjectLocator, Sandbox.Foundation.ObjectResolution" resolve="true" />
				<processor type="Sandbox.Foundation.ObjectResolution.Pipelines.ResolveObject.LocateObjectProcessor.ILocateObject, Sandbox.Foundation.ObjectResolution" resolve="true" />
				<processor type="Sandbox.Foundation.ObjectResolution.Pipelines.ResolveObject.SetObjectCreatorProcessor.ISetObjectCreator, Sandbox.Foundation.ObjectResolution" resolve="true" />
				<processor type="Sandbox.Foundation.ObjectResolution.Pipelines.ResolveObject.CreateObjectProcessor.ICreateObject, Sandbox.Foundation.ObjectResolution" resolve="true" />
			</resolveObject>
		</pipelines>

		<moduleSettings>
			<foundation>
				<objectResolution>
					<itemResolverServiceSettings type="Sandbox.Foundation.ObjectResolution.Models.Resolvers.ItemResolvers.ItemResolverServiceSettings, Sandbox.Foundation.ObjectResolution" singleInstance="true">
						<ResolveItemPipelineName>resolveItem</ResolveItemPipelineName>
					</itemResolverServiceSettings>
					<typeResolverServiceSettings type="Sandbox.Foundation.ObjectResolution.Models.Resolvers.TypeResolvers.TypeResolverServiceSettings, Sandbox.Foundation.ObjectResolution" singleInstance="true">
						<ResolveTypePipelineName>resolveType</ResolveTypePipelineName>
					</typeResolverServiceSettings>
					<objectLocatorServiceSettings type="Sandbox.Foundation.ObjectResolution.Models.Resolvers.ObjectLocators.ObjectLocatorServiceSettings, Sandbox.Foundation.ObjectResolution" singleInstance="true">
						<LocateObjectPipelineName>locateObject</LocateObjectPipelineName>
					</objectLocatorServiceSettings>
					<objectCreatorServiceSettings type="Sandbox.Foundation.ObjectResolution.Models.Resolvers.ObjectCreators.ObjectCreatorServiceSettings, Sandbox.Foundation.ObjectResolution" singleInstance="true">
						<CreateObjectPipelineName>createObject</CreateObjectPipelineName>
					</objectCreatorServiceSettings>
					<objectResolverServiceSettings type="Sandbox.Foundation.ObjectResolution.Models.Resolvers.ObjectResolvers.ObjectResolverServiceSettings, Sandbox.Foundation.ObjectResolution" singleInstance="true">
						<ResolveObjectPipelineName>resolveObject</ResolveObjectPipelineName>
						<UseTypeCache>true</UseTypeCache>
					</objectResolverServiceSettings>
				</objectResolution>
			</foundation>
		</moduleSettings>
</sitecore>
</configuration>

In my next post, I will be using the entire system above for resolving custom Forms Submit Actions from the Sitecore IoC container. Stay tuned for that post.

If you have made it this far, hats off to you. 😉

Sorry for throwing so much at you, but that’s what I do. 😉

On closing, I would like to mention that the system above could be used for resolving types from the Sitecore IoC container for WFFM but this is something that I will not investigate. If you happen to get this to work on WFFM, please share in a comment below.

Until next time, keep on Sitecoring.

Advertisements

Encrypt Sitecore Experience Forms Data in Powerful Ways

Last week, I was honoured to co-present Sitecore Experience Forms alongside my dear friend — and fellow trollster 😉 — Sitecore MVP Kamruz Jaman at SUGCON EU 2018. We had a blast showing the ins and outs of Experience Forms, and of course trolled a bit whilst on the main stage.

During our talk, Kamruz had mentioned the possibility of replacing the “Out of the Box” (OOTB) Sitecore.ExperienceForms.Data.IFormDataProvider — this lives in Sitecore.ExperienceForms.dll — whose class implementations serve as Repository objects for storing or retrieving from some datastore (in Experience Forms this is MS SQL Server OOTB) with another to encrypt/decrypt data when saving to or retrieving from the datastore.

Well, I had done something exactly like this for Web Forms for Marketers (WFFM) about five years ago — be sure to have a read my blog post on this before proceeding as it gives context to the rest of this blog post — so thought it would be appropriate for me to have a swing at doing this for Experience Forms.

I first created an interface for classes that will encrypt/decrypt strings — this is virtually the same interface I had used in my older post on encrypting data in WFFM:

namespace Sandbox.Foundation.Forms.Services.Encryption
{
	public interface IEncryptor
	{
		string Encrypt(string key, string input);

		string Decrypt(string key, string input);
	}
}

I then created a class to encrypt/decrypt strings using the RC2 encryption algorithm — I had also poached this from my older post on encrypting data in WFFM (please note, this encryption algorithm is not the most robust so do not use this in any production environment. Please be sure to use something more robust):

using System.Text;
using System.Security.Cryptography;

namespace Sandbox.Foundation.Forms.Services.Encryption
{
	public class RC2Encryptor : IEncryptor
	{
		public string Encrypt(string key, string input)
		{
			byte[] inputArray = UTF8Encoding.UTF8.GetBytes(input);
			RC2CryptoServiceProvider rc2 = new RC2CryptoServiceProvider();
			rc2.Key = UTF8Encoding.UTF8.GetBytes(key);
			rc2.Mode = CipherMode.ECB;
			rc2.Padding = PaddingMode.PKCS7;
			ICryptoTransform cTransform = rc2.CreateEncryptor();
			byte[] resultArray = cTransform.TransformFinalBlock(inputArray, 0, inputArray.Length);
			rc2.Clear();
			return System.Convert.ToBase64String(resultArray, 0, resultArray.Length);
		}

		public string Decrypt(string key, string input)
		{
			byte[] inputArray = System.Convert.FromBase64String(input);
			RC2CryptoServiceProvider rc2 = new RC2CryptoServiceProvider();
			rc2.Key = UTF8Encoding.UTF8.GetBytes(key);
			rc2.Mode = CipherMode.ECB;
			rc2.Padding = PaddingMode.PKCS7;
			ICryptoTransform cTransform = rc2.CreateDecryptor();
			byte[] resultArray = cTransform.TransformFinalBlock(inputArray, 0, inputArray.Length);
			rc2.Clear();
			return UTF8Encoding.UTF8.GetString(resultArray);
		}
	}
}

Next, I created the following class to store settings I need for encrypting and decrypting data using the RC2 algorithm class above:

namespace Sandbox.Foundation.Forms.Models
{
	public class FormEncryptionSettings
	{
		public string EncryptionKey { get; set; }
	}
}

The encryption key above is needed for the RC2 algorithm to encrypt/decrypt data. I set this key in a config object defined in a Sitecore patch configuration file towards the bottom of this post.

I then created an interface for classes that will encrypt/decrypt FormEntry instances (FormEntry objects contain submitted data from form submissions):

using Sitecore.ExperienceForms.Data.Entities;

namespace Sandbox.Foundation.Forms.Services.Encryption
{
	public interface IFormEntryEncryptor
	{
		void EncryptFormEntry(FormEntry entry);

		void DecryptFormEntry(FormEntry entry);
	}
}

The following class implements the interface above:

using System.Linq;

using Sitecore.ExperienceForms.Data.Entities;

using Sandbox.Foundation.Forms.Models;

namespace Sandbox.Foundation.Forms.Services.Encryption
{
	public class FormEntryEncryptor : IFormEntryEncryptor
	{
		private readonly FormEncryptionSettings _formEncryptionSettings;
		private readonly IEncryptor _encryptor;

		public FormEntryEncryptor(FormEncryptionSettings formEncryptionSettings, IEncryptor encryptor)
		{
			_formEncryptionSettings = formEncryptionSettings;
			_encryptor = encryptor;
		}

		public void EncryptFormEntry(FormEntry entry)
		{
			if (!HasFields(entry))
			{
				return;
			}

			foreach (FieldData field in entry.Fields)
			{
				EncryptField(field);
			}
		}

		protected virtual void EncryptField(FieldData field)
		{
			if(field == null)
			{
				return;
			}

			field.FieldName = Encrypt(field.FieldName);
			field.Value = Encrypt(field.Value);
			field.ValueType = Encrypt(field.ValueType);
		}

		protected virtual string Encrypt(string input)
		{
			return _encryptor.Encrypt(_formEncryptionSettings.EncryptionKey, input);
		}

		public void DecryptFormEntry(FormEntry entry)
		{
			if (!HasFields(entry))
			{
				return;
			}

			foreach (FieldData field in entry.Fields)
			{
				DecryptField(field);
			}
		}

		protected virtual bool HasFields(FormEntry entry)
		{
			return entry != null
					&& entry.Fields != null
					&& entry.Fields.Any();
		}

		protected virtual void DecryptField(FieldData field)
		{
			if(field == null)
			{
				return;
			}

			field.FieldName = Decrypt(field.FieldName);
			field.Value = Decrypt(field.Value);
			field.ValueType = Decrypt(field.ValueType);
		}

		protected virtual string Decrypt(string input)
		{
			return _encryptor.Decrypt(_formEncryptionSettings.EncryptionKey, input);
		}
	}
}

The EncryptFormEntry() method above iterates over all FieldData objects contained on the FormEntry instance, and passes them to the EncryptField() mehod which encrypts the FieldName, Value and ValueType properties on them.

Likewise, the DecryptFormEntry() method iterates over all FieldData objects contained on the FormEntry instance, and passes them to the DecryptField() mehod which decrypts the same properties mentioned above.

I then created an interface for classes that will serve as factories for IFormDataProvider instances:

using Sitecore.ExperienceForms.Data;
using Sitecore.ExperienceForms.Data.SqlServer;

namespace Sandbox.Foundation.Forms.Services.Factories
{
	public interface IFormDataProviderFactory
	{
		IFormDataProvider CreateNewSqlFormDataProvider(ISqlDataApiFactory sqlDataApiFactory);
	}
}

The following class implements the interface above:

using Sitecore.ExperienceForms.Data;
using Sitecore.ExperienceForms.Data.SqlServer;

namespace Sandbox.Foundation.Forms.Services.Factories
{
	public class FormDataProviderFactory : IFormDataProviderFactory
	{
		public IFormDataProvider CreateNewSqlFormDataProvider(ISqlDataApiFactory sqlDataApiFactory)
		{
			return new SqlFormDataProvider(sqlDataApiFactory);
		}
	}
}

The CreateNewSqlFormDataProvider() method above does exactly was the method name says. You’ll see it being used in the following class below.

This next class ultimately becomes the new IFormDataProvider instance but decorates the OOTB one which is created from the factory class above:

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


using Sitecore.ExperienceForms.Data;
using Sitecore.ExperienceForms.Data.Entities;
using Sitecore.ExperienceForms.Data.SqlServer;

using Sandbox.Foundation.Forms.Services.Encryption;
using Sandbox.Foundation.Forms.Services.Factories;

namespace Sandbox.Foundation.Forms.Services.Data
{
	public class FormEncryptionDataProvider : IFormDataProvider
	{
		private readonly IFormDataProvider _innerProvider;
		private readonly IFormEntryEncryptor _formEntryEncryptor;

		public FormEncryptionDataProvider(ISqlDataApiFactory sqlDataApiFactory, IFormDataProviderFactory formDataProviderFactory, IFormEntryEncryptor formEntryEncryptor)
		{
			_innerProvider = CreateInnerProvider(sqlDataApiFactory, formDataProviderFactory);
			_formEntryEncryptor = formEntryEncryptor;
		}

		protected virtual IFormDataProvider CreateInnerProvider(ISqlDataApiFactory sqlDataApiFactory, IFormDataProviderFactory formDataProviderFactory)
		{
			return formDataProviderFactory.CreateNewSqlFormDataProvider(sqlDataApiFactory);
		}

		public void CreateEntry(FormEntry entry)
		{
			EncryptFormEntryField(entry);
			_innerProvider.CreateEntry(entry);
		}

		protected virtual void EncryptFormEntryField(FormEntry entry)
		{
			_formEntryEncryptor.EncryptFormEntry(entry);
		}

		public void DeleteEntries(Guid formId)
		{
			_innerProvider.DeleteEntries(formId);
		}

		public IReadOnlyCollection<FormEntry> GetEntries(Guid formId, DateTime? startDate, DateTime? endDate)
		{
			IReadOnlyCollection<FormEntry>  entries = _innerProvider.GetEntries(formId, startDate, endDate);
			if(entries == null || !entries.Any())
			{
				return entries;
			}

			foreach(FormEntry entry in entries)
			{
				DecryptFormEntryField(entry);
			}

			return entries;
		}

		protected virtual void DecryptFormEntryField(FormEntry entry)
		{
			_formEntryEncryptor.DecryptFormEntry(entry);
		}
	}
}

The class above does delegation to the IFormEntryEncryptor instance to encrypt the FormEntry data and then passes the FormEntry to the inner provider for saving.

For decrypting, it retrieves the data from the inner provider, and then decrypts it via the IFormEntryEncryptor instance before returning to the caller.

Finally, I created an IServicesConfigurator class to wire everything up into the Sitecore container (I hope you are using Sitecore Dependency Injection capabilities as this comes OOTB — there are no excuses for not using this!!!!!!):

using System;

using Microsoft.Extensions.DependencyInjection;

using Sitecore.Abstractions;
using Sitecore.DependencyInjection;
using Sitecore.ExperienceForms.Data;

using Sandbox.Foundation.Forms.Models;
using Sandbox.Foundation.Forms.Services.Encryption;
using Sandbox.Foundation.Forms.Services.Data;
using Sandbox.Foundation.Forms.Services.Factories;

namespace Sandbox.Foundation.Forms
{
	public class FormsServicesConfigurator : IServicesConfigurator
	{
		public void Configure(IServiceCollection serviceCollection)
		{
			serviceCollection.AddSingleton(provider => GetFormEncryptionSettings(provider));
			serviceCollection.AddSingleton<IEncryptor, RC2Encryptor>();
			serviceCollection.AddSingleton<IFormEntryEncryptor, FormEntryEncryptor>();
			serviceCollection.AddSingleton<IFormDataProviderFactory, FormDataProviderFactory>();
			serviceCollection.AddSingleton<IFormDataProvider, FormEncryptionDataProvider>();
		}

		private FormEncryptionSettings GetFormEncryptionSettings(IServiceProvider provider)
		{
			return CreateConfigObject<FormEncryptionSettings>(provider, "moduleSettings/foundation/forms/formEncryptionSettings");
		}

		private TConfigObject CreateConfigObject<TConfigObject>(IServiceProvider provider, string path) where TConfigObject : class
		{
			BaseFactory factory = GetService<BaseFactory>(provider);
			return factory.CreateObject(path, true) as TConfigObject;
		}

		private TService GetService<TService>(IServiceProvider provider)
		{
			return provider.GetService<TService>();
		}
	}
}

Everything above is normal service class registration except for the stuff in the GetFormEncryptionSettings() method. Here, I’m creating an instance of a FormEncryptionSettings class but am instantiating it using the Sitecore Configuration Factory for the configuration object defined in the Sitecore patch configuration file below, and am making that available for being injected into classes that need it (the FormEntryEncryptor above uses it).

I then wired everything together using the following Sitecore patch configuration file:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
	<sitecore>
		<services>
			<configurator type="Sandbox.Foundation.Forms.FormsServicesConfigurator, Sandbox.Foundation.Forms" />
			<register serviceType="Sitecore.ExperienceForms.IFormDataProvider, Sitecore.ExperienceForms">
				<patch:delete />
			</register>
		</services>
		<moduleSettings>
			<foundation>
				<forms>
					<formEncryptionSettings type="Sandbox.Foundation.Forms.Models.FormEncryptionSettings, Sandbox.Foundation.Forms" singleInstance="true">
						<!-- I stole this from https://sitecorejunkie.com/2013/06/21/encrypt-web-forms-for-marketers-fields-in-sitecore/ -->
						<EncryptionKey>88bca90e90875a</EncryptionKey>
					</formEncryptionSettings>
				</forms>
			</foundation>
		</moduleSettings>
	</sitecore>
</configuration>

I want to call out that I’m deleting the OOTB IFormDataProvider above using a patch:delete. I’m re-adding it via the IServicesConfigurator above using the decorator class previously shown above.

Let’s take this for a spin.

I first created a new form (this is under “Forms” on the Sitecore Launchepad ):

I then put it on a page with an MVC Layout; published everything; navigated to the test page with the form created above; filled out the form; and then clicked the submit button:

Let’s see if the data was encrypted. I opened up SQL Server Management Studio and ran a query on the FormEntry table in my Experience Forms Database:

As you can see the data was encrypted.

Let’s export the data to make sure it gets decrypted. We can do that by exporting the data as a CSV from Forms in the Sitecore Launchpad:

As you can see the data is decrypted in the CSV:

I do want to mention that Sitecore MVP João Neto had provided two other methods for encrypting data in Experience Forms in a post he wrote last January. I recommend having a read of that.

Until next time, see you on the Sitecore Slack 😉

Bundle CSS and JavaScript Files in Sitecore MVC

The other day I was poking around Sitecore.Forms.Mvc.dll — this assembly ships with Web Forms for Marketers (WFFM), and is used when WFFM is running on Sitecore MVC — and noticed WFFM does some bundling of JavaScript and CSS files:

wffm-bundling

WFFM uses the above class as an <initialize> pipeline processor. You can see this defined in Sitecore.Forms.Mvc.config:

Sitecore-Forms-Mvc-config

This got me thinking: why not build my own class to serve as an <initialize> pipeline processor to bundle my CSS and JavaScript files?

As an experiment I whipped up the following class to do just that:

using System.Collections.Generic;
using System.Linq;
using System.Web.Optimization;

using Sitecore.Pipelines;

namespace Sitecore.Sandbox.Forms.Mvc.Pipelines
{         
    public class RegisterAdditionalFormBundles
    {
        public RegisterAdditionalFormBundles()
        {
            CssFiles = new List<string>();
            JavaScriptFiles = new List<string>();
        }

        public void Process(PipelineArgs args)
        {
            BundleCollection bundles = GetBundleCollection();
            if (bundles == null)
            {
                return;
            }

            AddBundle(bundles, CreateCssBundle());
            AddBundle(bundles, CreateJavaScriptBundle());
        }

        protected virtual BundleCollection GetBundleCollection()
        {
            return BundleTable.Bundles;
        }

        protected virtual Bundle CreateCssBundle()
        {
            if (!CanBundleAssets(CssVirtualPath, CssFiles))
            {
                return null;
            }

            return new StyleBundle(CssVirtualPath).Include(CssFiles.ToArray());
        }

        protected virtual Bundle CreateJavaScriptBundle()
        {
            if (!CanBundleAssets(JavaScriptVirtualPath, JavaScriptFiles))
            {
                return null;
            }

            return new ScriptBundle(JavaScriptVirtualPath).Include(JavaScriptFiles.ToArray());
        }

        protected virtual bool CanBundleAssets(string virtualPath, IEnumerable<string> filePaths)
        {
            return !string.IsNullOrWhiteSpace(virtualPath)
                    && filePaths != null
                    && filePaths.Any();
        }

        private static void AddBundle(BundleCollection bundles, Bundle bundle)
        {
            if(bundle == null)
            {
                return;
            }

            bundles.Add(bundle);
        }

        private string CssVirtualPath { get; set; }

        private List<string> CssFiles { get; set; }

        private string JavaScriptVirtualPath { get; set; }

        private List<string> JavaScriptFiles { get; set; }
    }
}

The class above basically takes in a collection of CSS and JavaScript file paths as well as their virtual bundled paths — these are magically populated by Sitecore’s Configuration Factory using values provided by the configuration file shown below — iterates over both collections, and adds them to the BundleTable — the BundleTable is defined in System.Web.Optimization.dll.

I then glued everything together using a patch configuration file:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <initialize>
        <processor patch:after="processor[@type='Sitecore.Forms.Mvc.Pipelines.RegisterFormBundles, Sitecore.Forms.Mvc']"
          type="Sitecore.Sandbox.Forms.Mvc.Pipelines.RegisterAdditionalFormBundles, Sitecore.Sandbox">
          <CssVirtualPath>~/wffm-bundles/styles.css</CssVirtualPath>
          <CssFiles hint="list">
            <CssFile>~/css/uniform.aristo.css</CssFile>
          </CssFiles>
          <JavaScriptVirtualPath>~/wffm-bundles/scripts.js</JavaScriptVirtualPath>
          <JavaScriptFiles hint="list">
            <JavaScriptFile>~/js/jquery.min.js</JavaScriptFile>
            <JavaScriptFile>~/js/jquery.uniform.min.js</JavaScriptFile>
            <JavaScriptFile>~/js/bind.uniform.js</JavaScriptFile>
          </JavaScriptFiles>
        </processor>
      </initialize>
    </pipelines>
  </sitecore>
</configuration>

I’m adding the <initialize> pipeline processor shown above after WFFM’s though theoretically you could add it anywhere within the <initialize> pipeline.

The CSS and JavaScript files defined in the configuration file above are from the Uniform project — this project includes CSS, JavaScript and images to make forms look nice, though I am in no way endorsing this project. I only needed some CSS and JavaScript files to spin up something quickly for testing.

For testing, I built the following View — it uses some helpers to render the <link> and <script> tags for the bundles — and tied it to my Layout in Sitecore:

@using System.Web.Optimization
@using Sitecore.Mvc
@using Sitecore.Mvc.Presentation

<!DOCTYPE html>
<html>

<head>
    <title></title>

    @Styles.Render("~/wffm-bundles/styles.css")
    @Scripts.Render("~/wffm-bundles/scripts.js")
    
</head>
<body>
    

    @Html.Sitecore().Placeholder("page content")

</body>
</html>

I then built a “Feedback” form in WFFM; mapped it to the “page content” placeholder defined in the View above; published it; and pulled it up in my browser. As you can see the code from the Uniform project styled the form:

styled-form

For comparison, this is what the form looks like without the <initialize> pipeline processor above:

2014-10-30_2316

If you have any thoughts on this, or have alternative ways of bundling CSS and JavaScript files in your Sitecore MVC solutions, please share in a comment.

Turn Off the Attaching of Files in Emails Sent by Web Forms for Marketers in Sitecore

The other day Michael West pinged me to see if I could help someone in one of the SDN forums via this tweet:

The poster had asked if there were an easy way to disable the attachment of files on emails sent by the Web Forms for Marketers (WFFM) module in Sitecore.

After quickly peeking in the WFFM assemblies, I gave a potential solution — check out the thread to see what it was — though it did not work.

Tonight I looked further into how to accomplish this, and came up with the following solution:

using System;
using System.Net;

using Sitecore.Data;
using Sitecore.Form.Core.Client.Data.Submit;
using Sitecore.Form.Core.Pipelines.ProcessMessage;
using Sitecore.Pipelines;

namespace Sitecore.Sandbox.Form.Submit
{
    public class SendMessage : Sitecore.Form.Submit.SendMessage
    {
        protected override void ExecuteMail(ID form, AdaptedResultList fields)
        {
            ProcessMessageArgs args = new ProcessMessageArgs(form, fields, this.MessageType);
            base.To = base.To.TrimEnd(new char[] { ',', ';' });
            base.LocalFrom = base.From.TrimEnd(new char[] { ',', ';' });
            args.To.Append(base.To.Replace(";", ","));
            args.From = base.From.Replace(";", ",");

            // magically populated by WFFM via XML
            args.IncludeAttachment = IncludeAttachments; 

            args.Mail.Append(base.Mail);
            args.Subject.Append(base.Subject);
            args.Recipient = this.Recipient;
            args.RecipientGateway = this.RecipientGateway;
            args.Host = base.Host;
            args.Port = base.Port;
            args.IsBodyHtml = this.IsBodyHtml;
            args.EnableSsl = this.EnableSsl;
            args.Data.Add("FromPhone", this.FromPhone ?? string.Empty);
            string[] strArray = string.IsNullOrEmpty(base.Login) ? new string[] { string.Empty } : base.Login.Split(new char[] { '\\' });
            if ((strArray.Length > 0) && !string.IsNullOrEmpty(strArray[0]))
            {
                if ((strArray.Length == 2) && !string.IsNullOrEmpty(strArray[1]))
                {
                    args.Credentials = new NetworkCredential(strArray[1], base.Password, strArray[0]);
                }
                else
                {
                    args.Credentials = new NetworkCredential(strArray[0], base.Password);
                }
            }
            if (!string.IsNullOrEmpty(base.CC))
            {
                base.CC = base.CC.TrimEnd(new char[] { ',', ';' });
                args.CC.Append(base.CC.Replace(";", ","));
            }
            if (!string.IsNullOrEmpty(base.BCC))
            {
                base.BCC = base.BCC.TrimEnd(new char[] { ',', ';' });
                args.BCC.Append(base.BCC.Replace(";", ","));
            }
            CorePipeline.Run("processMessage", args);
        }

        // magically populated by WFFM via XML
        private bool IncludeAttachments { get; set; }
    }
}

The class above inherits from Sitecore.Form.Submit.SendMessage in Sitecore.Forms.Custom.dll, and overrides the ExecuteMail() method — this method is declared virtual in Sitecore.Form.Submit.SendMail which is the base class of Sitecore.Form.Submit.SendMessage.

Most of the code in ExecuteMail() above is from the ExecuteMail() method on Sitecore.Form.Submit.SendMessage, though with one minor difference: I have added a boolean property named “IncludeAttachments” which is magically populated by WFFM via an XML property declaration for this class:

wffm-no-attachments

I spun up a form quickly with some File Upload fields, and mapped the above Save Action to it:

form-save-action-no-attachment

I then navigated to my form, attached two images, and clicked the submit button:

attached-two-images

As you can see, the images were not attached to the email:

no-attachments-email

I then went back to my Save Action; set the <IncludeAttachments> XML element’s value to be true; saved the form Item; published it; and resubmitted the form:

with-attachments-email

As you can see, the images were attached this time.

If you have any thoughts on this, please drop a comment.

A Fix for the Hard-coded Submit Button Text in Web Forms for Marketers on Sitecore MVC

No doubt you have heard the latest version of Web Forms for Marketers — better known as WFFM — now works on Sitecore MVC — don’t know about you but I am quite excited over this!

Plus, as an added bonus — and this made my day when I installed it 😀 — you can change most of the rendered markup for forms in Views that ship with the module (these are installed into ~\Views\Form\EditorTemplates\ of your Sitecore instance).

However, the other day, I discovered the submit button text is hard-coded in one of the module’s Views:

submit-button-text-hardcoded

As you may know, the module does provide a field on Form Items to change the submit button text:

submit-button-text-form-item

Unfortunately, the value of this field is not being put into the value attribute of the submit button in the View above.

I have alerted Sitecore support of this issue via a ticket though do recommend modifying ~\Views\Form\EditorTemplates\FormModel.cshtml to use the submit button text set on the Form Item if you are using WFFM on Sitecore MVC:

submit-button-text-not-hardcoded

As you can see, the value of the submit button text field is set on a property of the model, and can be easily be put into the value attribute of the submit button.

If you have any thoughts on this, please share in a comment.

Restrict Certain Files from Being Uploaded Through Web Forms for Marketers Forms in Sitecore: an Alternative Approach

This past weekend I noticed the <formUploadFile> pipeline in Web Forms for Marketers (WFFM), and wondered whether I could create an alternative solution to the one I had shared in this post — I had built a custom WFFM field type to prevent certain files from being uploaded through WFFM forms.

After some tinkering, I came up the following class to serve as a <formUploadFile> pipeline processor:

using System;
using System.Collections.Generic;
using System.Web;

using Sitecore.Diagnostics;

using Sitecore.Form.Core.Pipelines.FormUploadFile;

namespace Sitecore.Sandbox.Form.Pipelines.FormUploadFile
{
    public class CheckMimeTypesNotAllowed
    {
        static CheckMimeTypesNotAllowed()
        {
            MimeTypesNotAllowed = new List<string>();
        }

        public void Process(FormUploadFileArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            string mimeType = GetMimeType(args.File.FileName);
            if (IsMimeTypeAllowed(mimeType))
            {
                return;
            }

            throw new Exception(string.Format("Uploading a file with MIME type {0} is not allowed", mimeType));
        }

        protected virtual string GetMimeType(string fileName)
        {
            Assert.ArgumentNotNullOrEmpty(fileName, "fileName");
            return MimeMapping.GetMimeMapping(fileName);
        }

        protected virtual bool IsMimeTypeAllowed(string mimeType)
        {
            foreach (string mimeTypeNotAllowed in MimeTypesNotAllowed)
            {
                if (AreEqualIgnoreCase(mimeTypeNotAllowed, mimeType))
                {
                    return false;
                }    
            }

            return true;
        }

        private static bool AreEqualIgnoreCase(string stringOne, string stringTwo)
        {
            return string.Equals(stringOne, stringTwo, StringComparison.CurrentCultureIgnoreCase);
        }

        private static void AddMimeTypeNotAllowed(string mimeType)
        {
            if (string.IsNullOrWhiteSpace(mimeType) || MimeTypesNotAllowed.Contains(mimeType))
            {
                return;
            }

            MimeTypesNotAllowed.Add(mimeType);
        }

        private static IList<string> MimeTypesNotAllowed { get; set; }
    }
}

The class above adds restricted MIME types to a list — these MIME types are defined in a configuration file shown below — and checks to see if the uploaded file’s MIME type is in the restricted list. If it is restricted, an exception is thrown with some information — throwing an exception in WFFM prevents the form from being submitted.

MIME types are inferred using System.Web.MimeMapping.GetMimeMapping(string fileName), a method that is new in .NET 4.5 (this solution will not work in older versions of .NET).

I then registered the class above as a <formUploadFile> pipeline processor via the following configuration file:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:x="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <formUploadFile>
        <processor patch:before="processor[@type='Sitecore.Form.Core.Pipelines.FormUploadFile.Save, Sitecore.Forms.Core']"
                   type="Sitecore.Sandbox.Form.Pipelines.FormUploadFile.CheckMimeTypesNotAllowed">
          <mimeTypesNotAllowed hint="list:AddMimeTypeNotAllowed">
            <mimeType>application/octet-stream</mimeType>
          </mimeTypesNotAllowed >
        </processor>  
      </formUploadFile>
    </pipelines>
  </sitecore>
</configuration>

In case you are wondering, the MIME type being passed to the processor in the configuration file is for executables.

Let’s see how we did. 😉

I whipped up a test form, and pulled it up in a browser:

form-for-testing

I then selected an executable:

select-executable

I saw this after an attempt to submit the form:

form-upload-exception

And my log file expressed why:

exception-in-log

I then copied a jpeg into my test folder, and selected it to be uploaded:

select-jpg

After clicking the submit button, I was given a nice confirmation message:

success-upload

There is one problem with the solution above that I would like to point out: it does not address the issue of file extensions being changed. I could not solve this problem since the MIME type of the file being uploaded cannot be determined from the Sitecore.Form.Core.Media.PostedFile instance set in the arguments object: there is no property for it. 😦

If you know of another way to determine MIME types on Sitecore.Form.Core.Media.PostedFile instances, or have other ideas for restricting certain files from being uploaded through WFFM, please share in a comment.

Show Submitted Web Forms for Marketers Form Field Values on a Confirmation Page in Sitecore

Recently on my About page, someone had asked me how to show submitted form field values in Web Forms for Marketers.

I had done such a thing in a past project, and thought I would share how I went about accomplishing this.

This solution reuses an instance of a storage class I had used in a previous post.

This is the interface for that storage class:

namespace Sitecore.Sandbox.Utilities.Storage
{
    public interface IRepository<TKey, TValue>
    {
        bool Contains(TKey key);

        TValue this[TKey key] { get; set; }

        void Put(TKey key, TValue value);

        void Remove(TKey key);

        void Clear();

        TValue Get(TKey key);
    }
}

This is the implementation of the storage class:

using System.Web;
using System.Web.SessionState;

using Sitecore.Diagnostics;

namespace Sitecore.Sandbox.Utilities.Storage
{
    public class SessionRepository : IRepository<string, object>
    {
        private HttpSessionStateBase Session { get; set; }

        public object this[string key]
        {
            get
            {
                return Get(key);
            }
            set
            {
                Put(key, value);
            }
        }

        public SessionRepository()
            : this(HttpContext.Current.Session)
        {
        }

        public SessionRepository(HttpSessionState session)
            : this(CreateNewHttpSessionStateWrapper(session))
        {
        }

        public SessionRepository(HttpSessionStateBase session)
        {
            SetSession(session);
        }

        private void SetSession(HttpSessionStateBase session)
        {
            Assert.ArgumentNotNull(session, "session");
            Session = session;
        }

        public bool Contains(string key)
        {
            return Session[key] != null;
        }

        public void Put(string key, object value)
        {
            Assert.ArgumentNotNullOrEmpty(key, "key");
            Assert.ArgumentCondition(IsSerializable(value), "value", "value must be serializable!");
            Session[key] = value;
        }

        private static bool IsSerializable(object instance)
        {
            Assert.ArgumentNotNull(instance, "instance");
            return instance.GetType().IsSerializable;
        }

        public void Remove(string key)
        {
            Session.Remove(key);
        }

        public void Clear()
        {
            Session.Clear();
        }

        public object Get(string key)
        {
            return Session[key];
        }

        private static HttpSessionStateWrapper CreateNewHttpSessionStateWrapper(HttpSessionState session)
        {
            Assert.ArgumentNotNull(session, "session");
            return new HttpSessionStateWrapper(session);
        }
    }
}

The class above basically serializes a supplied object, and puts it into session using a key given by the calling code.

Plus, you can remove objects saved in it using a key.

I modified this class from the original version: I declared the constructors public so that I can reference them in a Sitecore configuration file (you will see this configuration file further down in this post).

I then created a POCO to house form field values for serialization purposes:

using System;
using System.Collections.Generic;

namespace Sitecore.Sandbox.Form.Submit.DTO
{
    [Serializable]
    public class Field
    {
        public string Name { get; set; }

        public string Value { get; set; }
    }
}

Field values belong to a form, so I built another POCO class to store a collection of Sitecore.Sandbox.Form.Submit.DTO.Field class instances, and also hold on to the submitted form’s ID:

using System;
using System.Collections.Generic;

namespace Sitecore.Sandbox.Form.Submit.DTO
{
    [Serializable]
    public class FormSubmission
    {
        public Guid ID { get; set; }

        public List<Field> Fields { get; set; }
    }
}

Now we need a custom Web Forms for Marketers SaveAction — all custom SaveActions must implement Sitecore.Form.Core.Client.Data.Submit.ISaveAction which is defined in Sitecore.Forms.Core.dll — to create and store instances of the POCO classes defined above:

using System.Collections.Generic;

using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Diagnostics;
using Sitecore.Web;

using Sitecore.Form.Core.Client.Data.Submit;
using Sitecore.Form.Core.Controls.Data;
using Sitecore.Form.Submit;

using Sitecore.Sandbox.Form.Submit.DTO;
using Sitecore.Sandbox.Utilities.Storage;

namespace Sitecore.Sandbox.Form.Submit
{
    public class StoreForm : ISaveAction
    {
        static StoreForm()
        {
            RepositoryKey = Settings.GetSetting("RepositoryKey");
            Assert.IsNotNullOrEmpty(RepositoryKey, "RepositoryKey must be set in your configuration!");

            Repository = Factory.CreateObject("repository", true) as IRepository<string, object>;
            Assert.IsNotNull(Repository, "Repository must be set in your configuration!");
        }

        public void Execute(ID formid, AdaptedResultList fields, params object[] data)
        {
            StoreFormSubmission(formid, fields);
        }

        protected virtual void StoreFormSubmission(ID formid, AdaptedResultList fields)
        {
            FormSubmission form = CreateNewFormSubmission(formid, fields);
            Repository[GetRepositoryKey()] = form;
        }

        protected virtual FormSubmission CreateNewFormSubmission(ID formid, AdaptedResultList fields)
        {
            return new FormSubmission
            {
                ID = formid.Guid,
                Fields = CreateNewFields(fields)
            };
        }

        protected virtual List<Field> CreateNewFields(AdaptedResultList results)
        {
            Assert.ArgumentNotNull(results, "results");
            List<Field> fields = new List<Field>();
            foreach (AdaptedControlResult result in results)
            {
                fields.Add(new Field{ Name = result.FieldName, Value = result.Value });
            }

            return fields;
        }

        protected virtual string GetRepositoryKey()
        {
            return string.Concat(RepositoryKey, "_", WebUtil.GetSessionID());
        }

        private static string RepositoryKey { get; set; }
        
        private static IRepository<string, object> Repository { get; set; }
    }
}

The SaveAction above creates instances of the POCO classes above using the submitted form field values, and passes these to an instance of a IRepository: this is defined in the configuration file below jointly with a substring of the unique storage key (this is a concatenation of the key defined in the following configuration file and the user’s session ID to guarantee a unique key):

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <repository type="Sitecore.Sandbox.Utilities.Storage.SessionRepository" />
    <settings>
      <setting name="RepositoryKey" value="MyRepository"/>
    </settings>
  </sitecore>
</configuration>

I then registered the ISaveAction class above in Web Forms for Marketers:

store-form-save-action

I then wired it up to my test form:

add-store-form-to-form

For testing, I created the following sublayout — no, it’s not the prettiest code I have ever written but I needed something quick for testing — which I mapped to a confirmation page:

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Form Submission Confirmation.ascx.cs" Inherits="Sandbox.layouts.sublayouts.FormSubmissionConfirmation" %>
<asp:Repeater ID="rptConfirmation" runat="server">
    <HeaderTemplate>
        <h2>What you gave us:</h2>
    </HeaderTemplate>
    <ItemTemplate>
        <%# Eval("Name") %>: <%# Eval("Value") %>
    </ItemTemplate>
    <SeparatorTemplate>
        <br />
    </SeparatorTemplate>
</asp:Repeater>

The following class serves as the code-behind for the sublayout:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

using Sitecore.Configuration;
using Sitecore.Diagnostics;
using Sitecore.Sandbox.Form.Submit.DTO;
using Sitecore.Sandbox.Utilities.Storage;
using Sitecore.Web;

namespace Sandbox.layouts.sublayouts
{
    public partial class FormSubmissionConfirmation : System.Web.UI.UserControl
    {
        private static string RepositoryKey { get; set; }
        
        private static IRepository<string, object> Repository { get; set; }

        static FormSubmissionConfirmation()
        {
            RepositoryKey = Settings.GetSetting("RepositoryKey");
            Assert.IsNotNullOrEmpty(RepositoryKey, "RepositoryKey must be set in your configuration!");

            Repository = Factory.CreateObject("repository", true) as IRepository<string, object>;
            Assert.IsNotNull(Repository, "Repository must be set in your configuration!");
        }

        protected void Page_Load(object sender, EventArgs e)
        {
            string key = GetRepositoryKey();
            FormSubmission submission = Repository.Get(key) as FormSubmission;
            Repository.Remove(key);
            if (submission == null || !submission.Fields.Any())
            {
               Visible = false;
                return;
            }
            
            rptConfirmation.DataSource = submission.Fields;
            rptConfirmation.DataBind();
        }

        protected virtual string GetRepositoryKey()
        {
            return string.Concat(RepositoryKey, "_", WebUtil.GetSessionID());
        }
    }
}

The code-behind above gets the FormSubmission instance from the IRepository instance defined in the configuration file shown above, and passes the Field POCO instances within it to a repeater.

Let’s see this in action!

I navigated to my test form, and filled it in:
filled-in-form

After submitting the form, I was redirected to my confirmation page. As you can see the form values I had entered are displayed:

form-confirmation

One thing to note: the solution above only works when your Web Forms for Marketers confirmation page is its own page, and you set your form to redirect to it after submitting the form.

If you have any thoughts on this, or know of other ways to show submitted Web Forms for Marketers form field values on a confirmation page, please share in a comment.