简体   繁体   English

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

[英]Best way to trim strings after data entry. Should I create a custom model binder?

I'm using ASP.NET MVC and I'd like all user entered string fields to be trimmed before they're inserted into the database.我正在使用 ASP.NET MVC,我希望在将所有用户输入的字符串字段插入数据库之前对其进行修剪。 And since I have many data entry forms, I'm looking for an elegant way to trim all strings instead of explicitly trimming every user supplied string value.而且由于我有许多数据输入表单,我正在寻找一种优雅的方式来修剪所有字符串,而不是显式地修剪每个用户提供的字符串值。 I'm interested to know how and when people are trimming strings.我很想知道人们如何以及何时修剪字符串。

I thought about perhaps creating a custom model binder and trimming any string values there...that way, all my trimming logic is contained in one place.我考虑过可能创建一个自定义模型绑定器并在那里修剪任何字符串值......这样,我所有的修剪逻辑都包含在一个地方。 Is this a good approach?这是一个好方法吗? Are there any code samples that do this?是否有任何代码示例可以做到这一点?

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

How about this code?这段代码怎么样?

ModelBinders.Binders.DefaultBinder = new TrimModelBinder();

Set global.asax Application_Start event.设置 global.asax Application_Start 事件。

This is @takepara same resolution but as an IModelBinder instead of DefaultModelBinder so that adding the modelbinder in global.asax is through这是 @takepara 相同的分辨率,但作为 IModelBinder 而不是 DefaultModelBinder 以便在 global.asax 中添加模型绑定器是通过

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

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

based on @haacked post: http://haacked.com/archive/2011/03/19/fixing-binding-to-decimals.aspx基于@haacked 帖子: http ://haacked.com/archive/2011/03/19/fixing-binding-to-decimals.aspx

One improvement to @takepara answer. @takepara 答案的一项改进。

Somewere in project:有人在项目中:

public class NoTrimAttribute : Attribute { }

In TrimModelBinder class change在 TrimModelBinder 类更改

if (propertyDescriptor.PropertyType == typeof(string))

to

if (propertyDescriptor.PropertyType == typeof(string) && !propertyDescriptor.Attributes.Cast<object>().Any(a => a.GetType() == typeof(NoTrimAttribute)))

and you can mark properties to be excluded from trimming with [NoTrim] attribute.并且您可以使用 [NoTrim] 属性标记要从修剪中排除的属性。

In ASP.Net Core 2 this worked for me.ASP.Net Core 2中,这对我有用。 I'm using the [FromBody] attribute in my controllers and JSON input.我在控制器和 JSON 输入中使用[FromBody]属性。 To override the string handling in the JSON deserialization I registered my own JsonConverter:为了覆盖 JSON 反序列化中的字符串处理,我注册了自己的 JsonConverter:

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

And this is the converter:这是转换器:

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

With improvements in C# 6, you can now write a very compact model binder that will trim all string inputs:随着 C# 6 的改进,您现在可以编写一个非常紧凑的模型绑定器,它将修剪所有字符串输入:

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

You need to include this line somewhere in Application_Start() in your Global.asax.cs file to use the model binder when binding string s:您需要在Global.asax.cs文件的Application_Start()中的某处包含此行,以便在绑定string s 时使用模型绑定器:

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

I find it is better to use a model binder like this, rather than overriding the default model binder, because then it will be used whenever you are binding a string , whether that's directly as a method argument or as a property on a model class.我发现最好使用这样的模型绑定器,而不是覆盖默认模型绑定器,因为这样无论何时绑定string时都会使用它,无论是直接作为方法参数还是作为模型类的属性。 However, if you override the default model binder as other answers here suggest, that will only work when binding properties on models, not when you have a string as an argument to an action method但是,如果您按照此处的其他答案建议覆盖默认模型绑定器,则在模型上绑定属性时才有效,而不是当您将string作为操作方法的参数时

