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 ,将ComplexTypeModelBinderProviderreplace为修剪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()); ... } 
Interesting Posts