From 4f3d82cf107f7b9897fdc3c336cbfe2f143e23c6 Mon Sep 17 00:00:00 2001 From: AndyButland Date: Tue, 10 May 2016 10:50:59 +0200 Subject: [PATCH] Change field to which data is saved for a data type if provided in prevalues and handle potential mis-matched types on display --- src/Umbraco.Core/Constants-PropertyEditors.cs | 6 +++ .../Models/DataTypeDatabaseType.cs | 4 -- src/Umbraco.Core/Models/Property.cs | 52 +++++++++++++++++-- src/Umbraco.Core/Models/PropertyType.cs | 14 +++++ .../PropertyEditors/PropertyValueEditor.cs | 20 +++---- src/Umbraco.Core/Services/DataTypeService.cs | 32 ++++++++++++ .../PropertyEditors/LabelPropertyEditor.cs | 11 ++-- 7 files changed, 115 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Core/Constants-PropertyEditors.cs b/src/Umbraco.Core/Constants-PropertyEditors.cs index 2f7d247b36..358ca0e5d7 100644 --- a/src/Umbraco.Core/Constants-PropertyEditors.cs +++ b/src/Umbraco.Core/Constants-PropertyEditors.cs @@ -419,6 +419,12 @@ namespace Umbraco.Core /// Alias for the email address property editor /// public const string EmailAddressAlias = "Umbraco.EmailAddress"; + + /// + /// Pre-value name used to indicate a field that can be used to override the database field to which data for the associated + /// property is saved + /// + public const string DataValueTypePreValueKey = "umbracoDataValueType"; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/DataTypeDatabaseType.cs b/src/Umbraco.Core/Models/DataTypeDatabaseType.cs index 1db8ac65cb..2fccdc0645 100644 --- a/src/Umbraco.Core/Models/DataTypeDatabaseType.cs +++ b/src/Umbraco.Core/Models/DataTypeDatabaseType.cs @@ -6,10 +6,6 @@ namespace Umbraco.Core.Models /// /// Enum of the various DbTypes for which the Property values are stored /// - /// - /// Object is added to support complex values from PropertyEditors, - /// but will be saved under the Ntext column. - /// [Serializable] [DataContract] public enum DataTypeDatabaseType diff --git a/src/Umbraco.Core/Models/Property.cs b/src/Umbraco.Core/Models/Property.cs index d7c2eb92a8..f3fbefda4f 100644 --- a/src/Umbraco.Core/Models/Property.cs +++ b/src/Umbraco.Core/Models/Property.cs @@ -124,10 +124,54 @@ namespace Umbraco.Core.Models bool typeValidation = _propertyType.IsPropertyTypeValid(value); if (typeValidation == false) - throw new Exception( - string.Format( - "Type validation failed. The value type: '{0}' does not match the DataType in PropertyType with alias: '{1}'", - value == null ? "null" : value.GetType().Name, Alias)); + { + // Normally we'll throw an exception here. However if the property is of a type that can have it's data field (dataInt, dataVarchar etc.) + // changed, we might have a value of the now "wrong" type. As of May 2016 Label is the only built-in property editor that supports this. + // In that case we should try to parse the value and return null if that's not possible rather than throwing an exception. + if (value != null && _propertyType.CanHaveDataValueTypeChanged()) + { + var stringValue = value.ToString(); + switch (_propertyType.DataTypeDatabaseType) + { + case DataTypeDatabaseType.Nvarchar: + case DataTypeDatabaseType.Ntext: + value = stringValue; + break; + case DataTypeDatabaseType.Integer: + int integerValue; + if (int.TryParse(stringValue, out integerValue) == false) + { + // Edge case, but if changed from decimal --> integer, the above tryparse will fail. So we'll try going + // via decimal too to return the integer value rather than zero. + decimal decimalForIntegerValue; + if (decimal.TryParse(stringValue, out decimalForIntegerValue)) + { + integerValue = (int)decimalForIntegerValue; + } + } + + value = integerValue; + break; + case DataTypeDatabaseType.Decimal: + decimal decimalValue; + decimal.TryParse(stringValue, out decimalValue); + value = decimalValue; + break; + case DataTypeDatabaseType.Date: + DateTime dateValue; + DateTime.TryParse(stringValue, out dateValue); + value = dateValue; + break; + } + } + else + { + throw new Exception( + string.Format( + "Type validation failed. The value type: '{0}' does not match the DataType in PropertyType with alias: '{1}'", + value == null ? "null" : value.GetType().Name, Alias)); + } + } SetPropertyValueAndDetectChanges(o => { diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index 0649801a0c..00e7fc8d13 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Linq; using System.Reflection; using System.Runtime.Serialization; using System.Text.RegularExpressions; @@ -425,6 +426,19 @@ namespace Umbraco.Core.Models return false; } + /// + /// Checks the underlying property editor prevalues to see if the one that allows changing of the database field + /// to which data is saved (dataInt, dataVarchar etc.) is included. If so that means the field could be changed when the data + /// type is saved. + /// + /// + internal bool CanHaveDataValueTypeChanged() + { + var propertyEditor = PropertyEditorResolver.Current.GetByAlias(_propertyEditorAlias); + return propertyEditor.PreValueEditor.Fields + .SingleOrDefault(x => x.Key == Constants.PropertyEditors.DataValueTypePreValueKey) != null; + } + /// /// Validates the Value from a Property according to the validation settings /// diff --git a/src/Umbraco.Core/PropertyEditors/PropertyValueEditor.cs b/src/Umbraco.Core/PropertyEditors/PropertyValueEditor.cs index eb08b07d43..a7154d7d67 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyValueEditor.cs @@ -130,20 +130,20 @@ namespace Umbraco.Core.PropertyEditors { switch (ValueType.ToUpper(CultureInfo.InvariantCulture)) { - case "INT": - case "INTEGER": + case PropertyEditorValueTypes.IntegerType: + case PropertyEditorValueTypes.IntegerTypeAlternative: return DataTypeDatabaseType.Integer; - case "DECIMAL": + case PropertyEditorValueTypes.DecimalType: return DataTypeDatabaseType.Decimal; - case "STRING": + case PropertyEditorValueTypes.StringType: return DataTypeDatabaseType.Nvarchar; - case "TEXT": - case "JSON": - case "XML": + case PropertyEditorValueTypes.TextType: + case PropertyEditorValueTypes.JsonType: + case PropertyEditorValueTypes.XmlType: return DataTypeDatabaseType.Ntext; - case "DATETIME": - case "DATE": - case "TIME": + case PropertyEditorValueTypes.DateTimeType: + case PropertyEditorValueTypes.DateType: + case PropertyEditorValueTypes.TimeType: return DataTypeDatabaseType.Date; default: throw new FormatException("The ValueType does not match a known value type"); diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 035cfd0ab6..75234fa3ab 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -504,6 +504,8 @@ namespace Umbraco.Core.Services if (Saving.IsRaisedEventCancelled(new SaveEventArgs(dataTypeDefinition), this)) return; + OverrideDatabaseTypeIfProvidedInPreValues(dataTypeDefinition, values); + var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow)) { @@ -523,6 +525,36 @@ namespace Umbraco.Core.Services Audit(AuditType.Save, string.Format("Save DataTypeDefinition performed by user"), userId, dataTypeDefinition.Id); } + /// + /// If the database data field is provided in the pre-values update the data type definition to that instead of the + /// default for the property editor + /// + /// + /// + private static void OverrideDatabaseTypeIfProvidedInPreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values) + { + if (values != null && values.ContainsKey(Constants.PropertyEditors.DataValueTypePreValueKey)) + { + switch (values[Constants.PropertyEditors.DataValueTypePreValueKey].Value) + { + case PropertyEditorValueTypes.StringType: + dataTypeDefinition.DatabaseType = DataTypeDatabaseType.Nvarchar; + break; + case PropertyEditorValueTypes.IntegerType: + dataTypeDefinition.DatabaseType = DataTypeDatabaseType.Integer; + break; + case PropertyEditorValueTypes.DecimalType: + dataTypeDefinition.DatabaseType = DataTypeDatabaseType.Decimal; + break; + case PropertyEditorValueTypes.DateTimeType: + dataTypeDefinition.DatabaseType = DataTypeDatabaseType.Date; + break; + case PropertyEditorValueTypes.TextType: + dataTypeDefinition.DatabaseType = DataTypeDatabaseType.Ntext; + break; + } + } + } /// /// Deletes an diff --git a/src/Umbraco.Web/PropertyEditors/LabelPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/LabelPropertyEditor.cs index 1132fcfacd..fab9d0bba3 100644 --- a/src/Umbraco.Web/PropertyEditors/LabelPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/LabelPropertyEditor.cs @@ -41,7 +41,6 @@ namespace Umbraco.Web.PropertyEditors internal class LabelPreValueEditor : PreValueEditor { - private const string ValueTypeKey = "valueType"; private const string LegacyPropertyEditorValuesKey = "values"; public LabelPreValueEditor() @@ -56,7 +55,7 @@ namespace Umbraco.Web.PropertyEditors ValueType = PropertyEditorValueTypes.StringType; } - [PreValueField(ValueTypeKey, "Value type", "valuetype")] + [PreValueField(Constants.PropertyEditors.DataValueTypePreValueKey, "Value type", "valuetype")] public string ValueType { get; set; } /// @@ -72,19 +71,19 @@ namespace Umbraco.Web.PropertyEditors // Check for a saved value type. If not found set to default string type. var valueType = PropertyEditorValueTypes.StringType; - if (existing.ContainsKey(ValueTypeKey)) + if (existing.ContainsKey(Constants.PropertyEditors.DataValueTypePreValueKey)) { - valueType = (string)existing[ValueTypeKey]; + valueType = (string)existing[Constants.PropertyEditors.DataValueTypePreValueKey]; } // Convert any other values from a legacy property editor to a list, easier to enumerate on the editor. // Make sure to exclude values defined on the label property editor itself. var asList = existing .Select(e => new KeyValuePair(e.Key, e.Value)) - .Where(e => e.Key != ValueTypeKey) + .Where(e => e.Key != Constants.PropertyEditors.DataValueTypePreValueKey) .ToList(); - var result = new Dictionary { { ValueTypeKey, valueType } }; + var result = new Dictionary { { Constants.PropertyEditors.DataValueTypePreValueKey, valueType } }; if (asList.Any()) { result.Add("values", asList);