数据输入后修剪字符串的最佳方法 . 我应该创建自定义模型 Binders 吗?

我正在使用ASP.NET MVC,我希望所有用户输入的字符串字段在插入数据库之前进行修剪 . 由于我有很多数据输入表单,我正在寻找一种优雅的方法来修剪所有字符串,而不是明确地修剪每个用户提供的字符串值 . 我很想知道人们如何以及何时修剪弦乐 .

我想也许创建一个自定义模型 Binders 并修剪那里的任何字符串值......这样,我的所有修剪逻辑都包含在一个地方 . 这是一个好方法吗?是否有任何代码示例执行此操作?

回答(13)

2 years ago

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事件 .

2 years ago

这是@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

2 years ago

@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]属性标记要从修剪中排除的属性 .

2 years ago

随着C#6的改进,你现在可以编写一个非常紧凑的模型 Binders 来修剪所有字符串输入:

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 时使用模型 Binders :

ModelBinders.Binders.Add(typeof(string), new TrimStringModelBinder());

我发现最好使用这样的模型 Binders ,而不是覆盖默认的模型 Binders ,因为每当绑定_756070时它都会被使用,无论是直接作为方法参数还是作为模型类的属性 . 但是,如果您按照此处提供的其他答案覆盖默认模型 Binders ,那么仅在模型上绑定属性时才有效,而不是在您将 string 作为操作方法的参数时

编辑:评论者询问在不应验证字段时处理情况 . 我的原始答案被简化为只处理OP提出的问题,但对于那些感兴趣的人,您可以使用以下扩展模型 Binders 来处理验证:

public class TrimStringModelBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var shouldPerformRequestValidation = controllerContext.Controller.ValidateRequest && bindingContext.ModelMetadata.RequestValidationEnabled;
        var unvalidatedValueProvider = bindingContext.ValueProvider as IUnvalidatedValueProvider;

        var value = unvalidatedValueProvider == null ?
          bindingContext.ValueProvider.GetValue(bindingContext.ModelName) :
          unvalidatedValueProvider.GetValue(bindingContext.ModelName, !shouldPerformRequestValidation);

        var attemptedValue = value?.AttemptedValue;

        return string.IsNullOrWhiteSpace(attemptedValue) ? attemptedValue : attemptedValue.Trim();
    }
}

2 years ago

@ takepara答案的另一个变种,但有一个不同的转折:

1)我更喜欢选择加入“StringTrim”属性机制(而不是@Anton的选择退出“NoTrim”示例) .

2)需要额外调用SetModelValue以确保正确填充ModelState并且可以正常使用默认验证/接受/拒绝模式,即应用TryUpdateModel(模型)和接受所有更改的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;
    }
}

如果您没有在 Binders 中设置属性值,即使您不想更改任何内容,也会完全阻止ModelState中的该属性!这是因为您注册为绑定所有字符串类型,因此它(在我的测试中)显示默认 Binders 不会为您执行此操作 .

2 years ago

ASP.Net Core 2 这对我有用 . 我在我的控制器和JSON输入中使用 [FromBody] 属性 . 要覆盖JSON反序列化中的字符串处理,我注册了自己的JsonConverter:

services.AddMvcCore()
    .AddJsonOptions(options =>
        {
            options.SerializerSettings.Converters.Insert(0, new TrimmingStringConverter());
        })

这是转换器:

public class TrimmingStringConverter : JsonConverter
{
    public override bool CanRead => true;
    public override bool CanWrite => false;

    public override bool CanConvert(Type objectType) => objectType == typeof(string);

    public override object ReadJson(JsonReader reader, Type objectType,
        object existingValue, JsonSerializer serializer)
    {
        if (reader.Value is string value)
        {
            return value.Trim();
        }

        return reader.Value;
    }

    public override void WriteJson(JsonWriter writer, object value,
        JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

2 years ago

在ASP.NET Core 1.0中搜索如何执行此操作的任何人的额外信息 . 逻辑发生了很大的变化 .

I wrote a blog post about how to do it,它更详细地解释了一些事情

所以ASP.NET Core 1.0解决方案:

模型 Binders 进行实际修剪

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,这表明此 Binders 应该用于此模型

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());
 });