Edit: a commenter asked about dealing with the situation when a field should not be validated.编辑:一位评论者询问如何处理不应验证字段的情况。 My original answer was reduced to deal just with the question the OP had posed, but for those who are interested, you can deal with validation by using the following extended model binder:我最初的答案被简化为仅处理 OP 提出的问题,但对于那些感兴趣的人,您可以使用以下扩展模型绑定器来处理验证:

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

Another variant of @takepara's answer but with a different twist: @takepara 答案的另一个变体,但有不同的转折:

1) I prefer the opt-in "StringTrim" attribute mechanism (rather than the opt-out "NoTrim" example of @Anton). 1)我更喜欢选择加入“StringTrim”属性机制(而不是@Anton 的选择退出“NoTrim”示例)。

2) An additional call to SetModelValue is required to ensure the ModelState is populated correctly and the default validation/accept/reject pattern can be used as normal, ie TryUpdateModel(model) to apply and ModelState.Clear() to accept all changes. 2) 需要额外调用 SetModelValue 以确保正确填充 ModelState,并且可以正常使用默认验证/接受/拒绝模式,即 TryUpdateModel(model) 应用和 ModelState.Clear() 接受所有更改。

Put this in your entity/shared library:把它放在你的实体/共享库中:

/// <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
{
}

Then this in your MVC application/library:然后在您的 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;
    }
}

If you don't set the property value in the binder, even when you don't want to change anything, you will block that property from ModelState altogether!如果您没有在活页夹中设置属性值,即使您不想更改任何内容,您也会从 ModelState 中完全阻止该属性! This is because you are registered as binding all string types, so it appears (in my testing) that the default binder will not do it for you then.这是因为您已注册为绑定所有字符串类型,因此(在我的测试中)默认绑定器不会为您执行此操作。

Extra info for anyone searching how to do this in ASP.NET Core 1.0.对于在 ASP.NET Core 1.0 中搜索如何执行此操作的任何人的额外信息。 Logic has changed quite a lot.逻辑发生了很大变化。

I wrote a blog post about how to do it , it explains things in bit more detailed 我写了一篇关于如何做的博客文章,它更详细地解释了一些事情

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

Model binder to do the actual trimming模型粘合剂进行实际修剪

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

Also you need Model Binder Provider in the latest version, this tells that should this binder be used for this model您还需要最新版本的模型绑定器提供程序,这告诉该绑定器是否应该用于此模型

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

Then it has to be registered in Startup.cs然后它必须在 Startup.cs 中注册

 services.AddMvc().AddMvcOptions(options => {  
       options.ModelBinderProviders.Insert(0, new TrimmingModelBinderProvider());
 });

In case of MVC Core在 MVC 核心的情况下

Binder:粘合剂:

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

Provider:提供者:

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

Registration function:注册功能:

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

Register:登记:

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

I created value providers to trim the query string parameter values and the form values.我创建了值提供程序来修剪查询字符串参数值和表单值。 This was tested with ASP.NET Core 3 and works perfectly.这已使用 ASP.NET Core 3 进行了测试,并且运行良好。

public class TrimmedFormValueProvider
    : FormValueProvider
{
    public TrimmedFormValueProvider(IFormCollection values)
        : base(BindingSource.Form, values, CultureInfo.InvariantCulture)
    { }

    public override ValueProviderResult GetValue(string key)
    {
        ValueProviderResult baseResult = base.GetValue(key);
        string[] trimmedValues = baseResult.Values.Select(v => v?.Trim()).ToArray();
        return new ValueProviderResult(new StringValues(trimmedValues));
    }
}

public class TrimmedQueryStringValueProvider
    : QueryStringValueProvider
{
    public TrimmedQueryStringValueProvider(IQueryCollection values)
        : base(BindingSource.Query, values, CultureInfo.InvariantCulture)
    { }

    public override ValueProviderResult GetValue(string key)
    {
        ValueProviderResult baseResult = base.GetValue(key);
        string[] trimmedValues = baseResult.Values.Select(v => v?.Trim()).ToArray();
        return new ValueProviderResult(new StringValues(trimmedValues));
    }
}

