Browse Source

Merge branch 'dev' into pr/2563

pull/3732/head
Galip Tolga Erdem 6 years ago
parent
commit
d515955383
  1. 47
      docs/en/Object-Extensions.md
  2. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/ExtensionPropertyDto.cs
  3. 38
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/AbpTagHelperLocalizer.cs
  4. 2
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpRadioInputTagHelperService.cs
  5. 79
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelperService.cs
  6. 6
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/IAbpTagHelperLocalizer.cs
  7. 13
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Pagination/AbpPaginationTagHelperService.cs
  8. 5
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/CachedObjectExtensionsDtoService.cs
  9. 36
      framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs
  10. 1
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
  11. 45
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs
  12. 2
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpDictionaryBasedStringLocalizer.cs
  13. 9
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/HasExtraPropertiesExtensions.cs
  14. 37
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensionPropertyHelper.cs
  15. 20
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/EntityExtensionConfiguration.cs
  16. 22
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ExtensionPropertyConfiguration.cs
  17. 3
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ModuleExtensionConfigurationHelper.cs
  18. 23
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs
  19. 7
      framework/test/Volo.Abp.Core.Tests/Volo/Abp/Reflection/TypeHelper_Tests.cs
  20. 124
      framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/ObjectExtensionManager_Tests.cs

47
docs/en/Object-Extensions.md

@ -174,6 +174,44 @@ ObjectExtensionManager.Instance
The following sections explain the fundamental property configuration options. The following sections explain the fundamental property configuration options.
#### Default Value
A default value is automatically set for the new property, which is the natural default value for the property type, like `null` for `string`, `false` for `bool` or `0` for `int`.
There are two ways to override the default value:
##### DefaultValue Option
`DefaultValue` option can be set to any value:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, int>(
"MyIntProperty",
options =>
{
options.DefaultValue = 42;
});
````
##### DefaultValueFactory Options
`DefaultValueFactory` can be set to a function that returns the default value:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUser, int>(
"MyIntProperty",
options =>
{
options.DefaultValueFactory = () => 42;
});
````
`options.DefaultValueFactory` has a higher priority than the `options.DefaultValue` .
> Tip: Use `DefaultValueFactory` option only if the default value may change over the time. If it is a constant value, then use the `DefaultValue` option.
#### CheckPairDefinitionOnMapping #### CheckPairDefinitionOnMapping
Controls how to check property definitions while mapping two extensible objects. See the "Object to Object Mapping" section to understand the `CheckPairDefinitionOnMapping` option better. Controls how to check property definitions while mapping two extensible objects. See the "Object to Object Mapping" section to understand the `CheckPairDefinitionOnMapping` option better.
@ -208,6 +246,15 @@ ObjectExtensionManager.Instance
With this configuration, `IdentityUserCreateDto` objects will be invalid without a valid `SocialSecurityNumber` value provided. With this configuration, `IdentityUserCreateDto` objects will be invalid without a valid `SocialSecurityNumber` value provided.
#### Default Validation Attributes
There are some attributes **automatically added** when you create certain type of properties;
* `RequiredAttribute` is added for non nullable primitive property types (e.g. `int`, `bool`, `DateTime`...) and `enum` types.
* `EnumDataTypeAttribute` is added for enum types, to prevent to set invalid enum values.
Use `options.Attributes.Clear();` if you don't want these attributes.
### Custom Validation ### Custom Validation
If you need, you can add a custom action that is executed to validate the extra properties. Example: If you need, you can add a custom action that is executed to validate the extra properties. Example:

2
framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/ExtensionPropertyDto.cs

@ -21,5 +21,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ObjectExtending
public List<ExtensionPropertyAttributeDto> Attributes { get; set; } public List<ExtensionPropertyAttributeDto> Attributes { get; set; }
public Dictionary<string, object> Configuration { get; set; } public Dictionary<string, object> Configuration { get; set; }
public object DefaultValue { get; set; }
} }
} }

38
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/AbpTagHelperLocalizer.cs

@ -21,38 +21,30 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers
public string GetLocalizedText(string text, ModelExplorer explorer) public string GetLocalizedText(string text, ModelExplorer explorer)
{ {
var resourceType = GetResourceTypeFromModelExplorer(explorer); var localizer = GetLocalizerOrNull(explorer);
var localizer = GetStringLocalizer(resourceType); return localizer == null
? text
return localizer == null ? text : localizer[text].Value; : localizer[text].Value;
}
public IStringLocalizer GetLocalizer(ModelExplorer explorer)
{
var resourceType = GetResourceTypeFromModelExplorer(explorer);
return GetStringLocalizer(resourceType);
}
public IStringLocalizer GetLocalizer(Assembly assembly)
{
var resourceType = _options.AssemblyResources.GetOrDefault(assembly);
return GetStringLocalizer(resourceType);
} }
public IStringLocalizer GetLocalizer(Type resourceType) public IStringLocalizer GetLocalizerOrNull(ModelExplorer explorer)
{ {
return GetStringLocalizer(resourceType); return GetLocalizerOrNull(explorer.Container.ModelType.Assembly);
} }
private IStringLocalizer GetStringLocalizer(Type resourceType) public IStringLocalizer GetLocalizerOrNull(Assembly assembly)
{ {
return resourceType == null ? null : _stringLocalizerFactory.Create(resourceType); var resourceType = GetResourceType(assembly);
return resourceType == null
? _stringLocalizerFactory.CreateDefaultOrNull()
: _stringLocalizerFactory.Create(resourceType);
} }
private Type GetResourceTypeFromModelExplorer(ModelExplorer explorer) private Type GetResourceType(Assembly assembly)
{ {
var assembly = explorer.Container.ModelType.Assembly; return _options
return _options.AssemblyResources.GetOrDefault(assembly); .AssemblyResources
.GetOrDefault(assembly);
} }
} }
} }

2
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpRadioInputTagHelperService.cs

