ASP.NET MVC:数据input后修剪string的最佳方法。 我应该创build一个自定义模型绑定
我正在使用ASP.NET MVC,我想所有用户input的string字段被修剪之前,他们被插入到数据库中。 由于我有很多数据input表单,我正在寻找一个优雅的方式来修剪所有string,而不是显式修剪每个用户提供的string值。 我很想知道如何以及何时修剪string。
我想过也许创build一个自定义的模型绑定器并修剪任何string值…那样,我所有的修剪逻辑都包含在一个地方。 这是一个好方法吗? 是否有任何代码示例这样做?
public class TrimModelBinder : DefaultModelBinder { protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, object value) { if (propertyDescriptor.PropertyType == typeof(string)) { var stringValue = (string)value; if (!string.IsNullOrWhiteSpace(stringValue)) { value = stringValue.Trim(); } else { value = null; } } base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value); } }
这个代码如何?
ModelBinders.Binders.DefaultBinder = new TrimModelBinder();
设置global.asax Application_Start事件。
这是@takepara相同的分辨率,但作为IModelBinder而不是DefaultModelBinder,以便在global.asax中添加modelbinder是通过
ModelBinders.Binders.Add(typeof(string),new TrimModelBinder());
class上:
public class TrimModelBinder : IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); if (valueResult== null || valueResult.AttemptedValue==null) return null; else if (valueResult.AttemptedValue == string.Empty) return string.Empty; return valueResult.AttemptedValue.Trim(); } }
基于@haacked文章: http ://haacked.com/archive/2011/03/19/fixing-binding-to-decimals.aspx
@takepara答案的一个改进。
有些在项目中:
public class NoTrimAttribute : Attribute { }
在TrimModelBinder类中更改
if (propertyDescriptor.PropertyType == typeof(string))
至
if (propertyDescriptor.PropertyType == typeof(string) && !propertyDescriptor.Attributes.Cast<object>().Any(a => a.GetType() == typeof(NoTrimAttribute)))
您可以使用[NoTrim]属性标记要从修剪中排除的属性。
通过对C#6的改进,您现在可以编写一个非常紧凑的模型绑定器,它将修剪所有的stringinput:
public class TrimStringModelBinder : IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); var attemptedValue = value?.AttemptedValue; return string.IsNullOrWhiteSpace(attemptedValue) ? attemptedValue : attemptedValue.Trim(); } }
您需要在Global.asax.cs
文件中的Application_Start()
某处包含此行以在绑定string
s时使用模型联编程序:
ModelBinders.Binders.Add(typeof(string), new TrimStringModelBinder());
我发现最好使用这样的模型绑定器,而不是重写默认模型绑定器,因为无论何时绑定一个string
,无论是直接作为方法参数,还是作为模型类的属性,都会使用它。 但是,如果您在此处build议的其他答案中覆盖了默认的模型联编程序,那么只有在模型上绑定属性时才能使用, 而不是在将string
作为parameter passing给某个操作方法时使用。
@ takepara的答案的另一个变种,但有一个不同的转折:
1)我更喜欢select“StringTrim”属性机制(而不是@Anton的select“NoTrim”示例)。
2)需要对SetModelValue进行额外的调用,以确保ModelState正确填充,并且可以使用默认的validation/接受/拒绝模式,即应用TryUpdateModel(model)和使用ModelState.Clear()接受所有更改。
把它放在你的实体/共享库中:
/// <summary> /// Denotes a data field that should be trimmed during binding, removing any spaces. /// </summary> /// <remarks> /// <para> /// Support for trimming is implmented in the model binder, as currently /// Data Annotations provides no mechanism to coerce the value. /// </para> /// <para> /// This attribute does not imply that empty strings should be converted to null. /// When that is required you must additionally use the <see cref="System.ComponentModel.DataAnnotations.DisplayFormatAttribute.ConvertEmptyStringToNull"/> /// option to control what happens to empty strings. /// </para> /// </remarks> [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] public class StringTrimAttribute : Attribute { }
然后在你的MVC应用程序/库中:
/// <summary> /// MVC model binder which trims string values decorated with the <see cref="StringTrimAttribute"/>. /// </summary> public class StringTrimModelBinder : IModelBinder { /// <summary> /// Binds the model, applying trimming when required. /// </summary> public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { // Get binding value (return null when not present) var propertyName = bindingContext.ModelName; var originalValueResult = bindingContext.ValueProvider.GetValue(propertyName); if (originalValueResult == null) return null; var boundValue = originalValueResult.AttemptedValue; // Trim when required if (!String.IsNullOrEmpty(boundValue)) { // Check for trim attribute if (bindingContext.ModelMetadata.ContainerType != null) { var property = bindingContext.ModelMetadata.ContainerType.GetProperties() .FirstOrDefault(propertyInfo => propertyInfo.Name == bindingContext.ModelMetadata.PropertyName); if (property != null && property.GetCustomAttributes(true) .OfType<StringTrimAttribute>().Any()) { // Trim when attribute set boundValue = boundValue.Trim(); } } } // Register updated "attempted" value with the model state bindingContext.ModelState.SetModelValue(propertyName, new ValueProviderResult( originalValueResult.RawValue, boundValue, originalValueResult.Culture)); // Return bound value return boundValue; } }
如果你没有在活页夹中设置属性值,即使你不想改变任何东西,你也可以从ModelState中完全禁止该属性! 这是因为你注册为绑定所有stringtypes,所以它出现(在我的testing),默认的活页夹不会为你做。
任何人在ASP.NET Core 1.0中search如何执行此操作的额外信息。 逻辑已经改变了很多。
我写了一篇关于如何做的博客文章 ,它解释了一些更详细的内容
所以ASP.NET Core 1.0解决scheme:
模型联编程序进行实际的修剪
public class TrimmingModelBinder : ComplexTypeModelBinder { public TrimmingModelBinder(IDictionary propertyBinders) : base(propertyBinders) { } protected override void SetProperty(ModelBindingContext bindingContext, string modelName, ModelMetadata propertyMetadata, ModelBindingResult result) { if(result.Model is string) { string resultStr = (result.Model as string).Trim(); result = ModelBindingResult.Success(resultStr); } base.SetProperty(bindingContext, modelName, propertyMetadata, result); } }
您还需要最新版本的Model Binder Provider,这告诉我们应该为这个模型使用这个活页夹
public class TrimmingModelBinderProvider : IModelBinderProvider { public IModelBinder GetBinder(ModelBinderProviderContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType) { var propertyBinders = new Dictionary(); foreach (var property in context.Metadata.Properties) { propertyBinders.Add(property, context.CreateBinder(property)); } return new TrimmingModelBinder(propertyBinders); } return null; } }
然后它必须在Startup.cs中注册
services.AddMvc().AddMvcOptions(options => { options.ModelBinderProviders.Insert(0, new TrimmingModelBinderProvider()); });
在阅读上面的优秀答案和评论,并越来越困惑时,我突然想到,嘿,我不知道是否有一个jQuery解决scheme。 所以对于像我这样的其他人来说,findModelBinders有些困惑,我提供了下面的jQuery代码片段,在提交表单之前修剪input字段。
$('form').submit(function () { $(this).find('input:text').each(function () { $(this).val($.trim($(this).val())); }) });
我不同意这个解决scheme。 您应该重写GetPropertyValue,因为SetProperty的数据也可以由ModelState填充。 要从input元素中捕获原始数据,请写下:
public class CustomModelBinder : System.Web.Mvc.DefaultModelBinder { protected override object GetPropertyValue(System.Web.Mvc.ControllerContext controllerContext, System.Web.Mvc.ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, System.Web.Mvc.IModelBinder propertyBinder) { object value = base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder); string retval = value as string; return string.IsNullOrWhiteSpace(retval) ? value : retval.Trim(); } }
如果你真的只对string值感兴趣,可以通过propertyDescriptor PropertyType进行过滤,但不要紧,因为所有东西都是string。
对于ASP.NET Core ,将ComplexTypeModelBinderProvider
replace为修剪string的提供者。
在您的启动代码ConfigureServices
方法中,添加以下内容:
services.AddMvc() .AddMvcOptions(s => { s.ModelBinderProviders[s.ModelBinderProviders.TakeWhile(p => !(p is ComplexTypeModelBinderProvider)).Count()] = new TrimmingModelBinderProvider(); })
定义TrimmingModelBinderProvider
像这样:
/// <summary> /// Used in place of <see cref="ComplexTypeModelBinderProvider"/> to trim beginning and ending whitespace from user input. /// </summary> class TrimmingModelBinderProvider : IModelBinderProvider { class TrimmingModelBinder : ComplexTypeModelBinder { public TrimmingModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders) : base(propertyBinders) { } protected override void SetProperty(ModelBindingContext bindingContext, string modelName, ModelMetadata propertyMetadata, ModelBindingResult result) { var value = result.Model as string; if (value != null) result = ModelBindingResult.Success(value.Trim()); base.SetProperty(bindingContext, modelName, propertyMetadata, result); } } public IModelBinder GetBinder(ModelBinderProviderContext context) { if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType) { var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>(); for (var i = 0; i < context.Metadata.Properties.Count; i++) { var property = context.Metadata.Properties[i]; propertyBinders.Add(property, context.CreateBinder(property)); } return new TrimmingModelBinder(propertyBinders); } return null; } }
这个丑陋的部分是从ComplexTypeModelBinderProvider
复制和粘贴GetBinder
逻辑,但似乎没有任何钩子让你避免这种情况。
但是以下是MVC 5.2.3所需的调整摘要,如果您要处理skipValidation
值提供程序的skipValidation
要求。
public class TrimStringModelBinder : IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { // First check if request validation is required var shouldPerformRequestValidation = controllerContext.Controller.ValidateRequest && bindingContext.ModelMetadata.RequestValidationEnabled; // determine if the value provider is IUnvalidatedValueProvider, if it is, pass in the // flag to perform request validation (eg [AllowHtml] is set on the property) var unvalidatedProvider = bindingContext.ValueProvider as IUnvalidatedValueProvider; var valueProviderResult = unvalidatedProvider?.GetValue(bindingContext.ModelName, !shouldPerformRequestValidation) ?? bindingContext.ValueProvider.GetValue(bindingContext.ModelName); return valueProviderResult?.AttemptedValue?.Trim(); } }
Global.asax中
protected void Application_Start() { ... ModelBinders.Binders.Add(typeof(string), new TrimStringModelBinder()); ... }