public class TrimmedFormValueProviderFactory
    : IValueProviderFactory
{
    public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
    {
        if (context.ActionContext.HttpContext.Request.HasFormContentType)
            context.ValueProviders.Add(new TrimmedFormValueProvider(context.ActionContext.HttpContext.Request.Form));
        return Task.CompletedTask;
    }
}

public class TrimmedQueryStringValueProviderFactory
    : IValueProviderFactory
{
    public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
    {
        context.ValueProviders.Add(new TrimmedQueryStringValueProvider(context.ActionContext.HttpContext.Request.Query));
        return Task.CompletedTask;
    }
}

Then register the value provider factories in the ConfigureServices() function in Startup.cs然后在 Startup.cs 的ConfigureServices()函数中注册值提供者工厂

services.AddControllersWithViews(options =>
{
    int formValueProviderFactoryIndex = options.ValueProviderFactories.IndexOf(options.ValueProviderFactories.OfType<FormValueProviderFactory>().Single());
    options.ValueProviderFactories[formValueProviderFactoryIndex] = new TrimmedFormValueProviderFactory();

    int queryStringValueProviderFactoryIndex = options.ValueProviderFactories.IndexOf(options.ValueProviderFactories.OfType<QueryStringValueProviderFactory>().Single());
    options.ValueProviderFactories[queryStringValueProviderFactoryIndex] = new TrimmedQueryStringValueProviderFactory();
});

While reading through the excellent answers and comments above, and becoming increasingly confused, I suddenly thought, hey, I wonder if there's a jQuery solution.看了上面优秀的答案和评论,越看越糊涂,突然想,哎,不知道有没有jQuery的解决方案。 So for others who, like me, find ModelBinders a bit bewildering, I offer the following jQuery snippet that trims the input fields before the form gets submitted.因此,对于像我一样觉得 ModelBinders 有点令人困惑的其他人,我提供了以下 jQuery 片段,它在表单提交之前修剪输入字段。

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

Update: This answer is out of date for recent versions of ASP.NET Core.更新:对于最新版本的 ASP.NET Core,此答案已过时。 Use Bassem's answer instead.请改用Bassem 的答案


For ASP.NET Core , replace the ComplexTypeModelBinderProvider with a provider that trims strings.对于ASP.NET Core ,将ComplexTypeModelBinderProvider替换为修剪字符串的提供程序。

In your startup code ConfigureServices method, add this:在您的启动代码ConfigureServices方法中,添加以下内容:

services.AddMvc()
    .AddMvcOptions(s => {
        s.ModelBinderProviders[s.ModelBinderProviders.TakeWhile(p => !(p is ComplexTypeModelBinderProvider)).Count()] = new TrimmingModelBinderProvider();
    })

Define TrimmingModelBinderProvider like this:像这样定义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;
    }
}

The ugly part of this is the copy and paste of the GetBinder logic from ComplexTypeModelBinderProvider , but there doesn't seem to be any hook to let you avoid this.丑陋的部分是从ComplexTypeModelBinderProvider复制和粘贴GetBinder逻辑,但似乎没有任何挂钩可以让您避免这种情况。

Late to the party, but the following is a summary of adjustments required for MVC 5.2.3 if you are to handle the skipValidation requirement of the build-in value providers.迟到了,但如果您要处理内置值提供程序的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全球.asax

    protected void Application_Start()
    {
        ...
        ModelBinders.Binders.Add(typeof(string), new TrimStringModelBinder());
        ...
    }

I disagree with the solution.我不同意这个解决方案。 You should override GetPropertyValue because the data for SetProperty could also be filled by the ModelState.您应该覆盖 GetPropertyValue,因为 SetProperty 的数据也可以由 ModelState 填充。 To catch the raw data from the input elements write this:要从输入元素中捕获原始数据,请编写以下代码:

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

}

