参数中带有 IEnumerable 的 C# OData Web API POST 端点返回错误 400 输入无效 [英] C# OData Web API POST endpoint with IEnumerable in parameters returns error 400 the input was not valid
问题描述
我在这里有一种怪异的行为.我这里有一个控制器(我在这里欺骗和混淆了很多东西......它仍然相关)
I have a spooky behavior here. I have a controller here (I have cheated and obfuscated a lot of stuff here... it is still relevant)
[Route("api/[controller]")]
[ApiController]
public class MyComplexTypeController : ControllerBase
{
//context...
public MyComplexTypeController()
{
//context..
}
[HttpPost]
// For simplicity, I have renamed the endpoint..
// In reality I just have ONE Post endpoint here
public ActionResult MyCallThatWorks(MyComplexObj param_origData)
{
// The param_origData is GREAT and working!
return Ok();
}
[HttpPost]
// For simplicity, I have renamed the endpoint.
// In reality I just have ONE Post endpoint here
public ActionResult MyCallThatDontWorks(IEnumerable<MyComplexObj> param_origData)
{
// I get a 400 "The input was not valid." in PostMan!
return Ok();
}
这里有 startup.cs
:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddAutoMapper(typeof(Startup2));
services.AddCors();
services.AddRazorPages();
services.AddOData();
services.AddControllers()
//https://docs.microsoft.com/en-us/aspnet/core/web-api/advanced/formatting?view=aspnetcore-5.0
.AddJsonOptions(options =>
options.JsonSerializerOptions.PropertyNamingPolicy = null);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseCors(x => x.AllowAnyMethod().AllowCredentials().AllowAnyHeader().SetIsOriginAllowed(x => true));
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
endpoints.Select().Filter().OrderBy().Expand().Count().MaxTop(null);
endpoints.MapODataRoute("odata", "api", MyGetEdmModel());
});
}
private static IEdmModel MyGetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<MyComplexObj>("MyComplexType");
return builder.GetEdmModel();
}
}
事实上,我有 3 个复杂类型和 3 个端点.我的两个端点与 OData 一起正常工作.
In fact I have 3 complex types with 3 endpoints. Two of my endpoint work correctly with OData.
我的第三种类型,这里解释的,也适用(GET、POST、DELETE)!
My third type, the one explained here, works also (GET, POST, DELETE) !
但是:我的端点的 POST
仅在我传递复杂类的一个实例时才有效.如果我传递 IEnumerable
(并在 Postman [ { "bla":12" }, { "bla":24 }]
中正确传递数据,它将不起作用.
BUT: the POST
of my endpoint only works if I pass ONE instance of my complex class. It will NOT work if I pass an IEnumerable
(and correctly pass data in Postman [ { "bla":12" }, { "bla":24 }]
.
我尝试了很多东西.仅举几例:
I tried a loooooot of stuff. Just to name a few:
删除控制器属性中的
[ApiController]
(抱歉,我找不到链接……) - 没有用:我得到了NULL
code> 在我的参数中的值(但仍然是进入方法)
Remove the
[ApiController]
in the controller attribute (I don't find the link sorry..) - didn't work : I was getting aNULL
value in my params (but still, was entering the method)
尝试改为传递 DTO(同样的问题 - 错误 400)
Tried passing a DTO instead (same problem - error 400)
试图在 param 中创建一个传递动态/对象/字符串(丑陋,必须努力将其转换回来)
Tried to create a pass a dynamic/object/string in param (ugly and have to fight to convert it back)
尝试了 OData 操作和 Odata CollectionParameter 函数(在这个函数中失败了)- 编辑:这就是解决方案!
Tried OData Actions and Odata CollectionParameter function (didn't survive this one) - EDIT : that was the solution!
试图传递它 [FromBody]
,并且没有 [FromBody]
(所以 xxx-form-encoded) - 正在进入方法,但得到NULL
!
Tried to pass it [FromBody]
, and without [FromBody]
(so xxx-form-encoded) - was entering in the method, but got NULL
!
在 Postman 中,我尝试使用
In Postman, I tried using
{"":[{"Mycollection":"Of"},{"Complex":"Objet"}]}
也试过了
=[{"Mycollection":"Of"},{"Complex":"Objet"}]
(如 MS 文档中某处所述..ahah)
(as stated somewhere in MS docs..ahah)
我在 Google 中做了标准的为什么 Odata 如此糟糕",打算尝试 GraphQL,但我确实拒绝...
I did the standard 'Why Odata is so shitty' in Google, was going to try GraphQL, but I did resist...
我终于做了一个聪明的程序员必须做的事情:我从基地重新开始.
I finally did what a clever coder must do: I restarted from the base.
长话短说:OData 不能容忍我的参数签名与初始路由不匹配.
Long story SHORT : OData was not tolerating that my param signature did not match the initial routing.
所以我不得不在我的 Startup.cs
中删除这一行:
So I had to remove this line in my Startup.cs
:
private static IEdmModel MyGetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<MyComplexObj>("MyComplexType"); <--THIS ONE
builder.EntitySet<OtherComplexType>("OtherComplexType");
builder.EntitySet<OtherComplexType2>("OtherComplexType2");
return builder.GetEdmModel();
}
它在传递 IEnumerable
时效果很好!
and it was working great with passing an IEnumerable
!
问题:为什么?
现在,我无法对该特定实体执行完整的 OData 提取(无法找到非 OData 路由的服务容器).这是正常的,我刚刚删除了它!
Now, I cannot do a full OData fetch on that particular entity (cannot find the services container for the non-OData route). That's normal, I just removed it!
推荐答案
我刚找到这个(写完我的帖子后):
WebApi OData 4 ,第二个 POST 端点在控制器中批量插入
I JUST found this one (after writing my post) :
WebApi OData 4 , second POST endpoint for bulk insertion in the controller
似乎就是答案,并保持OData 批准"的所有内容!
Seems to be the answer, and keeping every thing 'OData approved'!
如此大的图景,为了能够发布与注册的 OData 类型不同的 TYPE,您必须创建一个新的端点(方法),称为 Action.此操作可以采用您定义的任何类型(在 startup.cs 中):
So big picture, to be able to POST a TYPE that is different than the OData type registered, you have to create a new endpoint (method), called Action. This action can than take whatever type you define (in startup.cs):
private static IEdmModel MyGetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<xxx>("xx");
builder.EntitySet<yyyy>("yyy");
builder.EntitySet<MyComplexType>("MyComplexType");
// added this line here:
builder.EntityType<MyComplexType>().Collection
.Action("BulkAdd")
.CollectionParameter<MyComplexType>("MyXREF");
return builder.GetEdmModel();
}
然后,在控制器的方法中:
and then, in the method of the controller:
[HttpPost]
public async Task<ActionResult> BulkAdd(ODataActionParameters parameters)
{
if (!ModelState.IsValid)
{
return BadRequest();
}
List<MycomplexType> bookings = ((IEnumerable<MycomplexType>)parameters["MyXREF"]).ToList();
瞧!
这篇关于参数中带有 IEnumerable 的 C# OData Web API POST 端点返回错误 400 输入无效的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!