2

I am developing a WebApi using aspnet core. I have been able to setup a basic project and get requests are working well.

Now I am trying to POST a complex JSON object to the API using postman. The API code is like:

//controller class
public class DemoController : ControllerBase {

    [HttpPost]
    public IActionResult Action1([FromBody]TokenRequest request) {
        /* This works. I get request object with properties populated */
    }

    [HttpPost]
    public IActionResult Action2(TokenRequest request) {
        /* The request is received. But properties are null */
    }
}

//TokenRequest Class
public class TokenRequest {
    public string Username { get; set; }
    public string Password { get; set; }
}

Tried to test the same with postman.

Request 1: (Fail for both Action1 and Action2)

POST /Demo/Action2 HTTP/1.1
Host: localhost:5001
Content-Type: application/json
Cache-Control: no-cache
Postman-Token: 42642430-bbd3-49ca-a56c-cb3f5f2177cc

{
    "request": {
        "Username": "saurabh",
        "Password": "******"
    }
}

Request 2: (Success for Action1, Fail for Action2)

POST /User/Register HTTP/1.1
Host: localhost:5001
Content-Type: application/json
Cache-Control: no-cache
Postman-Token: 94141e01-7fef-4847-953e-a9acb4e6c445

{
    "Username": "saurabh",
    "Password": "******"
}

The things are working with Action1 due to the [FromBody[ tag. But what in case I need to accept multiple parameters? like

public IActionResult Action1(int param1, TokenRequest request)

Option 1: Use a wrapper class (as suggested by Francisco Goldenstein)

[FromBody] cannot be used with two different parameters. Is there any graceful solution where I can accept them as separate parameters in the Action method?

2
  • Where is param1? Query param? Commented Aug 23, 2018 at 18:32
  • Lets say its in body as well. Commented Aug 23, 2018 at 18:37

3 Answers 3

3

One more solution to post several objects is to use Form and post each json object in separated field of the form. Applying JsonBinder to it we can use models in arguments in the same way as with [FromBody].

public class FormDataJsonBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext));

        // Fetch the value of the argument by name and set it to the model state
        string fieldName = bindingContext.FieldName;
        var valueProviderResult = bindingContext.ValueProvider.GetValue(fieldName);
        if (valueProviderResult == ValueProviderResult.None) return Task.CompletedTask;
        else bindingContext.ModelState.SetModelValue(fieldName, valueProviderResult);

        // Do nothing if the value is null or empty
        string value = valueProviderResult.FirstValue;
        if (string.IsNullOrEmpty(value)) return Task.CompletedTask;

        try
        {
            // Deserialize the provided value and set the binding result
            object result = JsonConvert.DeserializeObject(value, bindingContext.ModelType);
            bindingContext.Result = ModelBindingResult.Success(result);
        }
        catch (JsonException)
        {
            bindingContext.Result = ModelBindingResult.Failed();
        }

        return Task.CompletedTask;
    }
}

And the action will be:

public IActionResult Action1(
    [FromForm][ModelBinder(BinderType = typeof(FormDataJsonBinder))] TokenRequest request,
    [FromForm][ModelBinder(BinderType = typeof(FormDataJsonBinder))] TokenRequest request1)
{
    return Ok();
}

Though I would go with a wrapper class option.

Sign up to request clarification or add additional context in comments.

2 Comments

This may work but it is quite complex to solve simple problem.
Thanks for this, I needed to post complex JSON in an hidden input field (generated by angularjs as a component) as part of an overall form submit and didn't want to receive it as a string in the action then deserialize it in the controller.
2
  1. If you pass param1 in JSON document, you have to define param1 in TokenRequest class. The error you got already mean that.

  2. You don't have to pass param1 in JSON document. You can pass param1 via Query params OR Http headers

  3. I prefer passing param1 as a query param.

    POST /User/Register?param1=Param1

    POST /User/Register?param1=Param1 HTTP/1.1
    Host: localhost:5001
    Content-Type: application/json
    Cache-Control: no-cache
    Postman-Token: 94141e01-7fef-4847-953e-a9acb4e6c445
    
    {
        "Username": "saurabh",
        "Password": "******"
    }
    

AND

public IActionResult Action1([FromQuery] int param1, [FromBody] TokenRequest request)

1 Comment

That looks nice. Can we have complex JSON objects in Header or some place other than body? example TokenRequest in this case.
1

If you need to accept more parameters you need to create another class that contains all the things you need. A wrapper class, something like this:

public class X 
{
     public TokenRequest TokenRequest { get; set; }
     public int Param1 { get; set; }
}

And the action of the controller would be like this:

[HttpPost] public IActionResult Action1([FromBody]X request) { // your code }

In ASP.NET Core you need to indicate if the values of the request are part of the body, form, etc. This is different to how it works ASP.NET MVC where every parts (body, form, etc.) are considered.

As a side note:

You could also think about using inheritance, defining a new class that inherits from TokenRequest but that's not a good practice as the new type doesn't respect "is a TokenRequest". It's better to say "has a TokenRequest".

5 Comments

That is the first thought I had. But is it possible to handle this scenario without a wrapper class?
No, that won't work. You need the wrapper. I know it's more classes but it's cleaner in my opinion.
Ok. Will try out a few more things around this tomorrow. Hope there is some other way. And thanks for the answer. Will be going ahead with this one if I can't find/workout any other solution in a day or two.
I migrated a big app from ASP.NET MVC to ASP.NET Core MVC a month ago so I know what you're facing. I wish Microsoft had written a migration guide. Yesterday I had to write an API using ASP.NET MVC to continue using a PDF library that has no Core version. Good luck!
Yea even I am used to ASP.NET MVC. New to core. I think I need to learn a bit more how http works.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.