MetaDataType.StringDictionary from code

Vote:
 

How to create strongly typed property that uses type: MetaDataType.StringDictionary?

Read this: "Working with the Catalog as IContent" but it says nothing about StringDictionary

Example of MetaDataType.EnumMultiValue property that is closest match, but not quite what is needed:

[CatalogContentType(GUID = "correct-guid", MetaClassName = "TestProduct")]
public class TestProduct : ProductContent
{
    [Display(Name = "Multiple dictionary property example")]
    [BackingType(typeof(PropertyDictionaryMultiple))]
    public virtual ItemCollection List { get; set; }
}

I have multiple existing properties that uses: MetaDataType.StringDictionary

And by looking at EPiServer.Commerce.Catalog.Provider.Construction.MetaDataTypeResolver and EPiServer.Commerce.Catalog.Provider.Construction.CatalogContentScannerExtension I can see that there is no mapping for MetaDataType.StringDictionary and that results to: NotSupported exception - "The property data type {0} can not be mapped to a MetaDataType".

Have I missed something? Please, suggestions to get StringDictionary strongly typed from code?

PS. version of EPiServer Commerce 7.9

#88649
Jul 22, 2014 15:28
Vote:
 

Hi,

StringDictionary is not supported yet, so unfortunately you'll be not able to define it by code. We have plan to add support for it in future.

Regards.

/Q

#88653
Jul 22, 2014 18:18
Vote:
 

Hi,

Is there particular reason why EPiServer.Commerce.Catalog.Provider.Construction.MetaDataTypeResolver does not implement interface then I could replace implementation but instead is defined like this:

