How to register Dictionary using Unity 2.0

Oct 14, 2010 at 3:48 PM
Edited Oct 14, 2010 at 3:48 PM

Can somebody guide me to register property of type Dictionary using Unity 2. (using config file).

 

Oct 18, 2010 at 5:05 AM

There's nothing explicit built in to do this. You would need to write a type converter that can convert a string into a dictionary, and use that.

Why do you need to do this? Usually when somebody asks for this feature there's an opportunity for building a better object model instead.

 

Jan 10, 2011 at 11:27 AM

Hi,

I also struggled with that problem.

So far I managed to change a little sources to fit my needs by using CData section (probably as a separate extension it would be much better but I didn't dig too deep).

In Unity.Configuration additional class CDataElement

 

 

     /// <summary>
    /// Element that describes a constant value stored as CData
    /// that will be injected into the container.
    /// </summary>
    public class CDataElement:ParameterValueElement
    {
        private const string ValuePropertyName = "value";
        private const string TypeConverterTypeNamePropertyName = "typeConverter";

        /// <summary>
        /// Construct a new <see cref="CDataElement"/> object.
        /// </summary>
        public CDataElement(){}

        /// <summary>
        /// Construct a new <see cref="ValueElement"/> object,
        /// initializing properties from the contents of
        /// <paramref name="propertyValues"/>.
        /// </summary>
        /// <param name="propertyValues">Name/value pairs which
        /// contain the values to initialize properties to.</param>
        public CDataElement(IDictionary<string, string> propertyValues)
        {
            GuardPropertyValueIsPresent(propertyValues, "value");
            Value = propertyValues["value"];
            if (propertyValues.ContainsKey("typeConverter"))
            {
                TypeConverterTypeName = propertyValues["typeConverter"];
            }
        }

        /// <summary>
        /// Value for this element
        /// </summary>
        [ConfigurationProperty(ValuePropertyName)]
        public string Value
        {
            get { return (string)base[ValuePropertyName]; }
            set { base[ValuePropertyName] = value; }
        }

        /// <summary>
        /// 
        /// </summary>
        [ConfigurationProperty(TypeConverterTypeNamePropertyName, IsRequired = false)]
        public string TypeConverterTypeName
        {
            get { return (string)base[TypeConverterTypeNamePropertyName]; }
            set { base[TypeConverterTypeNamePropertyName] = value; }
        }

        ///<summary>
        /// Write the contents of this element to the given <see cref="XmlWriter"/>. This
        /// method always outputs an explicit &lt;dependency&gt; tag, instead of providing
        /// attributes to the parent method.
        ///</summary>
        ///<param name="writer">Writer to send XML content to.</param>
        public override void SerializeContent(XmlWriter writer)
        {
            writer.WriteAttributeIfNotEmpty(TypeConverterTypeNamePropertyName, TypeConverterTypeName);
            writer.WriteCData(Value);
        }

        /// <summary>
        /// Deserialize element from cdata tag
        /// </summary>
        /// <param name="reader">XmlReader</param>
        /// <param name="serializeCollectionKey">Flag which indicates collection key serialization</param>
        protected override void DeserializeElement(XmlReader reader, bool serializeCollectionKey)
        {
            foreach (ConfigurationProperty configurationProperty in Properties)
            {
                string name = configurationProperty.Name;
                if (String.Compare(name, ValuePropertyName, true) != 0)
                {
                    string attributeValue = reader.GetAttribute(name);
                    base[name] = attributeValue;
                }
            }
            string contentString = reader.ReadString();
            base[ValuePropertyName] = contentString;
        }

        /// <summary>
        /// Generate an <see cref="InjectionParameterValue"/> object
        /// that will be used to configure the container for a type registration.
        /// </summary>
        /// <param name="container">Container that is being configured. Supplied in order
        /// to let custom implementations retrieve services; do not configure the container
        /// directly in this method.</param>
        /// <param name="parameterType">Type of the parameter to get the value for.</param>
        /// <returns>The required <see cref="InjectionParameterValue"/> object.</returns>
        public override InjectionParameterValue GetInjectionParameterValue(IUnityContainer container, Type parameterType)
        {
            CheckNonGeneric(parameterType);

            var converter = GetTypeConverter(parameterType);
            return new InjectionParameter(parameterType, converter.ConvertFromInvariantString(Value));
        }

        private void CheckNonGeneric(Type parameterType)
        {
            if (parameterType.IsGenericParameter)
            {
                throw new InvalidOperationException(
                    string.Format(
                        CultureInfo.CurrentCulture,
                        Resources.ValueNotAllowedForGenericParameterType,
                        parameterType.Name,
                        this.Value));
            }

            var reflector = new ReflectionHelper(parameterType);

            if (reflector.IsOpenGeneric)
            {
                throw new InvalidOperationException(
                    string.Format(
                        CultureInfo.CurrentCulture,
                        Resources.ValueNotAllowedForOpenGenericType,
                        parameterType.Name,
                        this.Value));
            }

            if (reflector.IsGenericArray)
            {
                throw new InvalidOperationException(
                    string.Format(
                        CultureInfo.CurrentCulture,
                        Resources.ValueNotAllowedForGenericArrayType,
                        parameterType.Name,
                        this.Value));
            }
        }

        private TypeConverter GetTypeConverter(Type parameterType)
        {
            if (!string.IsNullOrEmpty(TypeConverterTypeName))
            {
                Type converterType = TypeResolver.ResolveType(TypeConverterTypeName);
                return (TypeConverter)Activator.CreateInstance(converterType);
            }
            return TypeDescriptor.GetConverter(parameterType);
        }
    }

 

 

Changes in two places in ValueElementHelper

 

private static readonly Dictionary<Type, string> knownValueElementTags =
            new Dictionary<Type, string>
            {
                {typeof (DependencyElement), "dependency"},
                {typeof (ValueElement), "value"},
                {typeof (ArrayElement), "array"},
                {typeof (OptionalElement), "optional"},
                {typeof (CDataElement), "cdata"}
            };

public ValueElementHelper(IValueProvidingElement parentElement)
        {
            this.parentElement = parentElement;

            unknownElementHandlerMap = new UnknownElementHandlerMap
            {
                { "value",      xr => SetValue<ValueElement>(xr) },
                { "dependency", xr => SetValue<DependencyElement>(xr) },
                { "array",      xr => SetValue<ArrayElement>(xr) },
                { "optional",   xr => SetValue<OptionalElement>(xr) },
                { "cdata",      xr => SetValue<CDataElement>(xr) },
            };
        }

And voila ;)
Some sample converter
/// <summary>
    /// Converts string with values separated by pipe to Dictionary&lt;string,string&gt; 
    /// </summary>
    public class PipeStringToDictionaryStringStringTypeConverter : TypeConverter
    {
        protected string separator="|";
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="separator"></param>
        protected PipeStringToDictionaryStringStringTypeConverter()
        {
        }
        /// <summary>
        /// Gets a value indicating whether this converter 
        /// can convert an object in the given source type 
        /// to a string array object using the specified context. 
        /// </summary>
        /// <param name="context">An ITypeDescriptorContext that provides a format context.</param>
        /// <param name="sourceType">A Type that represents the type you wish to convert from.</param>
        /// <returns>true if this object can perform the conversion; otherwise, false. </returns>
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            return sourceType.GetType() == typeof(string);
        }
        /// <summary>
        /// Gets a value indicating whether this converter 
        /// can convert an object to the given destination type 
        /// from a string array object using the specified context. 
        /// </summary>
        /// <param name="context">An ITypeDescriptorContext that provides a format context.</param>
        /// <param name="destinationType">A Type that represents the type you wish convert to.</param>
        /// <returns>true if this object can perform the conversion; otherwise, false. </returns>
        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            return destinationType == typeof(Dictionary<string, string>);
        }
        /// <summary>
        /// Converts the given value object to a string array object. 
        /// </summary>
        /// <param name="context">An ITypeDescriptorContext that provides a format context.</param>
        /// <param name="culture">A CultureInfo that specifies the culture to which to convert.</param>
        /// <param name="value">The Object to convert.</param>
        /// <returns>An Object that represents the converted value.</returns>
        public override object ConvertFrom(
            ITypeDescriptorContext context,
            CultureInfo culture,
            object value)
        {
            string[] separators = new string[] { separator };
            Dictionary<string, string> retval = new Dictionary<string, string>();
            using (StringReader sr = new StringReader((string)value))
            {
                string line;
                string[] parts;

                while ((line = sr.ReadLine()) != null)
                {
                    parts = line.Split(separators, StringSplitOptions.None);
                    if (parts.Length < 1)
                    {
                        continue;
                    }
                    if (String.IsNullOrEmpty(parts[0]))
                    {
                        continue;
                    }
                    for (int i = 0; i < parts.Length; i++)
                    {
                        parts[i] = HttpUtility.HtmlDecode(parts[i]);
                    }
                    if (parts.Length > 1)
                    {
                        retval[parts[0]] = String.Join(separator, parts.Skip(1).ToArray());
                    }
                    else
                    {
                        retval[parts[0]] = String.Empty;
                    }
                }
            }
            return retval;
        }
        /// <summary>
        /// Converts the given value object from a string array object. 
        /// </summary>
        /// <param name="context">An ITypeDescriptorContext that provides a format context.</param>
        /// <param name="culture">A CultureInfo that specifies the culture to which to convert.</param>
        /// <param name="value">The Object to convert.</param>
        /// <param name="destinationType">Destination type</param>
        /// <returns>An Object that represents the converted value.</returns>
        public override object ConvertTo(
            ITypeDescriptorContext context,
            CultureInfo culture,
            object value,
            Type destinationType)
        {
            Dictionary<string, string> dval = (Dictionary<string, string>)value;
            StringBuilder sb = new StringBuilder();
            char c = separator[0];
            string rep = String.Concat("&#x", Convert.ToInt32(c).ToString("X"), ";");
            foreach (KeyValuePair<string, string> kvp in dval)
            {
                sb.Append(HttpUtility.HtmlEncode(kvp.Key).Replace(separator, rep))
                    .Append(separator)
                    .AppendLine(HttpUtility.HtmlEncode(kvp.Value).Replace(separator, rep));
            }
            return sb.ToString();
        }
    }


Some sample xml configuration for this 
<alias alias="DictionaryStringString" type="System.Collections.Generic.Dictionary`2[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>

<alias alias="PipeStringToDictionaryStringStringTypeConverter" type="<namespace>.PipeStringToDictionaryStringStringTypeConverter,<dll>"/>

<register type="IValueProcessor" name="AbbreviationProcessor" mapTo="KeywordValueProcessor">
        <lifetime type="singleton" />
        <constructor>
          <param name="replacements" type="DictionaryStringString">
            <cdata typeConverter="PipeStringToDictionaryStringStringTypeConverter">
              <![CDATA[
ACCEPTANCE|ACPT.
ACCESSORIES|ACCES.
ACCOMMODATION|ACCOM.
ACCOMMODATIONS|ACCOMS.
]]>
            </cdata>
          </param>
        </constructor>
      </register>

 I know that this is ugly brute force but it works for me - it can be treated as starting point to some better code.
Best regards
Adam