198 lines
8.0 KiB
C#
198 lines
8.0 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Reflection;
|
|
using Newtonsoft.Json;
|
|
using Newtonsoft.Json.Linq;
|
|
using Umbraco.Core.Composing;
|
|
using Umbraco.Core.IO;
|
|
|
|
namespace Umbraco.Core.PropertyEditors
|
|
{
|
|
/// <summary>
|
|
/// Represents a data type configuration editor with a typed configuration.
|
|
/// </summary>
|
|
public abstract class ConfigurationEditor<TConfiguration> : ConfigurationEditor
|
|
where TConfiguration : new()
|
|
{
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="ConfigurationEditor{TConfiguration}"/> class.
|
|
/// </summary>
|
|
protected ConfigurationEditor(IIOHelper ioHelper)
|
|
: base(DiscoverFields(ioHelper))
|
|
{ }
|
|
|
|
/// <summary>
|
|
/// Discovers fields from configuration properties marked with the field attribute.
|
|
/// </summary>
|
|
private static List<ConfigurationField> DiscoverFields(IIOHelper ioHelper)
|
|
{
|
|
var fields = new List<ConfigurationField>();
|
|
var properties = TypeHelper.CachedDiscoverableProperties(typeof(TConfiguration));
|
|
|
|
foreach (var property in properties)
|
|
{
|
|
var attribute = property.GetCustomAttribute<ConfigurationFieldAttribute>(false);
|
|
if (attribute == null) continue;
|
|
|
|
ConfigurationField field;
|
|
|
|
var attributeView = ioHelper.ResolveRelativeOrVirtualUrl(attribute.View);
|
|
// if the field does not have its own type, use the base type
|
|
if (attribute.Type == null)
|
|
{
|
|
field = new ConfigurationField
|
|
{
|
|
// if the key is empty then use the property name
|
|
Key = string.IsNullOrWhiteSpace(attribute.Key) ? property.Name : attribute.Key,
|
|
Name = attribute.Name,
|
|
PropertyName = property.Name,
|
|
PropertyType = property.PropertyType,
|
|
Description = attribute.Description,
|
|
HideLabel = attribute.HideLabel,
|
|
View = attributeView
|
|
};
|
|
|
|
fields.Add(field);
|
|
continue;
|
|
}
|
|
|
|
// if the field has its own type, instantiate it
|
|
try
|
|
{
|
|
field = (ConfigurationField) Activator.CreateInstance(attribute.Type);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw new Exception($"Failed to create an instance of type \"{attribute.Type}\" for property \"{property.Name}\" of configuration \"{typeof(TConfiguration).Name}\" (see inner exception).", ex);
|
|
}
|
|
|
|
// then add it, and overwrite values if they are assigned in the attribute
|
|
fields.Add(field);
|
|
|
|
field.PropertyName = property.Name;
|
|
field.PropertyType = property.PropertyType;
|
|
|
|
if (!string.IsNullOrWhiteSpace(attribute.Key))
|
|
field.Key = attribute.Key;
|
|
|
|
// if the key is still empty then use the property name
|
|
if (string.IsNullOrWhiteSpace(field.Key))
|
|
field.Key = property.Name;
|
|
|
|
if (!string.IsNullOrWhiteSpace(attribute.Name))
|
|
field.Name = attribute.Name;
|
|
|
|
if (!string.IsNullOrWhiteSpace(attribute.View))
|
|
field.View = attributeView;
|
|
|
|
if (!string.IsNullOrWhiteSpace(attribute.Description))
|
|
field.Description = attribute.Description;
|
|
|
|
if (attribute.HideLabelSettable.HasValue)
|
|
field.HideLabel = attribute.HideLabel;
|
|
}
|
|
|
|
return fields;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override IDictionary<string, object> DefaultConfiguration => ToConfigurationEditor(DefaultConfigurationObject);
|
|
|
|
/// <inheritdoc />
|
|
public override object DefaultConfigurationObject => new TConfiguration();
|
|
|
|
/// <inheritdoc />
|
|
public override bool IsConfiguration(object obj)
|
|
=> obj is TConfiguration;
|
|
|
|
/// <inheritdoc />
|
|
public override object FromDatabase(string configuration)
|
|
{
|
|
try
|
|
{
|
|
if (string.IsNullOrWhiteSpace(configuration)) return new TConfiguration();
|
|
return JsonConvert.DeserializeObject<TConfiguration>(configuration, ConfigurationJsonSettings);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
throw new InvalidOperationException($"Failed to parse configuration \"{configuration}\" as \"{typeof(TConfiguration).Name}\" (see inner exception).", e);
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public sealed override object FromConfigurationEditor(IDictionary<string, object> editorValues, object configuration)
|
|
{
|
|
return FromConfigurationEditor(editorValues, (TConfiguration) configuration);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts the configuration posted by the editor.
|
|
/// </summary>
|
|
/// <param name="editorValues">The configuration object posted by the editor.</param>
|
|
/// <param name="configuration">The current configuration object.</param>
|
|
public virtual TConfiguration FromConfigurationEditor(IDictionary<string, object> editorValues, TConfiguration configuration)
|
|
{
|
|
// note - editorValue contains a mix of CLR types (string, int...) and JToken
|
|
// turning everything back into a JToken... might not be fastest but is simplest
|
|
// for now
|
|
|
|
var o = new JObject();
|
|
|
|
foreach (var field in Fields)
|
|
{
|
|
// field only, JsonPropertyAttribute is ignored here
|
|
// only keep fields that have a non-null/empty value
|
|
// rest will fall back to default during ToObject()
|
|
if (editorValues.TryGetValue(field.Key, out var value) && value != null && (!(value is string stringValue) || !string.IsNullOrWhiteSpace(stringValue)))
|
|
{
|
|
if (value is JToken jtoken)
|
|
{
|
|
//if it's a jtoken then set it
|
|
o[field.PropertyName] = jtoken;
|
|
}
|
|
else if (field.PropertyType == typeof(bool) && value is string sBool)
|
|
{
|
|
//if it's a boolean property type but a string is found, try to do a conversion
|
|
var converted = sBool.TryConvertTo<bool>();
|
|
if (converted)
|
|
o[field.PropertyName] = converted.Result;
|
|
}
|
|
else
|
|
{
|
|
//default behavior
|
|
o[field.PropertyName] = JToken.FromObject(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
return o.ToObject<TConfiguration>();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public sealed override IDictionary<string, object> ToConfigurationEditor(object configuration)
|
|
{
|
|
return ToConfigurationEditor((TConfiguration) configuration);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts configuration values to values for the editor.
|
|
/// </summary>
|
|
/// <param name="configuration">The configuration.</param>
|
|
public virtual Dictionary<string, object> ToConfigurationEditor(TConfiguration configuration)
|
|
{
|
|
string FieldNamer(PropertyInfo property)
|
|
{
|
|
// try the field
|
|
var field = property.GetCustomAttribute<ConfigurationFieldAttribute>();
|
|
if (field != null) return field.Key;
|
|
|
|
// but the property may not be a field just an extra thing
|
|
var json = property.GetCustomAttribute<JsonPropertyAttribute>();
|
|
return json?.PropertyName ?? property.Name;
|
|
}
|
|
|
|
return ObjectJsonExtensions.ToObjectDictionary(configuration, FieldNamer);
|
|
}
|
|
}
|
|
}
|