Asp.net 数据输入后修剪字符串的最佳方法。我应该创建自定义模型活页夹吗?
我使用的是ASP.NETMVC,我希望在将所有用户输入的字符串字段插入数据库之前对其进行修剪。由于我有很多数据输入表单,我正在寻找一种优雅的方法来修剪所有字符串,而不是显式地修剪每个用户提供的字符串值。我很想知道人们是如何以及何时修剪琴弦的 我考虑过创建一个自定义的模型绑定器,并在那里修剪任何字符串值……这样,我所有的修剪逻辑都包含在一个地方。这是一个好方法吗?有这样做的代码示例吗Asp.net 数据输入后修剪字符串的最佳方法。我应该创建自定义模型活页夹吗?,asp.net,asp.net-mvc,model-binders,Asp.net,Asp.net Mvc,Model Binders,我使用的是ASP.NETMVC,我希望在将所有用户输入的字符串字段插入数据库之前对其进行修剪。由于我有很多数据输入表单,我正在寻找一种优雅的方法来修剪所有字符串,而不是显式地修剪每个用户提供的字符串值。我很想知道人们是如何以及何时修剪琴弦的 我考虑过创建一个自定义的模型绑定器,并在那里修剪任何字符串值……这样,我所有的修剪逻辑都包含在一个地方。这是一个好方法吗?有这样做的代码示例吗 public class TrimModelBinder : DefaultModelBinder {
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应用程序\u启动事件。这是@takepara相同的分辨率,但作为IModelBinder而不是DefaultModelBinder,以便在global.asax中添加modelbinder
ModelBinders.Binders.Add(typeof(string),new TrimModelBinder());
班级:
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 post:
对@takepara答案的一个改进 有些人参与了这个项目:
public class NoTrimAttribute : Attribute { }
在TrimModelBinder类中更改
if (propertyDescriptor.PropertyType == typeof(string))
到
if(propertyDescriptor.propertydype==typeof(string)和&!propertyDescriptor.Attributes.Cast().Any(a=>a.GetType()==typeof(NoTrimAttribute)))
您可以使用[NoTrim]属性标记要从修剪中排除的属性。我不同意该解决方案。
[TrimInputStrings]
public IActionResult Register(RegisterViewModel registerModel)
{
// Some business logic...
return Ok();
}
您应该重写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();
}
}
如果您真的只对字符串值感兴趣,则按propertyDescriptor PropertyType进行筛选,但这并不重要,因为输入的所有内容基本上都是字符串。另一个@takepara答案的变体,但有不同的含义: 1) 我更喜欢opt-in“StringTrim”属性机制(而不是@Anton的opt-out“NoTrim”示例) 2) 需要额外调用SetModelValue,以确保正确填充ModelState,并且可以正常使用默认的验证/接受/拒绝模式,即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
{
}
//
///表示在绑定期间应修剪的数据字段,删除所有空格。
///
///
///
///与当前一样,修剪支持在模型绑定器中实现
///数据注释没有提供强制值的机制。
///
///
///此属性并不意味着空字符串应转换为null。
///如果需要,您还必须使用
///选项来控制对空字符串的处理。
///
///
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field,AllowMultiple=false)]
公共类StringTrimAttribute:属性
{
}
然后在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;
}
}
//
///MVC模型绑定器,用于修剪用。
///
公共类StringTrimModelBinder:IModelBinder
{
///
///绑定模型,在需要时应用修剪。
///
公共对象绑定模型(ControllerContext ControllerContext,ModelBindingContext bindingContext)
{
//获取绑定值(不存在时返回null)
var propertyName=bindingContext.ModelName;
var originalValueResult=bindingContext.ValueProvider.GetValue(propertyName);
if(originalValueResult==null)
返回null;
var boundValue=originalValueResult.AttemptedValue;
//必要时进行修剪
如果(!String.IsNullOrEmpty(boundValue))
{
//检查修剪属性
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().Any())
{
//设置属性时修剪
boundValue=boundValue.Trim();
}
}
}
//将更新的“尝试”值注册到模型状态
bindingContext.ModelState.SetModelValue(propertyName,新值ProviderResult(
originalValueResult.RawValue、boundValue、originalValueResult.Culture));
//返回界值
返回边界值;
}
}
如果您不在活页夹中设置属性值,即使您不想更改任何内容,也会从ModelState中完全阻止该属性!这是因为您已注册为绑定所有字符串类型,因此(在我的测试中)默认绑定器似乎不适合您。在阅读上述优秀答案和注释时,我变得越来越困惑,突然想到,嘿,我想知道是否有jQuery解决方案。因此,对于像我这样觉得ModelBinder有点困惑的其他人,我提供了以下jQuery片段,在表单提交之前修剪输入字段
$('form').submit(function () {
$(this).find('input:text').each(function () {
$(this).val($.trim($(this).val()));
})
});
在ASP.NET Core 1.0中搜索如何执行此操作的任何人的额外信息。逻辑发生了很大变化 ,它解释得更详细一些 因此ASP.NET核心1.0解决方案: 模型绑定器进行实际修剪
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);
}
}
您还需要最新版本的模型绑定器提供程序,这说明此绑定器是否应用于此模型
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());
});
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();
});
通过对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();
}
}
您需要在Global.asax.cs
文件的Application\u Start()
中的某个位置包含这一行,以便在绑定字符串时使用模型绑定器:
ModelBinders.Binders.Add(typeof(string), new TrimStringModelBinder());
我发现最好使用这样的模型绑定器,而不是覆盖默认的模型绑定器,因为这样无论何时绑定都会使用它
services.AddMvc()
.AddMvcOptions(s => {
s.ModelBinderProviders[s.ModelBinderProviders.TakeWhile(p => !(p is ComplexTypeModelBinderProvider)).Count()] = new 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;
}
}
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();
}
}
protected void Application_Start()
{
...
ModelBinders.Binders.Add(typeof(string), new TrimStringModelBinder());
...
}
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())
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();
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();
}
}
[TrimInputStrings]
public IActionResult Register(RegisterViewModel registerModel)
{
// Some business logic...
return Ok();
}
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;
}
}
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();
});