繁体   English   中英

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

[英]C# OData Web API POST endpoint with IEnumerable in parameters returns error 400 the input was not valid

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

[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仅在我传递我的复杂 class 的一个实例时才有效。 如果我通过IEnumerable (并正确传递 Postman [ { "bla":12" }, { "bla":24 }]中的数据,它将不起作用。

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

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

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

  3. 试图在参数中创建一个动态/对象/字符串的传递(丑陋并且必须努力将其转换回来)

  4. 尝试过 OData Actions 和 Odata CollectionParameter function (没能幸存下来) -编辑:这就是解决方案!

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

  6. 在 Postman 中,我尝试使用

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

    也试过

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

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

  7. 我在 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 路由的服务容器)。 这很正常,我刚刚删除了它!

我刚刚找到了这个(在写完我的帖子之后):
WebApi OData 4,用于在 controller 中批量插入的第二个 POST 端点

似乎是答案,并保持一切“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();
    }

然后,在controller的方法中:

    [HttpPost]
    public async Task<ActionResult> BulkAdd(ODataActionParameters parameters)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest();
        } 

        List<MycomplexType> bookings = ((IEnumerable<MycomplexType>)parameters["MyXREF"]).ToList();

瞧!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM