From de81fca5db3870c56cfbdd6ff86e4a7468935820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Sat, 25 Apr 2020 14:44:15 +0300 Subject: [PATCH] Added binder for extra properties --- .../AspNetCore/Mvc/AbpMvcOptionsExtensions.cs | 1 + ...PropertiesDictionaryModelBinderProvider.cs | 48 +++++++++++ .../AbpExtraPropertyModelBinder.cs | 79 +++++++++++++++++++ 3 files changed, 128 insertions(+) create mode 100644 framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpExtraPropertiesDictionaryModelBinderProvider.cs create mode 100644 framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpExtraPropertyModelBinder.cs diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs index f1225f1abf..59166d3e1d 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs @@ -48,6 +48,7 @@ namespace Volo.Abp.AspNetCore.Mvc private static void AddModelBinders(MvcOptions options) { options.ModelBinderProviders.Insert(0, new AbpDateTimeModelBinderProvider()); + options.ModelBinderProviders.Insert(0, new AbpExtraPropertiesDictionaryModelBinderProvider()); } private static void AddMetadataProviders(MvcOptions options, IServiceCollection services) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpExtraPropertiesDictionaryModelBinderProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpExtraPropertiesDictionaryModelBinderProvider.cs new file mode 100644 index 0000000000..3ed1eda3a1 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpExtraPropertiesDictionaryModelBinderProvider.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Binders; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Volo.Abp.Data; + +namespace Volo.Abp.AspNetCore.Mvc.ModelBinding +{ + public class AbpExtraPropertiesDictionaryModelBinderProvider : IModelBinderProvider + { + public IModelBinder GetBinder(ModelBinderProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (context.Metadata.ModelType != typeof(Dictionary)) + { + return null; + } + + if (!context.Metadata.ContainerType.IsAssignableTo()) + { + return null; + } + + var binderType = typeof(DictionaryModelBinder); + var keyBinder = context.CreateBinder(context.MetadataProvider.GetMetadataForType(typeof(string))); + var valueBinder = new AbpExtraPropertyModelBinder(context.Metadata.ContainerType); + var loggerFactory = context.Services.GetRequiredService(); + var mvcOptions = context.Services.GetRequiredService>().Value; + + return (IModelBinder)Activator.CreateInstance( + binderType, + keyBinder, + valueBinder, + loggerFactory, + true /* allowValidatingTopLevelNodes */, + mvcOptions); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpExtraPropertyModelBinder.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpExtraPropertyModelBinder.cs new file mode 100644 index 0000000000..da199cae90 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ModelBinding/AbpExtraPropertyModelBinder.cs @@ -0,0 +1,79 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Volo.Abp.ObjectExtending; + +namespace Volo.Abp.AspNetCore.Mvc.ModelBinding +{ + public class AbpExtraPropertyModelBinder : IModelBinder + { + public Type ExtensibleObjectType { get; } + + public AbpExtraPropertyModelBinder(Type extensibleObjectType) + { + ExtensibleObjectType = extensibleObjectType; + } + + public virtual Task BindModelAsync(ModelBindingContext bindingContext) + { + var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); + if (valueProviderResult == ValueProviderResult.None) + { + return Task.CompletedTask; + } + + bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult); + + var model = ConvertStringToPropertyType( + bindingContext, + valueProviderResult.FirstValue + ); + + bindingContext.Result = ModelBindingResult.Success(model); + + return Task.CompletedTask; + } + + protected virtual object ConvertStringToPropertyType(ModelBindingContext bindingContext, string value) + { + if (bindingContext.ModelMetadata.ConvertEmptyStringToNull && string.IsNullOrWhiteSpace(value)) + { + return null; + } + + var extensionInfo = ObjectExtensionManager.Instance.GetOrNull(ExtensibleObjectType); + if (extensionInfo == null) + { + return value; + } + + var propertyName = ExtractExtraPropertyName(bindingContext.ModelName); + if (propertyName == null) + { + return value; + } + + var propertyInfo = extensionInfo.GetPropertyOrNull(propertyName); + if (propertyInfo == null) + { + return value; + } + + return Convert.ChangeType(value, propertyInfo.Type); + } + + /* modelName is a string like "UserInfo.ExtraProperties[SocialSecurityNumber]" + * This method returns "SocialSecurityNumber" for this example. */ + protected virtual string ExtractExtraPropertyName(string modelName) + { + //TODO: Use regex(?) and add unit test (by extracting to a helper class) + var index = modelName.IndexOf(".ExtraProperties[", StringComparison.Ordinal); + if (index < 0) + { + return null; + } + + return modelName.Substring(index + 17, modelName.Length - index - 18); + } + } +} \ No newline at end of file