参数中带有 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

查看:54
本文介绍了参数中带有 IEnumerable 的 C# OData Web API POST 端点返回错误 400 输入无效的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在这里有一种怪异的行为.我这里有一个控制器(我在这里欺骗和混淆了很多东西......它仍然相关)

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:

  1. 删除控制器属性中的 [ApiController](抱歉,我找不到链接……) - 没有用:我得到了 NULLcode> 在我的参数中的值(但仍然是进入方法)

  1. Remove the [ApiController] in the controller attribute (I don't find the link sorry..) - didn't work : I was getting a NULL 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