2 years ago

阅读上面的优秀答案和评论,并变得越来越困惑,我突然想,嘿,我想知道是否有一个jQuery解决方案 . 所以对于像我这样的人来说,发现ModelBinder有点令人困惑,我提供了以下jQuery片段,它在表单提交之前修剪输入字段 .

$('form').submit(function () {
        $(this).find('input:text').each(function () {
            $(this).val($.trim($(this).val()));
        })
    });

2 years ago

In case of MVC Core

Binders :

using Microsoft.AspNetCore.Mvc.ModelBinding;
using System;
using System.Threading.Tasks;
public class TrimmingModelBinder
    : IModelBinder
{
    private readonly IModelBinder FallbackBinder;

    public TrimmingModelBinder(IModelBinder fallbackBinder)
    {
        FallbackBinder = fallbackBinder ?? throw new ArgumentNullException(nameof(fallbackBinder));
    }

    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
        {
            throw new ArgumentNullException(nameof(bindingContext));
        }

        var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

        if (valueProviderResult != null &&
            valueProviderResult.FirstValue is string str &&
            !string.IsNullOrEmpty(str))
        {
            bindingContext.Result = ModelBindingResult.Success(str.Trim());
            return Task.CompletedTask;
        }

        return FallbackBinder.BindModelAsync(bindingContext);
    }
}

提供者:

using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using System;

public class TrimmingModelBinderProvider
    : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        if (!context.Metadata.IsComplexType && context.Metadata.ModelType == typeof(string))
        {
            return new TrimmingModelBinder(new SimpleTypeModelBinder(context.Metadata.ModelType));
        }

        return null;
    }
}

注册功能:

public static void AddStringTrimmingProvider(this MvcOptions option)
    {
        var binderToFind = option.ModelBinderProviders
            .FirstOrDefault(x => x.GetType() == typeof(SimpleTypeModelBinderProvider));

        if (binderToFind == null)
        {
            return;
        }

        var index = option.ModelBinderProviders.IndexOf(binderToFind);
        option.ModelBinderProviders.Insert(index, new TrimmingModelBinderProvider());
    }

寄存器:

service.AddMvc(option => option.AddStringTrimmingProvider())

2 years ago

对于 ASP.NET Core ,将 ComplexTypeModelBinderProvider 替换为修剪字符串的提供程序 .

在您的启动代码 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;
    }
}

丑陋的部分是 ComplexTypeModelBinderProviderGetBinder 逻辑的复制和粘贴,但似乎没有任何钩子可以让你避免这种情况 .

2 years ago

我不同意这个解决方案 . 您应该重写GetPropertyValue,因为SetProperty的数据也可以由ModelState填充 . 要从输入元素捕获原始数据,请写下:

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();
    }

}

如果你真的只对字符串值感兴趣,那么按属性描述符PropertyType进行过滤,但这并不重要,因为所有内容基本上都是一个字符串 .

2 years ago

晚到晚会,但以下是一个如果要处理内置值提供程序的 skipValidation 要求,则需要对MVC 5.2.3进行调整的摘要 .

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 (e.g. [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());
        ...
    }

2 years ago

有很多帖子暗示了属性方法 . 这是一个已经具有trim属性的包以及许多其他包:Dado.ComponentModel.MutationsNuGet

public partial class ApplicationUser
{
    [Trim, ToLower]
    public virtual string UserName { get; set; }
}

// Then to preform mutation
var user = new ApplicationUser() {
    UserName = "   M@X_speed.01! "
}

new MutationContext<ApplicationUser>(user).Mutate();

在调用Mutate()之后,user.UserName将变为 m@x_speed.01! .

此示例将修剪空格并将字符串设置为小写 . 它不会引入验证,但 System.ComponentModel.Annotations 可以与 Dado.ComponentModel.Mutations 一起使用 .