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
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?
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
}
}
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
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
///
/// 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!
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.
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
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?
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?
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:
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