In a previous post, I had mentioned primitive types cannot be injected into classes via Sitecore’s Configuration Factory, and further learned through exploring and experimentation that this applies to all value types in C#.
However, after some digging in Sitecore.Configuration.Factory — this lives in Sitecore.Kernel.dll — I discovered the CreateFromFactoryMethod() method which calls a static method — this method is defined in an XML attribute named “factoryMethod” — on a class defined in the “type” attribute on the XML configuration element being processed, and felt I could take advantage of this hidden gem for injecting value types into my configuration-defined classes.
Despite the fact the method defined in the “factoryMethod” attribute must be static, I decided to define an interface for classes that do value type conversions from strings, and created the following interface (don’t worry, I haven’t completely lost my mind — yes, I did say it requires a static method — but do bear with me until we get further down in this post 😉 ):
namespace Sitecore.Sandbox.Configuration { public interface IValueTypesConverter { bool ConvertToBoolean(string argument); char ConvertToCharacter(string argument); decimal ConvertToDecimal(string argument); double ConvertToDouble(string argument); float ConvertToFloat(string argument); int ConvertToInteger(string argument); } }
I then created the following class to implement the interface above:
using Sitecore.Diagnostics; namespace Sitecore.Sandbox.Configuration { public class ValueTypesConverter : IValueTypesConverter { public bool ConvertToBoolean(string argument) { bool value; Assert.ArgumentCondition(bool.TryParse(argument, out value), "argument", "argument is not a boolean!"); return value; } public char ConvertToCharacter(string argument) { char value; Assert.ArgumentCondition(char.TryParse(argument, out value), "argument", "argument is not a character!"); return value; } public decimal ConvertToDecimal(string argument) { decimal value; Assert.ArgumentCondition(decimal.TryParse(argument, out value), "argument", "argument is not a decimal!"); return value; } public double ConvertToDouble(string argument) { double value; Assert.ArgumentCondition(double.TryParse(argument, out value), "argument", "argument is not a double!"); return value; } public float ConvertToFloat(string argument) { float value; Assert.ArgumentCondition(float.TryParse(argument, out value), "argument", "argument is not a float!"); return value; } public int ConvertToInteger(string argument) { int value; Assert.ArgumentCondition(int.TryParse(argument, out value), "argument", "argument is not an integer!"); return value; } } }
Each method in the class above calls its specific value type’s TryParse() method to convert the passed string to the value type. We ensure the argument was in the correct format: an assertion is done to check if the argument was of the type the method was expecting.
I then created the following adapter which wraps an instance of a class that implements the interface defined above:
using Sitecore.Configuration; namespace Sitecore.Sandbox.Configuration { public class TypesConverter { static TypesConverter() { ValueTypesConverter = Factory.CreateObject("valueTypesConverter", true) as IValueTypesConverter; } public static bool ConvertToBoolean(string argument) { return ValueTypesConverter.ConvertToBoolean(argument); } public static char ConvertToCharacter(string argument) { return ValueTypesConverter.ConvertToCharacter(argument); } public static decimal ConvertToDecimal(string argument) { return ValueTypesConverter.ConvertToDecimal(argument); } public static double ConvertToDouble(string argument) { return ValueTypesConverter.ConvertToDouble(argument); } public static float ConvertToFloat(string argument) { return ValueTypesConverter.ConvertToFloat(argument); } public static int ConvertToInteger(string argument) { return ValueTypesConverter.ConvertToInteger(argument); } private static IValueTypesConverter ValueTypesConverter { get; set; } } }
The class above creates an instance of the ValueTypesConverter class via the Sitecore.Configuration.Factory.CreateObject() method. I felt defining the class that implements the IValueTypesConverter interface in Sitecore configuration would allow for customization if one would ever need to do so.
Further, all methods in the class above just delegate calls to the ValueTypesConverter instance.
For testing, I created the following class to serve as a <renderField> pipeline processor:
using System.Text; using Sitecore.Diagnostics; using Sitecore.Pipelines.RenderField; namespace Sitecore.Sandbox.Pipelines.RenderField { public class ValueTypesTest { public void Process(RenderFieldArgs args) { Assert.ArgumentNotNull(args, "args"); StringBuilder htmlBuilder = new StringBuilder(); htmlBuilder.AppendFormat("SomeBoolean ==> {0} <br />", SomeBoolean); htmlBuilder.AppendFormat("SomeCharacter ==> {0} <br />", SomeCharacter); htmlBuilder.AppendFormat("SomeDecimal ==> {0} <br />", SomeDecimal); htmlBuilder.AppendFormat("SomeDouble ==> {0} <br />", SomeDouble); htmlBuilder.AppendFormat("SomeFloat ==> {0} <br />", SomeFloat); htmlBuilder.AppendFormat("SomeInteger ==> {0}", SomeInteger); args.Result.FirstPart = htmlBuilder.ToString(); } private bool SomeBoolean { get; set; } private char SomeCharacter { get; set; } private decimal SomeDecimal { get; set; } private double SomeDouble { get; set; } private float SomeFloat { get; set; } private int SomeInteger { get; set; } } }
The class above defines properties that will be populated magically by Sitecore’s Configuration Factory, and builds a string of HTML to be rendered on the front-end.
I wired everything together in the following Sitecore configuration file:
<?xml version="1.0" encoding="utf-8" ?> <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <pipelines> <renderField> <processor type="Sitecore.Sandbox.Pipelines.RenderField.ValueTypesTest, Sitecore.Sandbox"> <SomeBoolean type="Sitecore.Sandbox.Configuration.TypesConverter, Sitecore.Sandbox" factoryMethod="ConvertToBoolean" arg0="true" /> <SomeCharacter type="Sitecore.Sandbox.Configuration.TypesConverter, Sitecore.Sandbox" factoryMethod="ConvertToCharacter" arg0="b" /> <SomeDecimal type="Sitecore.Sandbox.Configuration.TypesConverter, Sitecore.Sandbox" factoryMethod="ConvertToDecimal" arg0="0.1" /> <SomeDouble type="Sitecore.Sandbox.Configuration.TypesConverter, Sitecore.Sandbox" factoryMethod="ConvertToDouble" arg0="1234.5678" /> <SomeFloat type="Sitecore.Sandbox.Configuration.TypesConverter, Sitecore.Sandbox" factoryMethod="ConvertToFloat" arg0=".98765" /> <SomeInteger type="Sitecore.Sandbox.Configuration.TypesConverter, Sitecore.Sandbox" factoryMethod="ConvertToInteger" arg0="2" /> </processor> </renderField> </pipelines> <valueTypesConverter type="Sitecore.Sandbox.Configuration.ValueTypesConverter, Sitecore.Sandbox" /> </sitecore> </configuration>
When I loaded my home page, I saw that all value types defined in configuration were accounted for:
If you have any thoughts on this, or ideas around implementing this in a different way, please share in a comment.
Check ‘Sitecore.Pipelines.HttpRequest.StopMeasurements, Sitecore.Kernel’ in the web.config file. It passes the primitive values normally. I don’t get why to write more code when it is already working with existing Sitecore architecture.
Kindly correct me if wrong or this is handling any corner case.
Nice find, and I stand corrected!
I do know that char will not work with the existing framework — I had tried this, and the Configuration Factory did not set it.
ok I tried with following example and it works with char property type also. Secondly you could use ‘TypeConverterAttribute’ class.
using Sitecore.Diagnostics;
using Sitecore.Pipelines.HttpRequest;
namespace Sitecore.Company.Framework.Pipelines
{
public class DummyProcessor : HttpRequestProcessor
{
public string FirstStringVariable { get; set; }
public char FirstCharVariable { get; set; }
public override void Process(HttpRequestArgs args)
{
Log.Info(“My Dummy Processor is called now.”, this);
Log.Info(string.Format(“First string variable {0}.”, FirstStringVariable), this);
Log.Info(string.Format(“First char variable {0}.”, FirstCharVariable), this);
}
}
}
And following configuration:
<pipelines>
<httpRequestBegin>
<processor type="Sitecore.Company.Framework.Pipelines.DummyProcessor, Sitecore.Company.Framework" patch:after="processor[@type='Sitecore.Pipelines.HttpRequest.ExecuteRequest, Sitecore.Kernel']">
<FirstStringVariable>My First Variable</FirstStringVariable>
<FirstCharVariable>t</FirstCharVariable>
</processor>
</httpRequestBegin>
</pipelines>
Thanks for this!
Apparently, I must have done something wrong when testing with char.