2018-01-24 11:44:44 +01:00
using System ;
using System.Collections.Generic ;
using System.Reflection ;
using Newtonsoft.Json ;
2018-02-02 19:43:03 +01:00
using Newtonsoft.Json.Linq ;
2018-01-24 11:44:44 +01:00
using Umbraco.Core.Composing ;
2019-12-04 14:03:39 +01:00
using Umbraco.Core.IO ;
2018-01-24 11:44:44 +01:00
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>
2019-12-04 14:03:39 +01:00
protected ConfigurationEditor ( IIOHelper ioHelper )
: base ( DiscoverFields ( ioHelper ) )
2018-01-24 11:44:44 +01:00
{ }
/// <summary>
/// Discovers fields from configuration properties marked with the field attribute.
/// </summary>
2019-12-04 14:03:39 +01:00
private static List < ConfigurationField > DiscoverFields ( IIOHelper ioHelper )
2018-01-24 11:44:44 +01:00
{
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 ;
2020-04-03 11:47:38 +11:00
var attributeView = ioHelper . ResolveRelativeOrVirtualUrl ( attribute . View ) ;
2018-01-24 11:44:44 +01:00
// 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 ,
2018-04-12 21:29:36 +10:00
PropertyType = property . PropertyType ,
2018-01-24 11:44:44 +01:00
Description = attribute . Description ,
HideLabel = attribute . HideLabel ,
2019-11-08 14:30:46 +01:00
View = attributeView
2018-01-24 11:44:44 +01:00
} ;
fields . Add ( field ) ;
continue ;
}
2019-01-22 18:03:39 -05:00
// if the field has its own type, instantiate it
2018-01-24 11:44:44 +01:00
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 ;
2018-04-12 21:29:36 +10:00
field . PropertyType = property . PropertyType ;
2018-01-24 11:44:44 +01:00
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 ) )
2019-11-08 14:30:46 +01:00
field . View = attributeView ;
2018-01-24 11:44:44 +01:00
if ( ! string . IsNullOrWhiteSpace ( attribute . Description ) )
field . Description = attribute . Description ;
if ( attribute . HideLabelSettable . HasValue )
field . HideLabel = attribute . HideLabel ;
}
return fields ;
}
2018-01-26 17:55:20 +01:00
/// <inheritdoc />
2018-09-17 12:54:22 +02:00
public override IDictionary < string , object > DefaultConfiguration = > ToConfigurationEditor ( DefaultConfigurationObject ) ;
/// <inheritdoc />
public override object DefaultConfigurationObject = > new TConfiguration ( ) ;
2018-02-05 17:48:54 +01:00
2018-02-07 13:35:59 +01:00
/// <inheritdoc />
public override bool IsConfiguration ( object obj )
= > obj is TConfiguration ;
2018-02-05 17:48:54 +01:00
/// <inheritdoc />
public override object FromDatabase ( string configuration )
2018-01-24 11:44:44 +01:00
{
2018-01-26 17:55:20 +01:00
try
{
2018-02-02 19:43:03 +01:00
if ( string . IsNullOrWhiteSpace ( configuration ) ) return new TConfiguration ( ) ;
2018-03-05 14:59:23 +01:00
return JsonConvert . DeserializeObject < TConfiguration > ( configuration , ConfigurationJsonSettings ) ;
2018-01-26 17:55:20 +01:00
}
catch ( Exception e )
{
2019-05-15 17:00:05 +02:00
throw new InvalidOperationException ( $"Failed to parse configuration \" { configuration } \ " as \"{typeof(TConfiguration).Name}\" (see inner exception)." , e ) ;
2018-01-26 17:55:20 +01:00
}
2018-01-24 11:44:44 +01:00
}
/// <inheritdoc />
2018-04-04 13:11:12 +10:00
public sealed override object FromConfigurationEditor ( IDictionary < string , object > editorValues , object configuration )
2018-01-24 11:44:44 +01:00
{
2018-02-05 17:48:54 +01:00
return FromConfigurationEditor ( editorValues , ( TConfiguration ) configuration ) ;
2018-01-24 11:44:44 +01:00
}
/// <summary>
/// Converts the configuration posted by the editor.
/// </summary>
2018-02-05 17:48:54 +01:00
/// <param name="editorValues">The configuration object posted by the editor.</param>
2018-01-24 11:44:44 +01:00
/// <param name="configuration">The current configuration object.</param>
2018-04-04 13:11:12 +10:00
public virtual TConfiguration FromConfigurationEditor ( IDictionary < string , object > editorValues , TConfiguration configuration )
2018-01-24 11:44:44 +01:00
{
2019-01-22 18:03:39 -05:00
// note - editorValue contains a mix of CLR types (string, int...) and JToken
2018-02-02 19:43:03 +01:00
// turning everything back into a JToken... might not be fastest but is simplest
// for now
var o = new JObject ( ) ;
foreach ( var field in Fields )
{
2018-02-05 17:48:54 +01:00
// 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 ) ) )
2018-04-12 21:29:36 +10:00
{
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 ) ;
}
}
2018-02-02 19:43:03 +01:00
}
return o . ToObject < TConfiguration > ( ) ;
2018-01-24 11:44:44 +01:00
}
/// <inheritdoc />
2018-04-04 13:11:12 +10:00
public sealed override IDictionary < string , object > ToConfigurationEditor ( object configuration )
2018-01-24 11:44:44 +01:00
{
2018-02-05 17:48:54 +01:00
return ToConfigurationEditor ( ( TConfiguration ) configuration ) ;
2018-01-24 11:44:44 +01:00
}
/// <summary>
/// Converts configuration values to values for the editor.
/// </summary>
/// <param name="configuration">The configuration.</param>
2018-02-05 17:48:54 +01:00
public virtual Dictionary < string , object > ToConfigurationEditor ( TConfiguration configuration )
2018-01-24 11:44:44 +01:00
{
2018-02-02 19:43:03 +01:00
string FieldNamer ( PropertyInfo property )
{
2018-02-05 17:48:54 +01:00
// try the field
2018-02-02 19:43:03 +01:00
var field = property . GetCustomAttribute < ConfigurationFieldAttribute > ( ) ;
if ( field ! = null ) return field . Key ;
2018-02-05 17:48:54 +01:00
// but the property may not be a field just an extra thing
var json = property . GetCustomAttribute < JsonPropertyAttribute > ( ) ;
return json ? . PropertyName ? ? property . Name ;
2018-02-02 19:43:03 +01:00
}
2019-10-30 15:28:15 +01:00
return ObjectJsonExtensions . ToObjectDictionary ( configuration , FieldNamer ) ;
2018-01-24 11:44:44 +01:00
}
}
2018-04-04 13:11:12 +10:00
}