Search code examples
curlasp.net-corehttp-postasp.net-core-webapiaspnetboilerplate

Can`t recive POST request Asp.net core 2.2


Can`t recive 3ds party POST request. 415 Unsupported Media type or empty model

On backend: Asp.net core 2.2 and Aspnetbilerplate(if its important)

request from a third-party server, so I can not influence it. I can only specify the endpoint to which requests will be sent

Looks like that: curl http://MyServer/api/MyController/MyAction -d '{"a":"a", "b":1}'

My code Dto:

    public class testDto
    {
        public string A { get; set; }
        public int B { get; set; }
    }

Controller:

[Route("api/[controller]/[action]")]
public class MyController : MyControllerBase
{
   ...
   [HttpPost]
   public async Task<testDto> MyAction(testDto dto)
   {
     //some code
     _logger.Info("test");            
   }
   ...
}

My test request from console with results:

C:\WINDOWS\system32>curl -d '{"a":"a", "b":1}' http://myServerUrl/api/MyController/MyAction
curl: (3) [globbing] unmatched close brace/bracket in column 4
{"result":{"a":null,"b":0},"targetUrl":null,"success":true,"error":null,"unAuthorizedRequest":false,"__abp":true}

Model is empty, no binding has occurred.

I am added [FromBody] to action like that:

   [HttpPost]
   public async Task<testDto> MyAction([FromBody]testDto dto)
   {
     //some code
     _logger.Info("test");            
   }

Result: HTTP status code 415

Also, try adding [FromForm] and [FromQyesry]. Result: empty model

What is the problem? How do I make it work? Thank you in advance.


Solution

  • The first thing is the request curl http://MyServer/api/MyController/MyAction -d '{"a":"a", "b":1}' is not correct , you could see the protocol use :

    curl http://MyServer/api/MyController/MyAction -d '{"a":"a", "b":1}' --trace-ascii debugdump.txt
    

    If checking the dump file , you will find the data is not completely sent :

    0000: POST /api/values/MyAction HTTP/1.1
    0024: Host: localhost:44348
    003b: User-Agent: curl/7.55.1
    0054: Accept: */*
    0061: Content-Length: 6
    0074: Content-Type: application/x-www-form-urlencoded
    00a5: 
    => Send data, 6 bytes (0x6)
    0000: '{a:a,
    == Info: upload completely sent off: 6 out of 6 bytes
    == Info: schannel: client wants to read 102400 bytes
    == Info: schannel: encdata_buffer resized 103424
    == Info: schannel: encrypted data buffer: offset 0 length 103424
    == Info: schannel: encrypted data got 322
    == Info: schannel: encrypted data buffer: offset 322 length 103424
    

    You should contact with 3ds party to confirm the request .

    Anyway , if default model binding doesn't meet your requirement , you can create Custom Model Binding :

    1. Adding middleware to make request EnableRewind :

      app.Use(async (ctx, next) =>
      {
          ctx.Request.EnableRewind();
          await next();
      });
      
    2. Create custom binder which implements IModelBinder:

      public class testDtoEntityBinder : IModelBinder
      {
          public Task BindModelAsync(ModelBindingContext bindingContext)
          {
              if (bindingContext == null)
              {
                  throw new ArgumentNullException(nameof(bindingContext));
              }
      
      
              var body = bindingContext.HttpContext.Request.Body;
              body.Position = 0;
      
      
              string raw = new System.IO.StreamReader(body).ReadToEnd();
      
              //now read content from request content and fill your model 
              var result = new testDto
              {
                  A = "",
                  B = 1,
              };
      
      
              bindingContext.Result = ModelBindingResult.Success(result);
              return Task.CompletedTask;
          }
      }
      
    3. User the binder :

      [ModelBinder(BinderType = typeof(testDtoEntityBinder))]
      public class testDto
      {
          public string A { get; set; }
          public int B { get; set; }
      }