@ -90,7 +90,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
protected virtual List<SelectListItem> GetSelectItemsFromEnum(TagHelperContext context, TagHelperOutput output, ModelExplorer explorer) protected virtual List<SelectListItem> GetSelectItemsFromEnum(TagHelperContext context, TagHelperOutput output, ModelExplorer explorer)
{ {
var localizer = _tagHelperLocalizer.GetLocalizer(explorer); var localizer = _tagHelperLocalizer.GetLocalizerOrNull(explorer);
var selectItems = explorer.Metadata.IsEnum ? explorer.ModelType.GetTypeInfo().GetMembers(BindingFlags.Public | BindingFlags.Static) var selectItems = explorer.Metadata.IsEnum ? explorer.ModelType.GetTypeInfo().GetMembers(BindingFlags.Public | BindingFlags.Static)
.Select((t, i) => new SelectListItem { Value = i.ToString(), Text = GetLocalizedPropertyName(localizer, explorer.ModelType, t.Name) }).ToList() : null; .Select((t, i) => new SelectListItem { Value = i.ToString(), Text = GetLocalizedPropertyName(localizer, explorer.ModelType, t.Name) }).ToList() : null;

79
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelperService.cs

@ -2,7 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Rendering;
@ -12,6 +11,8 @@ using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Localization; using Microsoft.Extensions.Localization;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
using Volo.Abp.DynamicProxy;
using Volo.Abp.Reflection;
namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
{ {
@ -102,7 +103,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
return TagHelper.AspItems.ToList(); return TagHelper.AspItems.ToList();
} }
if (TagHelper.AspFor.ModelExplorer.Metadata.IsEnum) if (IsEnum())
{ {
return GetSelectItemsFromEnum(context, output, TagHelper.AspFor.ModelExplorer); return GetSelectItemsFromEnum(context, output, TagHelper.AspFor.ModelExplorer);
} }
@ -116,6 +117,17 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
throw new Exception("No items provided for select attribute."); throw new Exception("No items provided for select attribute.");
} }
private bool IsEnum()
{
var value = TagHelper.AspFor.Model;
if (value != null && value.GetType().IsEnum)
{
return true;
}
return TagHelper.AspFor.ModelExplorer.Metadata.IsEnum;
}
protected virtual async Task<string> GetLabelAsHtmlAsync(TagHelperContext context, TagHelperOutput output, TagHelperOutput selectTag) protected virtual async Task<string> GetLabelAsHtmlAsync(TagHelperContext context, TagHelperOutput output, TagHelperOutput selectTag)
{ {
if (!string.IsNullOrEmpty(TagHelper.Label)) if (!string.IsNullOrEmpty(TagHelper.Label))
@ -186,8 +198,6 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
protected virtual List<SelectListItem> GetSelectItemsFromEnum(TagHelperContext context, TagHelperOutput output, ModelExplorer explorer) protected virtual List<SelectListItem> GetSelectItemsFromEnum(TagHelperContext context, TagHelperOutput output, ModelExplorer explorer)
{ {
var localizer = _tagHelperLocalizer.GetLocalizer(explorer);
var selectItems = new List<SelectListItem>(); var selectItems = new List<SelectListItem>();
var isNullableType = Nullable.GetUnderlyingType(explorer.ModelType) != null; var isNullableType = Nullable.GetUnderlyingType(explorer.ModelType) != null;
var enumType = explorer.ModelType; var enumType = explorer.ModelType;
@ -198,26 +208,75 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form
selectItems.Add(new SelectListItem()); selectItems.Add(new SelectListItem());
} }
var containerLocalizer = _tagHelperLocalizer.GetLocalizerOrNull(explorer.Container.ModelType.Assembly);
IStringLocalizer modelObjectLocalizer = null;
if (explorer.Model != null)
{
modelObjectLocalizer = _tagHelperLocalizer.GetLocalizerOrNull(ProxyHelper.UnProxy(explorer.Model).GetType().Assembly);
}
selectItems.AddRange(enumType.GetEnumNames() selectItems.AddRange(enumType.GetEnumNames()
.Select(enumName => new SelectListItem .Select(enumName => new SelectListItem
{ {
Value = Convert.ToUInt64(Enum.Parse(enumType, enumName)).ToString(), Value = Convert.ToUInt64(Enum.Parse(enumType, enumName)).ToString(),
Text = GetLocalizedPropertyName(localizer, enumType, enumName) Text = GetLocalizedEnumFieldName(containerLocalizer, modelObjectLocalizer, enumType, enumName)
})); }));
return selectItems; return selectItems;
} }
protected virtual string GetLocalizedPropertyName(IStringLocalizer localizer, Type enumType, string propertyName) protected virtual string GetLocalizedEnumFieldName(
IStringLocalizer containerLocalizer,
IStringLocalizer modelObjectLocalizer,
Type enumType,
string fieldName)
{ {
if (localizer == null) LocalizedString localizedString;
//Look for the enum name + enum field name
var localizationKey = enumType.Name + "." + fieldName;
if (containerLocalizer != null)
{
localizedString = containerLocalizer[localizationKey];
if (!localizedString.ResourceNotFound)
{
return localizedString.Value;
}
}
if (modelObjectLocalizer != null)
{ {
return propertyName; localizedString = modelObjectLocalizer[localizationKey];
if (!localizedString.ResourceNotFound)
{
return localizedString.Value;
}
} }
var localizedString = localizer[enumType.Name + "." + propertyName]; //Look for the enum field name
localizationKey = fieldName;
if (containerLocalizer != null)
{
localizedString = containerLocalizer[localizationKey];
if (!localizedString.ResourceNotFound)
{
return localizedString.Value;
}
}
if (modelObjectLocalizer != null)
{
localizedString = modelObjectLocalizer[localizationKey];
if (!localizedString.ResourceNotFound)
{
return localizedString.Value;
}
}
return !localizedString.ResourceNotFound ? localizedString.Value : localizer[propertyName].Value; return fieldName;
} }
protected virtual List<SelectListItem> GetSelectItemsFromAttribute( protected virtual List<SelectListItem> GetSelectItemsFromAttribute(

6
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/IAbpTagHelperLocalizer.cs

@ -10,10 +10,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers
{ {
string GetLocalizedText(string text, ModelExplorer explorer); string GetLocalizedText(string text, ModelExplorer explorer);
IStringLocalizer GetLocalizer(ModelExplorer explorer); IStringLocalizer GetLocalizerOrNull(ModelExplorer explorer);
IStringLocalizer GetLocalizer(Assembly assembly); IStringLocalizer GetLocalizerOrNull(Assembly assembly);
IStringLocalizer GetLocalizer(Type resourceType);
} }
} }

13
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Pagination/AbpPaginationTagHelperService.cs

