diff --git a/src/Plugins/Nop.Plugin.Widgets.FacebookPixel/Infrastructure/Mapper/MapperConfiguration.cs b/src/Plugins/Nop.Plugin.Widgets.FacebookPixel/Infrastructure/Mapper/MapperConfiguration.cs index a40764b4fa4..79b4e2b786a 100644 --- a/src/Plugins/Nop.Plugin.Widgets.FacebookPixel/Infrastructure/Mapper/MapperConfiguration.cs +++ b/src/Plugins/Nop.Plugin.Widgets.FacebookPixel/Infrastructure/Mapper/MapperConfiguration.cs @@ -17,6 +17,7 @@ public MapperConfiguration() CreateMap() .ForMember(model => model.AvailableStores, options => options.Ignore()) .ForMember(model => model.CustomEventSearchModel, options => options.Ignore()) + .ForMember(model => model.CustomProperties, options => options.Ignore()) .ForMember(model => model.HideCustomEventsSearch, options => options.Ignore()) .ForMember(model => model.HideStoresList, options => options.Ignore()) .ForMember(model => model.StoreName, options => options.Ignore()); diff --git a/src/Presentation/Nop.Web.Framework/Infrastructure/Extensions/ServiceCollectionExtensions.cs b/src/Presentation/Nop.Web.Framework/Infrastructure/Extensions/ServiceCollectionExtensions.cs index df04061a649..b42306e9dda 100644 --- a/src/Presentation/Nop.Web.Framework/Infrastructure/Extensions/ServiceCollectionExtensions.cs +++ b/src/Presentation/Nop.Web.Framework/Infrastructure/Extensions/ServiceCollectionExtensions.cs @@ -306,6 +306,7 @@ public static IMvcBuilder AddNopMvc(this IServiceCollection services) { //we'll use this until https://github.com/dotnet/aspnetcore/issues/6566 is solved options.ModelBinderProviders.Insert(0, new InvariantNumberModelBinderProvider()); + options.ModelBinderProviders.Insert(1, new CustomPropertiesModelBinderProvider()); //add custom display metadata provider options.ModelMetadataDetailsProviders.Add(new NopMetadataProvider()); diff --git a/src/Presentation/Nop.Web.Framework/Models/BaseNopModel.cs b/src/Presentation/Nop.Web.Framework/Models/BaseNopModel.cs index 7294397d04b..9f688b7ccd2 100644 --- a/src/Presentation/Nop.Web.Framework/Models/BaseNopModel.cs +++ b/src/Presentation/Nop.Web.Framework/Models/BaseNopModel.cs @@ -1,4 +1,9 @@ -namespace Nop.Web.Framework.Models +using System; +using System.Collections.Generic; +using System.Xml.Serialization; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Nop.Web.Framework.Models { /// /// Represents base nopCommerce model @@ -10,15 +15,26 @@ public partial record BaseNopModel /// /// Ctor /// + [Obsolete] public BaseNopModel() { + CustomProperties = new Dictionary(); PostInitialize(); } #endregion #region Methods - + + /// + /// Perform additional actions for binding the model + /// + /// Model binding context + /// Developers can override this method in custom partial classes in order to add some custom model binding + public virtual void BindModel(ModelBindingContext bindingContext) + { + } + /// /// Perform additional actions for the model initialization /// @@ -28,5 +44,21 @@ protected virtual void PostInitialize() } #endregion + + #region Properties + + ////MVC is suppressing further validation if the IFormCollection is passed to a controller method. That's why we add it to the model + //[XmlIgnore] + //public IFormCollection Form { get; set; } + + /// + /// Gets or sets property to store any custom values for models + /// + [XmlIgnore] + [Obsolete] + public Dictionary CustomProperties { get; set; } + + #endregion + } } \ No newline at end of file diff --git a/src/Presentation/Nop.Web.Framework/Mvc/ModelBinding/Binders/CustomPropertiesModelBinder.cs b/src/Presentation/Nop.Web.Framework/Mvc/ModelBinding/Binders/CustomPropertiesModelBinder.cs new file mode 100644 index 00000000000..70bc61463f9 --- /dev/null +++ b/src/Presentation/Nop.Web.Framework/Mvc/ModelBinding/Binders/CustomPropertiesModelBinder.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Nop.Web.Framework.Mvc.ModelBinding.Binders +{ + /// + /// Represents model binder for CustomProperties + /// + [Obsolete] + public class CustomPropertiesModelBinder : IModelBinder + { + Task IModelBinder.BindModelAsync(ModelBindingContext bindingContext) + { + if (bindingContext == null) + throw new ArgumentNullException(nameof(bindingContext)); + + var modelName = bindingContext.ModelName; + + var result = new Dictionary(); + if (bindingContext.HttpContext.Request.Method == "POST") + { + var keys = bindingContext.HttpContext.Request.Form.Keys.ToList().Where(x => x.IndexOf(modelName) == 0); + + if (keys != null && keys.Any()) + { + foreach (var key in keys) + { + var dicKey = key.Replace(modelName + "[", "").Replace("]", ""); + bindingContext.HttpContext.Request.Form.TryGetValue(key, out var value); + result.Add(dicKey, value.ToString()); + } + } + } + if (bindingContext.HttpContext.Request.Method == "GET") + { + var keys = bindingContext.HttpContext.Request.QueryString.Value.Split('&').Where(x => x.StartsWith(modelName)); + + if (keys != null && keys.Any()) + { + foreach (var key in keys) + { + var dicKey = key[(key.IndexOf("[") + 1)..key.IndexOf("]")]; + var value = key[(key.IndexOf("=") + 1)..]; + + result.Add(dicKey, value); + } + } + } + bindingContext.Result = ModelBindingResult.Success(result); + + return Task.CompletedTask; + + } + } +} diff --git a/src/Presentation/Nop.Web.Framework/Mvc/ModelBinding/Binders/CustomPropertiesModelBinderProvider.cs b/src/Presentation/Nop.Web.Framework/Mvc/ModelBinding/Binders/CustomPropertiesModelBinderProvider.cs new file mode 100644 index 00000000000..1b4d1007709 --- /dev/null +++ b/src/Presentation/Nop.Web.Framework/Mvc/ModelBinding/Binders/CustomPropertiesModelBinderProvider.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Nop.Web.Framework.Models; + +namespace Nop.Web.Framework.Mvc.ModelBinding.Binders +{ + /// + /// Represents a model binder provider for CustomProperties + /// + [Obsolete] + public class CustomPropertiesModelBinderProvider : IModelBinderProvider + { + IModelBinder IModelBinderProvider.GetBinder(ModelBinderProviderContext context) + { + var propertyBinders = context.Metadata.Properties + .ToDictionary(modelProperty => modelProperty, modelProperty => context.CreateBinder(modelProperty)); + + if (context.Metadata.ModelType == typeof(Dictionary) && context.Metadata.PropertyName == nameof(BaseNopModel.CustomProperties)) + return new CustomPropertiesModelBinder(); + else + return null; + } + } +} diff --git a/src/Presentation/Nop.Web/Areas/Admin/Infrastructure/Mapper/AdminMapperConfiguration.cs b/src/Presentation/Nop.Web/Areas/Admin/Infrastructure/Mapper/AdminMapperConfiguration.cs index d922084d111..6804c76bd6f 100644 --- a/src/Presentation/Nop.Web/Areas/Admin/Infrastructure/Mapper/AdminMapperConfiguration.cs +++ b/src/Presentation/Nop.Web/Areas/Admin/Infrastructure/Mapper/AdminMapperConfiguration.cs @@ -114,6 +114,13 @@ public AdminMapperConfiguration() //add some generic mapping rules this.Internal().ForAllMaps((mapConfiguration, map) => { + //exclude Form and CustomProperties from mapping BaseNopModel + if (typeof(BaseNopModel).IsAssignableFrom(mapConfiguration.DestinationType)) + { + //map.ForMember(nameof(BaseNopModel.Form), options => options.Ignore()); + map.ForMember(nameof(BaseNopModel.CustomProperties), options => options.Ignore()); + } + //exclude ActiveStoreScopeConfiguration from mapping ISettingsModel if (typeof(ISettingsModel).IsAssignableFrom(mapConfiguration.DestinationType)) map.ForMember(nameof(ISettingsModel.ActiveStoreScopeConfiguration), options => options.Ignore());