hea*_*vyd 16 c# asp.net asp.net-mvc asp.net-core-mvc asp.net-core
我正在使用ASP.NET Core 1.1 MVC来构建JSON API.给出以下模型和动作方法:
public class TestModel
{
public int Id { get; set; }
[Range(100, 999)]
public int RootId { get; set; }
[Required, MaxLength(200)]
public string Name { get; set; }
public string Description { get; set; }
}
[HttpPost("/test/{rootId}/echo/{id}")]
public IActionResult TestEcho([FromBody] TestModel data)
{
return Json(new
{
data.Id,
data.RootId,
data.Name,
data.Description,
Errors = ModelState.IsValid ? null : ModelState.SelectMany(x => x.Value.Errors)
});
}
Run Code Online (Sandbox Code Playgroud)
在[FromBody]我的动作方法的参数导致必须从发布到端点JSON有效载荷的约束的模型,然而,它也可以防止Id和RootId属性经由路由参数的约束.
我可以将其分解为单独的模型,一个绑定路由,一个绑定到正文,或者我也可以强制任何客户端发送id&rootId作为有效负载的一部分,但这两个解决方案似乎比我更复杂.我喜欢并且不允许我将验证逻辑保存在一个地方.有没有办法让这种情况适用于模型可以正确绑定的地方,我可以将我的模型和验证逻辑保持在一起?
Fra*_*rdo 16
您可以删除[FromBody]输入上的装饰器,让MVC绑定映射属性:
[HttpPost("/test/{rootId}/echo/{id}")]
public IActionResult TestEcho(TestModel data)
{
return Json(new
{
data.Id,
data.RootId,
data.Name,
data.Description,
Errors = ModelState.IsValid ? null : ModelState.SelectMany(x => x.Value.Errors)
});
}
Run Code Online (Sandbox Code Playgroud)
更多信息: ASP.NET Core MVC中的模型绑定
UPDATE
测试
更新2
@heavyd,你是对的,JSON数据需要[FromBody]属性来绑定你的模型.所以我上面说的将用于表单数据,但不用于JSON数据.
或者,您可以创建一个自定义模型绑定器,用于绑定url中的Id和RootId属性,同时绑定请求主体中的其余属性.
public class TestModelBinder : IModelBinder
{
private BodyModelBinder defaultBinder;
public TestModelBinder(IList<IInputFormatter> formatters, IHttpRequestStreamReaderFactory readerFactory) // : base(formatters, readerFactory)
{
defaultBinder = new BodyModelBinder(formatters, readerFactory);
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
// callinng the default body binder
await defaultBinder.BindModelAsync(bindingContext);
if (bindingContext.Result.IsModelSet)
{
var data = bindingContext.Result.Model as TestModel;
if (data != null)
{
var value = bindingContext.ValueProvider.GetValue("Id").FirstValue;
int intValue = 0;
if (int.TryParse(value, out intValue))
{
// Override the Id property
data.Id = intValue;
}
value = bindingContext.ValueProvider.GetValue("RootId").FirstValue;
if (int.TryParse(value, out intValue))
{
// Override the RootId property
data.RootId = intValue;
}
bindingContext.Result = ModelBindingResult.Success(data);
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
创建活页夹提供者:
public class TestModelBinderProvider : IModelBinderProvider
{
private readonly IList<IInputFormatter> formatters;
private readonly IHttpRequestStreamReaderFactory readerFactory;
public TestModelBinderProvider(IList<IInputFormatter> formatters, IHttpRequestStreamReaderFactory readerFactory)
{
this.formatters = formatters;
this.readerFactory = readerFactory;
}
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType == typeof(TestModel))
return new TestModelBinder(formatters, readerFactory);
return null;
}
}
Run Code Online (Sandbox Code Playgroud)
并告诉MVC使用它:
services.AddMvc()
.AddMvcOptions(options =>
{
IHttpRequestStreamReaderFactory readerFactory = services.BuildServiceProvider().GetRequiredService<IHttpRequestStreamReaderFactory>();
options.ModelBinderProviders.Insert(0, new TestModelBinderProvider(options.InputFormatters, readerFactory));
});
Run Code Online (Sandbox Code Playgroud)
然后你的控制器有:
[HttpPost("/test/{rootId}/echo/{id}")]
public IActionResult TestEcho(TestModel data)
{...}
Run Code Online (Sandbox Code Playgroud)
测试
您可以添加一个Id和RootId您的JSON,但它们将被忽略,因为我们在模型绑定器中覆盖它们.
更新3
以上允许您使用数据模型注释进行验证Id和RootId.但我认为可能会混淆其他开发人员,他们会查看您的API代码.我建议只是简化API签名以接受不同的模型使用[FromBody]并分离来自uri的其他两个属性.
[HttpPost("/test/{rootId}/echo/{id}")]
public IActionResult TestEcho(int id, int rootId, [FromBody]TestModelNameAndAddress testModelNameAndAddress)
Run Code Online (Sandbox Code Playgroud)
你可以为你的所有输入找到一个验证器,例如:
// This would return a list of tuples of property and error message.
var errors = validator.Validate(id, rootId, testModelNameAndAddress);
if (errors.Count() > 0)
{
foreach (var error in errors)
{
ModelState.AddModelError(error.Property, error.Message);
}
}
Run Code Online (Sandbox Code Playgroud)
小智 15
在研究之后,我想出了一个创建新模型绑定器+绑定源+属性的解决方案,它结合了BodyModelBinder和ComplexTypeModelBinder的功能.它首先使用BodyModelBinder从body读取,然后ComplexModelBinder填充其他字段.代码在这里:
public class BodyAndRouteBindingSource : BindingSource
{
public static readonly BindingSource BodyAndRoute = new BodyAndRouteBindingSource(
"BodyAndRoute",
"BodyAndRoute",
true,
true
);
public BodyAndRouteBindingSource(string id, string displayName, bool isGreedy, bool isFromRequest) : base(id, displayName, isGreedy, isFromRequest)
{
}
public override bool CanAcceptDataFrom(BindingSource bindingSource)
{
return bindingSource == Body || bindingSource == this;
}
}
Run Code Online (Sandbox Code Playgroud)
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class FromBodyAndRouteAttribute : Attribute, IBindingSourceMetadata
{
public BindingSource BindingSource => BodyAndRouteBindingSource.BodyAndRoute;
}
Run Code Online (Sandbox Code Playgroud)
public class BodyAndRouteModelBinder : IModelBinder
{
private readonly IModelBinder _bodyBinder;
private readonly IModelBinder _complexBinder;
public BodyAndRouteModelBinder(IModelBinder bodyBinder, IModelBinder complexBinder)
{
_bodyBinder = bodyBinder;
_complexBinder = complexBinder;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
await _bodyBinder.BindModelAsync(bindingContext);
if (bindingContext.Result.IsModelSet)
{
bindingContext.Model = bindingContext.Result.Model;
}
await _complexBinder.BindModelAsync(bindingContext);
}
}
Run Code Online (Sandbox Code Playgroud)
public class BodyAndRouteModelBinderProvider : IModelBinderProvider
{
private BodyModelBinderProvider _bodyModelBinderProvider;
private ComplexTypeModelBinderProvider _complexTypeModelBinderProvider;
public BodyAndRouteModelBinderProvider(BodyModelBinderProvider bodyModelBinderProvider, ComplexTypeModelBinderProvider complexTypeModelBinderProvider)
{
_bodyModelBinderProvider = bodyModelBinderProvider;
_complexTypeModelBinderProvider = complexTypeModelBinderProvider;
}
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
var bodyBinder = _bodyModelBinderProvider.GetBinder(context);
var complexBinder = _complexTypeModelBinderProvider.GetBinder(context);
if (context.BindingInfo.BindingSource != null
&& context.BindingInfo.BindingSource.CanAcceptDataFrom(BodyAndRouteBindingSource.BodyAndRoute))
{
return new BodyAndRouteModelBinder(bodyBinder, complexBinder);
}
else
{
return null;
}
}
}
Run Code Online (Sandbox Code Playgroud)
public static class BodyAndRouteModelBinderProviderSetup
{
public static void InsertBodyAndRouteBinding(this IList<IModelBinderProvider> providers)
{
var bodyProvider = providers.Single(provider => provider.GetType() == typeof(BodyModelBinderProvider)) as BodyModelBinderProvider;
var complexProvider = providers.Single(provider => provider.GetType() == typeof(ComplexTypeModelBinderProvider)) as ComplexTypeModelBinderProvider;
var bodyAndRouteProvider = new BodyAndRouteModelBinderProvider(bodyProvider, complexProvider);
providers.Insert(0, bodyAndRouteProvider);
}
}
Run Code Online (Sandbox Code Playgroud)
Men*_*tor 13
添加到Statrup:
services.AddMvc()
.AddHybridModelBinder();
Run Code Online (Sandbox Code Playgroud)模型:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public string FavoriteColor { get; set; }
}
Run Code Online (Sandbox Code Playgroud)控制器:
[HttpPost]
[Route("people/{id}")]
public IActionResult Post([FromHybrid]Person model)
{ }
Run Code Online (Sandbox Code Playgroud)请求:
curl -X POST -H "Accept: application/json" -H "Content-Type:application/json" -d '{
"id": 999,
"name": "Bill Boga",
"favoriteColor": "Blue"
}' "https://localhost/people/123?name=William%20Boga"
Run Code Online (Sandbox Code Playgroud)结果:
{
"Id": 123,
"Name": "William Boga",
"FavoriteColor": "Blue"
}
Run Code Online (Sandbox Code Playgroud)还有其他高级功能.
| 归档时间: |
|
| 查看次数: |
13434 次 |
| 最近记录: |