@ -7,6 +7,7 @@ using Localization.Resources.AbpUi;
using Microsoft.AspNetCore.Mvc.TagHelpers; using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Localization;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Microsoft.AspNetCore.Razor.TagHelpers;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions;
@ -17,12 +18,18 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination
private readonly IHtmlGenerator _generator; private readonly IHtmlGenerator _generator;
private readonly HtmlEncoder _encoder; private readonly HtmlEncoder _encoder;
private readonly IAbpTagHelperLocalizer _tagHelperLocalizer; private readonly IAbpTagHelperLocalizer _tagHelperLocalizer;
private readonly IStringLocalizerFactory _stringLocalizerFactory;
public AbpPaginationTagHelperService(IHtmlGenerator generator, HtmlEncoder encoder, IAbpTagHelperLocalizer tagHelperLocalizer) public AbpPaginationTagHelperService(
IHtmlGenerator generator,
HtmlEncoder encoder,
IAbpTagHelperLocalizer tagHelperLocalizer,
IStringLocalizerFactory stringLocalizerFactory)
{ {
_generator = generator; _generator = generator;
_encoder = encoder; _encoder = encoder;
_tagHelperLocalizer = tagHelperLocalizer; _tagHelperLocalizer = tagHelperLocalizer;
_stringLocalizerFactory = stringLocalizerFactory;
} }
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
@ -119,7 +126,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination
protected virtual async Task<string> RenderAnchorTagHelperLinkHtmlAsync(TagHelperContext context, TagHelperOutput output, string currentPage, string localizationKey) protected virtual async Task<string> RenderAnchorTagHelperLinkHtmlAsync(TagHelperContext context, TagHelperOutput output, string currentPage, string localizationKey)
{ {
var localizer = _tagHelperLocalizer.GetLocalizer(typeof(AbpUiResource)); var localizer = _stringLocalizerFactory.Create(typeof(AbpUiResource));
var anchorTagHelper = GetAnchorTagHelper(currentPage, out var attributeList); var anchorTagHelper = GetAnchorTagHelper(currentPage, out var attributeList);
@ -156,7 +163,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination
protected virtual string GetOpeningTags(TagHelperContext context, TagHelperOutput output) protected virtual string GetOpeningTags(TagHelperContext context, TagHelperOutput output)
{ {
var localizer = _tagHelperLocalizer.GetLocalizer(typeof(AbpUiResource)); var localizer = _stringLocalizerFactory.Create(typeof(AbpUiResource));
var pagerInfo = (TagHelper.ShowInfo ?? false) ? var pagerInfo = (TagHelper.ShowInfo ?? false) ?
" <div class=\"col-sm-12 col-md-5\"> " + localizer["PagerInfo{0}{1}{2}", TagHelper.Model.ShowingFrom, TagHelper.Model.ShowingTo, TagHelper.Model.TotalItemsCount] + "</div>\r\n" " <div class=\"col-sm-12 col-md-5\"> " + localizer["PagerInfo{0}{1}{2}", TagHelper.Model.ShowingFrom, TagHelper.Model.ShowingTo, TagHelper.Model.TotalItemsCount] + "</div>\r\n"

5
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ObjectExtending/CachedObjectExtensionsDtoService.cs

@ -104,10 +104,13 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ObjectExtending
var extensionPropertyDto = new ExtensionPropertyDto var extensionPropertyDto = new ExtensionPropertyDto
{ {
Type = TypeHelper.GetFullNameHandlingNullableAndGenerics(propertyConfig.Type), Type = TypeHelper.GetFullNameHandlingNullableAndGenerics(propertyConfig.Type),
TypeSimple = TypeHelper.GetSimplifiedName(propertyConfig.Type), TypeSimple = propertyConfig.Type.IsEnum
? "enum"
: TypeHelper.GetSimplifiedName(propertyConfig.Type),
Attributes = new List<ExtensionPropertyAttributeDto>(), Attributes = new List<ExtensionPropertyAttributeDto>(),
DisplayName = CreateDisplayNameDto(propertyConfig), DisplayName = CreateDisplayNameDto(propertyConfig),
Configuration = new Dictionary<string, object>(), Configuration = new Dictionary<string, object>(),
DefaultValue = propertyConfig.GetDefaultValue(),
Api = new ExtensionPropertyApiDto Api = new ExtensionPropertyApiDto
{ {
OnGet = new ExtensionPropertyApiGetDto OnGet = new ExtensionPropertyApiGetDto

36
framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs

@ -10,6 +10,30 @@ namespace Volo.Abp.Reflection
{ {
public static class TypeHelper public static class TypeHelper
{ {
private static readonly HashSet<Type> NonNullablePrimitiveTypes = new HashSet<Type>
{
typeof(byte),
typeof(short),
typeof(int),
typeof(long),
typeof(sbyte),
typeof(ushort),
typeof(uint),
typeof(ulong),
typeof(bool),
typeof(float),
typeof(decimal),
typeof(DateTime),
typeof(DateTimeOffset),
typeof(TimeSpan),
typeof(Guid)
};
public static bool IsNonNullablePrimitiveType(Type type)
{
return NonNullablePrimitiveTypes.Contains(type);
}
public static bool IsFunc(object obj) public static bool IsFunc(object obj)
{ {
if (obj == null) if (obj == null)
@ -277,5 +301,17 @@ namespace Volo.Abp.Reflection
.GetConverter(targetType) .GetConverter(targetType)
.ConvertFromString(value); .ConvertFromString(value);
} }
public static object ConvertFrom<TTargetType>(object value)
{
return ConvertFrom(typeof(TTargetType), value);
}
public static object ConvertFrom(Type targetType, object value)
{
return TypeDescriptor
.GetConverter(targetType)
.ConvertFrom(value);
}
} }
} }

1
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs

@ -194,6 +194,7 @@ namespace Volo.Abp.EntityFrameworkCore
{ {
continue; continue;
} }
/* Checking "currentValue != null" has a good advantage: /* Checking "currentValue != null" has a good advantage:
* Assume that you we already using a named extra property, * Assume that you we already using a named extra property,
* then decided to create a field (entity extension) for it. * then decided to create a field (entity extension) for it.

45
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Newtonsoft.Json; using Newtonsoft.Json;
using Volo.Abp.ObjectExtending; using Volo.Abp.ObjectExtending;
@ -11,7 +12,7 @@ namespace Volo.Abp.EntityFrameworkCore.ValueConverters
public ExtraPropertiesValueConverter(Type entityType) public ExtraPropertiesValueConverter(Type entityType)
: base( : base(
d => SerializeObject(d, entityType), d => SerializeObject(d, entityType),
s => DeserializeObject(s)) s => DeserializeObject(s, entityType))
{ {
} }
@ -38,9 +39,47 @@ namespace Volo.Abp.EntityFrameworkCore.ValueConverters
return JsonConvert.SerializeObject(copyDictionary, Formatting.None); return JsonConvert.SerializeObject(copyDictionary, Formatting.None);
} }
private static Dictionary<string, object> DeserializeObject(string extraPropertiesAsJson) private static Dictionary<string, object> DeserializeObject(string extraPropertiesAsJson, Type entityType)
{ {
return JsonConvert.DeserializeObject<Dictionary<string, object>>(extraPropertiesAsJson); var dictionary = JsonConvert.DeserializeObject<Dictionary<string, object>>(extraPropertiesAsJson);
if (entityType != null)
{
var objectExtension = ObjectExtensionManager.Instance.GetOrNull(entityType);
if (objectExtension != null)
{
foreach (var property in objectExtension.GetProperties())
{
dictionary[property.Name] = GetNormalizedValue(dictionary, property);
}
}
}
return dictionary;
}
private static object GetNormalizedValue(Dictionary<string, object> dictionary, ObjectExtensionPropertyInfo property)
{
var value = dictionary.GetOrDefault(property.Name);
if (value == null)
{
return null;
}
try
{
if (property.Type.IsEnum)
{
return Enum.Parse(property.Type, value.ToString(), true);
}
//return Convert.ChangeType(value, property.Type);
return value;
}
catch
{
return value;
}
} }
} }
} }

2
framework/src/Volo.Abp.Localization/Volo/Abp/Localization/AbpDictionaryBasedStringLocalizer.cs

@ -177,9 +177,7 @@ namespace Volo.Abp.Localization
return allStrings.Values.ToImmutableList(); return allStrings.Values.ToImmutableList();
} }
public class CultureWrapperStringLocalizer : IStringLocalizer, IStringLocalizerSupportsInheritance public class CultureWrapperStringLocalizer : IStringLocalizer, IStringLocalizerSupportsInheritance
{ {
private readonly string _cultureName; private readonly string _cultureName;

9
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/HasExtraPropertiesExtensions.cs

@ -65,8 +65,13 @@ namespace Volo.Abp.Data
public static TSource SetDefaultsForExtraProperties<TSource>(this TSource source, Type objectType = null) public static TSource SetDefaultsForExtraProperties<TSource>(this TSource source, Type objectType = null)
where TSource : IHasExtraProperties where TSource : IHasExtraProperties
{ {
if (objectType == null)
{
objectType = typeof(TSource);
}
var properties = ObjectExtensionManager.Instance var properties = ObjectExtensionManager.Instance
.GetProperties(objectType ?? typeof(TSource)); .GetProperties(objectType);
foreach (var property in properties) foreach (var property in properties)
{ {
@ -75,7 +80,7 @@ namespace Volo.Abp.Data
continue; continue;
} }
source.ExtraProperties[property.Name] = TypeHelper.GetDefaultValue(property.Type); source.ExtraProperties[property.Name] = property.GetDefaultValue();
} }
return source; return source;

37
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensionPropertyHelper.cs

@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Reflection;
namespace Volo.Abp.ObjectExtending
{
internal static class ExtensionPropertyHelper
{
public static IEnumerable<Attribute> GetDefaultAttributes(Type type)
{
if (TypeHelper.IsNonNullablePrimitiveType(type) || type.IsEnum)
{
yield return new RequiredAttribute();
}
if (type.IsEnum)
{
yield return new EnumDataTypeAttribute(type);
}
}
public static object GetDefaultValue(
Type propertyType,
Func<object> defaultValueFactory,
object defaultValue)
{
if (defaultValueFactory != null)
{
return defaultValueFactory();
}
return defaultValue ??
TypeHelper.GetDefaultValue(propertyType);
}
}
}

20
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/EntityExtensionConfiguration.cs

@ -50,6 +50,8 @@ namespace Volo.Abp.ObjectExtending.Modularity
configureAction?.Invoke(propertyInfo); configureAction?.Invoke(propertyInfo);
NormalizeProperty(propertyInfo);
return this; return this;
} }
@ -58,5 +60,23 @@ namespace Volo.Abp.ObjectExtending.Modularity
{ {
return Properties.Values.ToImmutableList(); return Properties.Values.ToImmutableList();
} }
private static void NormalizeProperty(ExtensionPropertyConfiguration propertyInfo)
{
if (!propertyInfo.Api.OnGet.IsAvailable)
{
propertyInfo.UI.OnTable.IsVisible = false;
}
if (!propertyInfo.Api.OnCreate.IsAvailable)
{
propertyInfo.UI.OnCreateForm.IsVisible = false;
}
if (!propertyInfo.Api.OnUpdate.IsAvailable)
{
propertyInfo.UI.OnEditForm.IsVisible = false;
}
}
} }
} }

22
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ExtensionPropertyConfiguration.cs

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using JetBrains.Annotations; using JetBrains.Annotations;
using Volo.Abp.Localization; using Volo.Abp.Localization;
using Volo.Abp.Reflection;
namespace Volo.Abp.ObjectExtending.Modularity namespace Volo.Abp.ObjectExtending.Modularity
{ {
@ -45,6 +46,19 @@ namespace Volo.Abp.ObjectExtending.Modularity
[NotNull] [NotNull]
public ExtensionPropertyApiConfiguration Api { get; } public ExtensionPropertyApiConfiguration Api { get; }
/// <summary>
/// Uses as the default value if <see cref="DefaultValueFactory"/> was not set.
/// </summary>
[CanBeNull]
public object DefaultValue { get; set; }
/// <summary>
/// Used with the first priority to create the default value for the property.
/// Uses to the <see cref="DefaultValue"/> if this was not set.
/// </summary>
[CanBeNull]
public Func<object> DefaultValueFactory { get; set; }
public ExtensionPropertyConfiguration( public ExtensionPropertyConfiguration(
[NotNull] EntityExtensionConfiguration entityExtensionConfiguration, [NotNull] EntityExtensionConfiguration entityExtensionConfiguration,
[NotNull] Type type, [NotNull] Type type,
@ -61,6 +75,14 @@ namespace Volo.Abp.ObjectExtending.Modularity
Entity = new ExtensionPropertyEntityConfiguration(); Entity = new ExtensionPropertyEntityConfiguration();
UI = new ExtensionPropertyUiConfiguration(); UI = new ExtensionPropertyUiConfiguration();
Api = new ExtensionPropertyApiConfiguration(); Api = new ExtensionPropertyApiConfiguration();
Attributes.AddRange(ExtensionPropertyHelper.GetDefaultAttributes(Type));
DefaultValue = TypeHelper.GetDefaultValue(Type);
}
public object GetDefaultValue()
{
return ExtensionPropertyHelper.GetDefaultValue(Type, DefaultValueFactory, DefaultValue);
} }
} }
} }

3
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ModuleExtensionConfigurationHelper.cs

@ -148,9 +148,12 @@ namespace Volo.Abp.ObjectExtending.Modularity
propertyConfig.Name, propertyConfig.Name,
property => property =>
{ {
property.Attributes.Clear();
property.Attributes.AddRange(propertyConfig.Attributes); property.Attributes.AddRange(propertyConfig.Attributes);
property.DisplayName = propertyConfig.DisplayName; property.DisplayName = propertyConfig.DisplayName;
property.Validators.AddRange(propertyConfig.Validators); property.Validators.AddRange(propertyConfig.Validators);
property.DefaultValue = propertyConfig.DefaultValue;
property.DefaultValueFactory = propertyConfig.DefaultValueFactory;
} }
); );
} }

23
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs

@ -2,8 +2,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.Extensions.Localization;
using Volo.Abp.Localization; using Volo.Abp.Localization;
using Volo.Abp.Reflection;
namespace Volo.Abp.ObjectExtending namespace Volo.Abp.ObjectExtending
{ {
@ -48,6 +48,19 @@ namespace Volo.Abp.ObjectExtending
[NotNull] [NotNull]
public Dictionary<object, object> Configuration { get; } public Dictionary<object, object> Configuration { get; }
/// <summary>
/// Uses as the default value if <see cref="DefaultValueFactory"/> was not set.
/// </summary>
[CanBeNull]
public object DefaultValue { get; set; }
/// <summary>
/// Used with the first priority to create the default value for the property.
/// Uses to the <see cref="DefaultValue"/> if this was not set.
/// </summary>
[CanBeNull]
public Func<object> DefaultValueFactory { get; set; }
public ObjectExtensionPropertyInfo( public ObjectExtensionPropertyInfo(
[NotNull] ObjectExtensionInfo objectExtension, [NotNull] ObjectExtensionInfo objectExtension,
[NotNull] Type type, [NotNull] Type type,
@ -61,6 +74,14 @@ namespace Volo.Abp.ObjectExtending
ValidationAttributes = new List<ValidationAttribute>(); ValidationAttributes = new List<ValidationAttribute>();
Attributes = new List<Attribute>(); Attributes = new List<Attribute>();
Validators = new List<Action<ObjectExtensionPropertyValidationContext>>(); Validators = new List<Action<ObjectExtensionPropertyValidationContext>>();
Attributes.AddRange(ExtensionPropertyHelper.GetDefaultAttributes(Type));
DefaultValue = TypeHelper.GetDefaultValue(Type);
}
public object GetDefaultValue()
{
return ExtensionPropertyHelper.GetDefaultValue(Type, DefaultValueFactory, DefaultValue);
} }
} }
} }

7
framework/test/Volo.Abp.Core.Tests/Volo/Abp/Reflection/TypeHelper_Tests.cs

@ -7,6 +7,13 @@ namespace Volo.Abp.Reflection
{ {
public class TypeHelper_Tests public class TypeHelper_Tests
{ {
[Fact]
public void IsNonNullablePrimitiveType()
{
TypeHelper.IsNonNullablePrimitiveType(typeof(int)).ShouldBeTrue();
TypeHelper.IsNonNullablePrimitiveType(typeof(string)).ShouldBeFalse();
}
[Fact] [Fact]
public void Should_Generic_Type_From_Nullable() public void Should_Generic_Type_From_Nullable()
{ {

124
framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/ObjectExtensionManager_Tests.cs

@ -1,4 +1,5 @@
using System.Linq; using System.ComponentModel.DataAnnotations;
using System.Linq;
using Shouldly; using Shouldly;
using Xunit; using Xunit;
@ -22,7 +23,7 @@ namespace Volo.Abp.ObjectExtending
var objectExtension = _objectExtensionManager.GetOrNull<MyExtensibleObject>(); var objectExtension = _objectExtensionManager.GetOrNull<MyExtensibleObject>();
objectExtension.ShouldNotBeNull(); objectExtension.ShouldNotBeNull();
var properties = objectExtension.GetProperties(); var properties = objectExtension.GetProperties();
properties.Count.ShouldBe(1); properties.Count.ShouldBe(1);
properties.FirstOrDefault(p => p.Name == "TestProp").ShouldNotBeNull(); properties.FirstOrDefault(p => p.Name == "TestProp").ShouldNotBeNull();
@ -55,9 +56,128 @@ namespace Volo.Abp.ObjectExtending
property.Configuration["TestConfig2"].ShouldBe("TestConfig2-Value"); property.Configuration["TestConfig2"].ShouldBe("TestConfig2-Value");
} }
[Fact]
public void Should_Automatically_Add_RequiredAttribute_To_Non_Nullable_Types_And_Enums()
{
_objectExtensionManager
.AddOrUpdateProperty<MyExtensibleObject, int>("IntProp")
.AddOrUpdateProperty<MyExtensibleObject, bool>("BoolProp")
.AddOrUpdateProperty<MyExtensibleObject, int?>("NullableIntProp")
.AddOrUpdateProperty<MyExtensibleObject, string>("StringProp")
.AddOrUpdateProperty<MyExtensibleObject, MyTestEnum>("EnumProp");
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("IntProp")
.Attributes
.ShouldContain(x => x is RequiredAttribute);
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("BoolProp")
.Attributes
.ShouldContain(x => x is RequiredAttribute);
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("EnumProp")
.Attributes
.ShouldContain(x => x is RequiredAttribute);
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("NullableIntProp")
.Attributes
.ShouldNotContain(x => x is RequiredAttribute);
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("StringProp")
.Attributes
.ShouldNotContain(x => x is RequiredAttribute);
}
[Fact]
public void Should_Automatically_Add_EnumDataTypeAttribute_For_Enums()
{
_objectExtensionManager
.AddOrUpdateProperty<MyExtensibleObject, MyTestEnum>("EnumProp");
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("EnumProp")
.Attributes
.ShouldContain(x => x is EnumDataTypeAttribute);
}
[Fact]
public void Should_Be_Able_To_Clear_Auto_Added_Attributes()
{
_objectExtensionManager
.AddOrUpdateProperty<MyExtensibleObject, int>("IntProp", property =>
{
property.Attributes.Clear();
});
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("IntProp")
.Attributes
.ShouldNotContain(x => x is RequiredAttribute);
}
[Fact]
public void Should_Set_DefaultValues()
{
_objectExtensionManager
.AddOrUpdateProperty<MyExtensibleObject, int>("IntProp")
.AddOrUpdateProperty<MyExtensibleObject, int>("IntPropWithCustomDefaultValue", property =>
{
property.DefaultValue = 42;
})
.AddOrUpdateProperty<MyExtensibleObject, bool>("BoolProp")
.AddOrUpdateProperty<MyExtensibleObject, int?>("NullableIntProp")
.AddOrUpdateProperty<MyExtensibleObject, int?>("NullableIntPropWithCustomDefaultValueFactory", property =>
{
property.DefaultValueFactory = () => 2;
})
.AddOrUpdateProperty<MyExtensibleObject, string>("StringProp")
.AddOrUpdateProperty<MyExtensibleObject, string>("StringPropWithCustomDefaultValue", property =>
{
property.DefaultValue = "custom-value";
});
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("IntProp")
.DefaultValue.ShouldBe(0);
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("IntPropWithCustomDefaultValue")
.DefaultValue.ShouldBe(42);
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("BoolProp")
.DefaultValue.ShouldBe(false);
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("NullableIntProp")
.DefaultValue.ShouldBeNull();
var propWithDefaultValueFactory = _objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("NullableIntPropWithCustomDefaultValueFactory");
propWithDefaultValueFactory.GetDefaultValue().ShouldBe(2);
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("StringProp")
.DefaultValue.ShouldBeNull();
_objectExtensionManager
.GetPropertyOrNull<MyExtensibleObject>("StringPropWithCustomDefaultValue")
.DefaultValue.ShouldBe("custom-value");
}
private class MyExtensibleObject : ExtensibleObject private class MyExtensibleObject : ExtensibleObject
{ {
} }
private enum MyTestEnum
{
EnumValue1,
EnumValue2,
}
} }
} }

Loading…
Cancel
Save