簡體   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