我有一个DTO类,它从body源绑定来创建我的用户:
public class UserDto
{
[Required()]
[MinLength(2)]
[MaxLength(30)]
public string FirstName { get; set; }
[Required()]
[MinLength(2)]
[MaxLength(30)]
public string LastName { get; set; }
[Required]
[SocialSerialNumber]
public string SSN { get; set; }
[Required]
[PhoneNumber]
public string PhoneNumber { get; set; }
[Required]
public bool? Gender { get; set; }
[Required]
[MinLength(6)]
[MaxLength(30)]
[IgnoreTrim] // this is what I need
public string Password { get; set; }
}
在验证之前,我想在所有模型中修剪(从所有模型中删除额外的空格)所有字符串。对于我显式指定为no的字符串,必须忽略修整(可能使用一个名为[IgnoreTrim]
的属性)。
在上面的示例中,需要对属性FirstName
、LastName
、PhoneNumber
进行修整,但不需要Password
。
我知道我可以在控制器操作中手动修剪它们,然后重新验证模型,但是我正在寻找一种优雅的方法,这样我的操作才能保持干净。
发布于 2021-09-08 13:55:10
我找到了一个很好的解决方案,使用定制模型绑定器和自定义JSON转换器。
public class StringTrimmerBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext is null)
throw new ArgumentNullException(nameof(bindingContext));
var modelName = bindingContext.ModelName;
var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult == ValueProviderResult.None)
return Task.CompletedTask;
bindingContext.Result = ModelBindingResult.Success(valueProviderResult.FirstValue.Trim());
return Task.CompletedTask;
}
}
StringTrimmerBinder是我使用Trim()
方法将字符串转换为修整字符串的自定义绑定器。为了使用这个绑定器,我做了一个定制的IModelBinderProvider
,用于解析指定类型的绑定器(在我的例子中是字符串类型)。
public class CustomModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context is null)
throw new ArgumentNullException(nameof(context));
if (context.Metadata.ModelType == typeof(string) && context.BindingInfo.BindingSource!=BindingSource.Body)
return new StringTrimmerBinder();
return null;
}
}
所以我这样注册CustomModelBinderProvider
:
services.AddControllers(opt =>
{
//registers CustomModelBinderProvider in the first place so that it is asked before other model binder providers.
opt.ModelBinderProviders.Insert(0, new CustomModelBinderProvider());
});
到目前为止,一切都是正常的,所有字符串模型都被裁剪了,除了那些绑定源是请求体(FromBody)的模型。因为默认模型绑定使用System.Text.Json
命名空间将请求体转换为模型类型,所以我创建了一个JSON转换器,它自定义将主体转换为模型类型,而不是制作一个新的绑定器。
这是我的自定义转换器:
public class StringTrimmerJsonConverter : JsonConverter<string>
{
public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return reader.GetString().Trim();
}
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
{
writer.WriteStringValue(value);
}
}
下面是使用这个转换器的方法:
services.AddControllers(opt =>
{
opt.ModelBinderProviders.Insert(0, new CustomModelBinderProvider());
})
.AddJsonOptions(opt =>
{
opt.JsonSerializerOptions.Converters.Add(new StringTrimmerJsonConverter());
});
就是这样,现在我所有的字符串,无论是复杂类型还是简单类型,都将被裁剪。
发布于 2021-09-06 12:41:42
更新
我已经包含了两个模型绑定程序,它们与JSON一起工作,并从主体中形成字段数据。
实现IModelBinder
并执行以下操作:
来自Json的绑定:
public class CustomModelBinderJson : IModelBinder
{
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var request = bindingContext.HttpContext.Request;
string body;
using (StreamReader sr = new StreamReader(request.Body, Encoding.UTF8))
{
body = await sr.ReadToEndAsync();
}
var dataModel = JsonSerializer.Deserialize<DataModel>(body);
dataModel.Name = dataModel.Name.Trim();
bindingContext.Result = ModelBindingResult.Success(dataModel);
}
}
或从表单字段绑定:
public class CustomModelBinderForm : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var request = bindingContext.HttpContext.Request;
var form = request.Form["Name"];
var id = request.Form["Id"];
var dataModel = new DataModel();
dataModel.Id = int.Parse(id);
dataModel.Name = form.ToString().Trim();
bindingContext.Result = ModelBindingResult.Success(dataModel);
return Task.CompletedTask;
}
}
然后
[ModelBinder(BinderType = typeof(CustomModelBinderForm))] //Or use the CustomModelBinderJson
public class DataModel
{
public int Id { get; set; }
public string Name { get; set; }
}
你的行动应该是这样的:
[HttpPost]
public IActionResult Index([FromBody] DataModel dataModel)
{
//Do stuff
}
请参阅MSDocs
发布于 2021-09-06 19:56:49
您可以使用getter来修剪将存储在私有字段中的值。这样,您的验证将针对已修剪的值进行。
例如,使用属性FirstName
public class UserDto
{
private string firstName;
[Required()]
[PersianChars]
[MinLength(2)]
[MaxLength(30)]
public string FirstName
{
get
{
return firstName?.Trim();
}
set
{
firstName = value;
}
}
}
另一种解决方案是实现一个自定义属性,该属性指示在验证之前需要修剪哪些属性。此时,您可以将此过程自动化。
要使用自定义属性自动调整属性的值,请执行以下操作:
您需要创建一个自定义属性
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class TrimmedValueAttribute : Attribute
{
}
然后,将属性添加到需要的属性,例如FirstName
public class UserDto
{
[Required()]
[PersianChars]
[MinLength(2)]
[MaxLength(30)]
[TrimmedValue]
public string FirstName { get; set }
}
之后,您需要创建一个操作筛选器,它自动调整具有TrimmedValueAttribute
的属性
public class TrimPropertiesActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
Use reflection to find all the properties which has the TrimValueAttribute
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
}
然后,在ConfigureServices
方法上添加过滤器
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews(options =>
{
options.Filters.Add(typeof(TrimPropertiesActionFilter));
});
}
可以通过设置Add
方法的第二个参数来更改操作筛选器的顺序。
options.Filters.Add(typeof(TrimPropertiesActionFilter, order: 0));
https://stackoverflow.com/questions/69074572
复制相似问题