Filter by propertyDescriptor PropertyType if you are really only interested in string values but it should not matter because everything what comes in is basically a string.如果您真的只对字符串值感兴趣,请按 propertyDescriptor PropertyType 过滤,但这无关紧要,因为进来的所有内容基本上都是字符串。

There have been a lot of posts suggesting an attribute approach.有很多帖子建议使用属性方法。 Here is a package that already has a trim attribute and many others: Dado.ComponentModel.Mutations or NuGet这是一个已经具有 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();

After the call to Mutate(), user.UserName will be mutated to m@x_speed.01!调用 Mutate() 后,user.UserName 将被变异为m@x_speed.01! . .

This example will trim whitespace and case the string to lowercase.此示例将修剪空格并将字符串大小写为小写。 It doesn't introduce validation, but the System.ComponentModel.Annotations can be used alongside Dado.ComponentModel.Mutations .它没有引入验证,但System.ComponentModel.Annotations可以与Dado.ComponentModel.Mutations一起使用。

I posted this in another thread.我在另一个线程中发布了这个。 In asp.net core 2, I went in a different direction.在 asp.net core 2 中,我走向了不同的方向。 I used an action filter instead.我使用了一个动作过滤器。 In this case the developer can either set it globally or use as an attribute for the actions he/she wants to apply the string trimming.在这种情况下,开发人员可以对其进行全局设置,也可以将其用作他/她想要应用字符串修剪的操作的属性。 This code runs after the model binding has taken place, and it can update the values in the model object.此代码在模型绑定发生后运行,它可以更新模型对象中的值。

Here is my code, first create an action filter:这是我的代码,首先创建一个动作过滤器:

public class TrimInputStringsAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        foreach (var arg in context.ActionArguments)
        {
            if (arg.Value is string)
            {
                string val = arg.Value as string;
                if (!string.IsNullOrEmpty(val))
                {
                    context.ActionArguments[arg.Key] = val.Trim();
                }

                continue;
            }

            Type argType = arg.Value.GetType();
            if (!argType.IsClass)
            {
                continue;
            }

            TrimAllStringsInObject(arg.Value, argType);
        }
    }

    private void TrimAllStringsInObject(object arg, Type argType)
    {
        var stringProperties = argType.GetProperties()
                                      .Where(p => p.PropertyType == typeof(string));

        foreach (var stringProperty in stringProperties)
        {
            string currentValue = stringProperty.GetValue(arg, null) as string;
            if (!string.IsNullOrEmpty(currentValue))
            {
                stringProperty.SetValue(arg, currentValue.Trim(), null);
            }
        }
    }
}

To use it, either register as global filter or decorate your actions with the TrimInputStrings attribute.要使用它,要么注册为全局过滤器,要么使用 TrimInputStrings 属性装饰您的操作。

[TrimInputStrings]
public IActionResult Register(RegisterViewModel registerModel)
{
    // Some business logic...
    return Ok();
}

OK, I have this thing and it kinda works:好的,我有这个东西,它有点工作:

class TrimmingModelBinder : IModelBinder
{
  public Task BindModelAsync (ModelBindingContext ctx)
  {
  if
  (
    ctx .ModelName is string name
    && ctx .ValueProvider .GetValue (name) .FirstValue is string v)
  ctx .ModelState .SetModelValue
  (
    name,
    new ValueProviderResult
    ((ctx .Result = ModelBindingResult .Success (v .Trim ())) .Model as string));
  return Task .CompletedTask; }}

class AutoTrimAttribute : ModelBinderAttribute
{
  public AutoTrimAttribute ()
  { this .BinderType = typeof (TrimmingModelBinder); }}

It is a shame that there is no standard feature for this though.遗憾的是,这没有标准功能。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM