参数中带有 IEnumerable 的 C# OData Web API POST 端点返回错误 400,输入无效

Posted

技术标签:

【中文标题】参数中带有 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 【发布时间】:2021-05-06 02:30:35 【问题描述】:

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

[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 一起正常工作。

我的第三种类型,这里解释的那种,也适用(GET、POST、DELETE)!

但是:我的端点的POST 仅在我传递复杂类的一个实例时才有效。如果我传递IEnumerable(并在邮递员[ "bla":12" , "bla":24 ] 中正确传递数据),它将不起作用。

我尝试了很多东西。仅举几例:

    删除控制器属性中的[ApiController](抱歉,我没有找到链接..) - 不起作用:我在参数中获得了NULL 值(但仍然在输入方法)

    尝试传递 DTO(同样的问题 - 错误 400)

    试图在参数中创建一个动态/对象/字符串的传递(丑陋,必须争取将其转换回来)

    尝试过 OData Actions 和 Odata CollectionParameter 函数(没能幸存下来) - EDIT:这就是解决方案!

    试图通过 [FromBody],但没有 [FromBody](所以 xxx-form-encoded) - 正在输入方法,但得到了 NULL

    在 Postman 中,我尝试使用

      "":["Mycollection":"Of","Complex":"Objet"]
    

    也试过了

     =["Mycollection":"Of","Complex":"Objet"]  
    

    (如 MS docs..ahah 中某处所述)

    我在 Google 做了标准的“为什么 Odata 如此糟糕”,打算尝试 GraphQL,但我拒绝了......

我终于做了一个聪明的程序员必须做的事:我从基地重新开始。

长话短说:OData 不能容忍我的参数签名与初始路由不匹配。

所以我不得不在我的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 效果很好!

问题:为什么?

现在,我无法对该特定实体执行完整的 OData 获取(无法找到非 OData 路由的服务容器)。这很正常,我只是删除了它!

【问题讨论】:

【参考方案1】:

我刚刚发现了这个(在写完我的帖子之后):WebApi OData 4 , second POST endpoint for bulk insertion in the controller

似乎是答案,并保持一切“OData 批准”!

如此大的图景,为了能够发布与注册的 OData 类型不同的 TYPE,您必须创建一个名为 Action 的新端点(方法)。此操作可以采用您定义的任何类型(在 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();
    

然后,在控制器的方法中:

    [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,输入无效的主要内容,如果未能解决你的问题,请参考以下文章

C# | 比较IEnumerableList数组

在 C# 中的列表上使用 IEnumerable [重复]

C# IQueryable和IEnumerable的区别

在 C# 中为 IEnumerable<string> 的每个成员添加前缀/后缀

IEnumerable<T> 使用 c# 在函数内访问 T 的属性

如何在 C# 中使用 Ienumerable 反转一系列元素?