namespace EPiServer.Commerce.Catalog.Provider.Construction
{
  [ServiceConfiguration(typeof (MetaDataTypeResolver))]
  public class MetaDataTypeResolver
  {
    .....

How near or far future could that be when StringDictionary is supported?

#88657
Jul 23, 2014 9:44
Vote:
 

You can surely replace our implementation by your custom implemetation, even without interface. Just define your class, inherited from MetaDataTypeResolver, overwrite the method you like (Most of them are virtual so no problem), then register in your initialization module (which inherits from IConfigurableModule):

public void ConfigureContainer(ServiceConfigurationContext context)
{
context.Container.Configure(ce =>
{

ce.For().Use();

}

}

We have the StringDictionary support in our backlog, but I cannot say about exact timeframe. But I hope it'll be able to be released by the end of this year.

Regards.

/Q

#88658
Jul 23, 2014 9:49
Vote:
 

So then here is my quick fix for this problem until EPiServer does it properly:

My Custom Product:

[CatalogContentType(GUID = "valid-guid", MetaClassName = "CustomTestProduct")]
    public class TestProduct : ProductContent
    {
        [Display(Name = "StringDictionary test")]
        public virtual StringDictionary DictTest { get; set; }
    }

That uses as a type StringDictionary(custom one) that inherits from: ItemCollection

public class StringDictionary : ItemCollection
    {
        public const string Seperator = "other-valid-guid";

        public StringDictionary()
        {
        }

        public StringDictionary(ItemCollection items) : base(items)
        {
        }

        public StringDictionary(IEnumerable items) : base(items)
        {
        }

        public Dictionary ToDictionary()
        {
            return this.Select(item => item.Split(new[] { Seperator }, StringSplitOptions.None)).ToDictionary(split => split[0], split => split[1]);
        }
    }

We need to inherit from ItemCollection becasue then we can reuse EPiServer.Commerce.SpecializedProperties.PropertyCollectionBase or almost, here is its modified version:

/// 
    ///     reflected and modified class from EPiServer.Commerce.SpecializedProperties.PropertyCollectionBase
    /// 
    public abstract class PropertyDictionaryBase : PropertyLongString, IEnumerable
    {
        private StringDictionary items;

        protected PropertyDictionaryBase() : this(new StringDictionary())
        {
        }

        protected PropertyDictionaryBase(StringDictionary items)
        {
            if (items == null)
            {
                throw new ArgumentNullException("items");
            }

            this.items = items;
            IsModified = true;
        }

        [XmlIgnore]
        public StringDictionary Items
        {
            get
            {
                if (!IsLongStringLoaded)
                {
                    FromLongString(GetDelayedLoadedString(true));
                    this.items.IsModified = false;
                    if (IsReadOnly && !this.items.IsReadOnly)
                    {
                        this.items.MakeReadOnly();
                    }
                }

                return this.items;
            }
            set
            {
                ThrowIfReadOnly();
                if (value == null || value.Count == 0)
                {
                    Clear();
                }
                else
                {
                    if (this.items == value)
                    {
                        return;
                    }

                    this.items = value;
                    Modified();
                }
            }
        }

        protected virtual Injected SerializerFactory { get; set; }

        public override bool IsModified
        {
            get
            {
                return base.IsModified || this.items.IsModified;
            }
            set
            {
                ThrowIfReadOnly();
                this.items.IsModified = value;
                base.IsModified = value;
            }
        }

        public override bool IsNull
        {
            get
            {
                if (Items != null)
                {
                    return Items.Count == 0;
                }

                return true;
            }
        }

        public override Type PropertyValueType
        {
            get
            {
                return typeof(ItemCollection<>).MakeGenericType(new[] { typeof(string) });
            }
        }

        public override object Value
        {
            get
            {
                return Items;
            }
            set
            {
                SetPropertyValue(value,
                                 (() =>
                                      {
                                          var local0 = value as StringDictionary;
                                          if (local0 != null)
                                          {
                                              Items = local0;
                                          }
                                          else
                                          {
                                              var local1 = value as IEnumerable;
                                              if (local1 == null)
                                              {
                                                  throw new ArgumentException(string.Format("Passed object must be of type {0}.",
                                                                                            typeof(IEnumerable).Name));
                                              }

                                              Items = new StringDictionary(local1);
                                          }
                                      }));
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.items.GetEnumerator();
        }

        public IEnumerator GetEnumerator()
        {
            return this.items.GetEnumerator();
        }

        public override PropertyData CreateWritableClone()
        {
            var propertyCollectionBase = (PropertyDictionaryBase)base.CreateWritableClone();
            propertyCollectionBase.items = new StringDictionary(Items.CreateWritableClone());
            return propertyCollectionBase;
        }

        public override void LoadData(object value)
        {
            if (value is StringDictionary)
            {
                Value = value;
            }
            else
            {
                FromLongString((string)value);
            }
        }

        public override void MakeReadOnly()
        {
            base.MakeReadOnly();
            if (this.items == null || this.items.IsReadOnly)
            {
                return;
            }

            this.items.MakeReadOnly();
        }

        public override PropertyData ParseToObject(string str)
        {
            var propertyData = base.ParseToObject(str);
            var instance = CreateInstance();
            instance.FromLongString((propertyData).ToString());
            instance.IsModified = false;

            return instance;
        }

        public override void ParseToSelf(string str)
        {
            base.ParseToSelf(str);
            FromLongString(str);
        }

        public override object SaveData(PropertyDataCollection properties)
        {
            return ToLongString();
        }

        protected virtual PropertyDictionaryBase CreateInstance()
        {
            return (PropertyDictionaryBase)Activator.CreateInstance(GetType());
        }

        protected virtual void FromLongString(string str)
        {
            if (string.IsNullOrEmpty(str))
            {
                return;
            }

            var serializer = SerializerFactory.Service.GetSerializer("application/json");
            using (var stringReader = new StringReader(str))
            {
                var objectType = typeof(ItemCollection<>).MakeGenericType(new[] { typeof(string) });
                this.items = (StringDictionary)serializer.Deserialize(stringReader, objectType);
            }
        }

        protected virtual string ToLongString()
        {
            using (var stringWriter = new StringWriter())
            {
                SerializerFactory.Service.GetSerializer("application/json").Serialize(stringWriter, this.items);

                return stringWriter.ToString();
            }
        }

        protected override void SetDefaultValue()
        {
            base.SetDefaultValue();
            this.items = new StringDictionary();
        }
    }

and resulting property that will be used:

[PropertyDefinitionTypePlugIn]
    public class PropertyStringDictionary : PropertyDictionaryBase
    {
        public override void LoadData(object value)
        {
            if (value is StringDictionary)
            {
                Value = value;
            }
            else if (value is MetaStringDictionary)
            {
                var data = value as MetaStringDictionary;
                var result = new StringDictionary();

                foreach (DictionaryEntry item in data)
                {
                    result.Add(string.Format("{0}{2}{1}", item.Key, item.Value, StringDictionary.Seperator));
                }

                Value = result;
            }
            else
            {
                FromLongString((string)value);
            }
        }
    }

So at this point we have string array where we store dictionary by seperating its key and value with constant seperator, that we hope are never used by users neither as key or value.

At this point it is possible to implement custom overrides for BackingTypeResolver and MetaDataTypeResolver

[ServiceConfiguration(typeof(MetaDataTypeResolver))]
    public class CustomMetaDataTypeResolver : MetaDataTypeResolver
    {
        protected virtual MetaDataType StringDictionary
        {
            get
            {
                return MetaDataType.StringDictionary;
            }
        }

        public new virtual MetaDataType? GetMetaDataTypeFromBackingType(Type backingType)
        {
            if (backingType == typeof(PropertyDictionarySingle))
            {
                return MetaDataType.EnumSingleValue;
            }

            if (backingType == typeof(PropertyDictionaryMultiple))
            {
                return MetaDataType.EnumMultiValue;
            }

            if (backingType == typeof(PropertyStringDictionary))
            {
                return MetaDataType.StringDictionary;
            }

            return new MetaDataType?();
        }
    }

and

[ServiceConfiguration(typeof(BackingTypeResolver))]
    public class CustomBackingTypeResolver : BackingTypeResolver
    {
        public CustomBackingTypeResolver(IPropertyDefinitionTypeRepository propertyDefinitionTypeRepository, IPropertyDataFactory propertyDataFactory)
                : base(propertyDefinitionTypeRepository, propertyDataFactory)
        {
        }

        protected virtual Type StringDictionary
        {
            get
            {
                return typeof(PropertyStringDictionary);
            }
        }
    }

Then to plug it all together:

[ModuleDependency(typeof(ServiceContainerInitialization))]
    [InitializableModule]
    public class CustomTypeResolverInitializationModule : IConfigurableModule
    {
        public void Initialize(InitializationEngine context)
        {
        }

        public void Uninitialize(InitializationEngine context)
        {
        }

        public void Preload(string[] parameters)
        {
        }

        public void ConfigureContainer(ServiceConfigurationContext context)
        {
            context.Container.Configure(ce =>
                                            {
                                                ce.For().Use();
                                                ce.For().Use();
                                            });
        }
    }

and finnaly test it:

var referenceConverter = ServiceLocator.Current.GetInstance();
            var reference = referenceConverter.GetContentLink("test");

            var repository = ServiceLocator.Current.GetInstance();
            var result = repository.Get(reference);

            var test = result.DictTest.ToDictionary()["333"];

With this code its possible to crete StringDictionary metadata field in Commerce also it is possible to read it, have not tested wether saving(inserting) works, most likely custom property will be missing something to do so.

So the real proble here was that EPiServer does not have Property that is able to save Dictionary already and I did not want to implement it so I reflected and modified EPiServer.Commerce.SpecializedProperties.PropertyCollectionBase.

Thanks Quan Mai for help!

#88666
Edited, Jul 23, 2014 15:07
Vote:
 

Hi

I ran into the same issue. I implemented the code above and everything worked fine. Unfortunately there is one bug. I have metafield where i need the field "Allow search" (standard field on every metafield) to be checked. However the code above somehow unchecks it, which results in my index not using the field. Does anyone have quickfix for this problem? To reproduce just reset the iis, afterwhich the mark in "allow search" will be gone. 

#122816
Jun 15, 2015 21:55
Vote:
 

Hi!

I implemented everything as described above and adding/reading items from CommerceManager works fine. Although when programmatically trying to set or add the values, all I get is an exception below:

[IncorrectValueTypeException: Incorrect value type. Must be MetaStringDictionary.]
   Mediachase.MetaDataPlus.MetaObject.ValidateMetaField(String metaFieldName, Object value) +1354
   Mediachase.MetaDataPlus.MetaObject.DoSetMetaField(String fieldName, Object value, Boolean validate) +38
   EPiServer.Commerce.Catalog.Provider.MetaDataPropertyMapper.SetMetaObjectValueFromProperty(PropertyData property, MetaField metaField, MetaObject metaObj) +593
   EPiServer.Commerce.Catalog.Provider.Persistence.MetaDataCommitter.CommitMetaAttributes(T content, Int32 objectId) +1292
   EPiServer.Commerce.Catalog.Provider.Persistence.EntryContentBaseCommitter.UpdateExisting(EntryContentBase content, Int32 entryId) +639
   EPiServer.Commerce.Catalog.Provider.Persistence.EntryContentBaseCommitter.Save(CatalogContentBase content) +250
   EPiServer.Commerce.Catalog.Provider.CatalogContentDraftStore.DoSaveFull(CatalogContentBase content, SaveAction saveAction, String userName, Boolean forceCurrentVersion, Boolean newVersionRequired, Boolean delayedPublish) +764
   EPiServer.Commerce.Catalog.Provider.CatalogContentDraftStore.SaveFull(CatalogContentBase content, SaveAction saveAction) +467
   EPiServer.Commerce.Catalog.Provider.CatalogContentDraftStore.Save(IContent content, SaveAction saveAction) +103
   EPiServer.Commerce.Catalog.Provider.CatalogContentProvider.Save(IContent content, SaveAction action) +238
   EPiServer.Core.DefaultContentRepository.Save(IContent content, SaveAction action, AccessLevel access) +405

 

#123308
Jul 01, 2015 11:28
Vote:
 

Quan Mai, it seems that in Commerce 9.10 StringDictionary is still not supported fully, or am I missing something?

Do EPiServer still plan to implement this?

#147112
Edited, Apr 06, 2016 13:40
Vote:
 
<p>Yes and yes.</p> <p>Our priority right now is the new promotion system, so I think there is no other new thing until that is done (BETA tags are removed).</p> <p>Regards,</p> <p>/Q</p>
#147118
Apr 06, 2016 15:13
Vote:
 

Did not quite get your answer, so it is not yet implemented, but it will be implemented in future, right?

Or i'm missing something and its stright forward already, if so, is it documented, some examples?

#147122
Edited, Apr 06, 2016 15:21
Vote:
 

Sorry if I was being unclear. It is still not implemented. We still have the plan to implement it, but it won't happen anytime before the new promotion system is done.

/Q

#147123
Apr 06, 2016 15:28
This topic was created over six months ago and has been resolved. If you have a similar question, please create a new topic and refer to this one.
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.