.Net Core API - Mixed Model Binding not working - c#

Framework used is .Net Core 3.0 but tested in 2.2 and got the same behavior.
I am using a class to automatically bind the body request properties and that works pretty well, even without having the [FromBody] attribute on them.
Now, I added a new property in this class that will match a property from the header and it works if I use it directly into the controller, like this:
public IActionResult Test(TestRequest request, [FromHeader(Name = "Authorization")] string token)
However, when I try to get the same result by adding the [FromHeader] attribute into the class property, it doesn't work.
Here is a sample code to illustrate the issue:
[ApiController]
[Route("api")]
public class TestController : ControllerBase
{
[HttpPost]
[Route("Test")]
public IActionResult Test(TestRequest request)
{
Console.WriteLine("request.UserId: " + request.UserId);
Console.WriteLine("request.Token: " + request.Token);
return Ok();
}
}
public class TestRequest
{
[FromBody]
public string UserId { get; set; }
[FromHeader(Name = "Authorization")]
public string Token { get; set; }
}
Did anybody ever face the same issue?

You need to configure SuppressInferBindingSourcesForParameters as true in ConfigureServices in Startup.cs like below :
services.AddMvc().ConfigureApiBehaviorOptions(options =>
{
options.SuppressInferBindingSourcesForParameters = true;
});
Action:
[HttpPost]
[Route("Test")]
public IActionResult Test(TestRequest request)
And call the api with your Authorization header(not shown below) and body string, for postman
Update:
Since you use [FromBody] on the string property,it accepts a string instead of json object.
If you still would like to pass json object as { "userId" : "123" }, you could warp the userId into a model,for example:
public class User
{
public string UserId { get; set; }
}
public class TestRequest
{
[FromBody]
public User User { get; set; }
[FromHeader(Name = "Authorization")]
public string Token { get; set; }
}

Related

Use FromRoute and FromQuery in single action of ApiController in AspNetCore 2.2

Using ASP.NET Core 2.2 I have the following ApiController action:
[ApiController]
public class PostController : Controller
{
[HttpGet("posts/{postId:int:min(1)}")]
public async Task<IActionResult> GetByPostId([FromQuery]GetByPostIdRequest request)
{
}
}
Where GetByPostIdRequest is the following:
public class GetByPostIdRequest
{
[FromRoute]
public Int32 PostId { get; set; }
public String LanguageCode { get; set; }
public IncludeExpression Include { get; set; }
}
The only way all parameters get values are:
Have FromQuery in action so I don't have the error Unsupported Media Type
Have FromRoute inside the Request class to bind the PostId.
Isn't there another way to do this?
I tried the following, which is logic to me, but does not work:
[ApiController]
public class PostController : Controller
{
[HttpGet("posts/{postId:int:min(1)}")]
public async Task<IActionResult> GetByPostId([FromRoute, FromQuery]GetByPostIdRequest request)
{
}
}
This is the package what you might be looking for: HybridModelBinding.
It solves the problem of mixed model binding in ASP.NET by using the [FromHybrid] attribute. Your model will get bound with data from the body of a request, and then get updated with data from route or querystring-attributes.
[ApiController]
public class PostController : Controller
{
[HttpGet("posts/{postId:int:min(1)}")]
public async Task<IActionResult> GetByPostId([FromHybrid]GetByPostIdRequest request)
{
}
}

Swagger showing incorrect query param

I have this controller and action method:
[ApiController]
[Route("api/[controller]")]
public class AppointmentController : ControllerBase
{
[Route("{provider}/AvailableSlots")]
[HttpGet]
public Task<AvailableSlotsResponse> GetAvailableSlots(Request<AvailableSlotsRequest> request)
{
return null;
}
}
Here's the model:
public class Request<T> where T : class
{
[FromRoute]
public string Provider { get; set; }
[FromQuery(Name = "")]
public T Model { get; set; }
}
public class AvailableSlotsRequest
{
//[FromQuery(Name = "Location")] //Would prefer not to have to use this
public string Location { get; set; }
}
I need to use Location as the query param name in the URL in order to hit the endpoint, as expected.
eg. http://localhost/api/Appointment/Company/AvailableSlots?Location=SYD
However, when I view the Swagger page, the parameter is called Model.Location which is confusing for consumers of my API:
I can use [FromQuery(Name = "Location")] to force Swagger to display Location, however this feels very redundant and duplicates the property name.
Here is my Swagger set up in ConfigureServices():
services.AddSwaggerDocument(document =>
{
document.PostProcess = d =>
{
d.Info.Version = Configuration["APIVersion"];
d.Info.Title = $"{Configuration["ApplicationName"]} {Configuration["DomainName"]} API";
};
});
How can I make Swagger display Location instead of Model.Location, without having to duplicate the word "Location" in the [FromQuery] attribute?
Add to the controller parameter the attribute [FromRoute]:
public Task<AvailableSlotsResponse> GetAvailableSlots([FromRoute]Request<AvailableSlotsRequest> request)
Remove the attribute FromQuery in the Model property and uncomment the attribute FromQuery from de Location Property.
Unfortunately I had to use [FromQuery(Name = "<PropertyName>")].
However I found a better way:
[ApiController]
[Route("api/[controller]")]
public class AppointmentController : ControllerBase
{
[Route("{provider}/AvailableSlots")]
[HttpGet]
public Task<AvailableSlotsResponse> GetAvailableSlots(AvailableSlotsRequest request)
{
return null;
}
}
public class Request
{
[FromRoute]
public string ProviderName { get; set; }
}
public class AvailableSlotsRequest : Request
{
[FromQuery]
public string Location { get; set; }
}
This also means the model can use any attribute, compared to my first attempt where the T Model was decorated with [FromQuery]

How to pass a file and json object from a postman to asp.net core webapi

I have a post method with below signature,
[HttpPost]
public ActionResult SavePriorAuthorization(MainPriorAuthorization priorAuthorization, IFormFile file)
Now I want to pass the object along with the file from a postman. I have tried the following option which doesn't work.
This gives an error, System.ArgumentNullException: Value cannot be null.Parameter name: header
Header Type : multipart/form-data
Any help would be appreciated.
What I usually do is create a ViewModel like this one:
public class MainPriorAuthorizationViewModel
{
public IFormFile File { get; set; }
public string TestName { get; set; }
}
Then create an action with [FromForm] attribute so that it knows from where it needs to mapped:
[HttpPost]
public void Post([FromForm]MainPriorAuthorizationViewModel priorAuthorization)
{
//do logic
}
Then in my postman its look like this:
Hope this helps
Try to change the settings of the key in the MainPriorAuthorization model, you could directly set properties name of the model as the key in Postman.
The following is the example code that I tested and worked well:
Guest Model
public class Guest
{
public int Id { get; set; }
public string Name { get; set; }
}
Add the [FromForm] attribute to the parameter in action
[HttpPost]
public void SaveGuest([FromForm]Guest guest,IFormFile file)
{ }
The screenshot of the Postman

asp.net core web api Post form data with brackets

I'm running server build with asp.net core (v2.1) web api and have this REST HTTP call:
POST http://website.com/callback
with the headers:
...
Content-Type: application/x-www-form-urlencoded
and the body:
response%5Bkey%5D=123&response%5Bname%5D=hi
I want to receive this message at this point:
[HttpPost]
[Route("callbacks")]
public ActionResult Hook([FromForm]Model model)
{
// <---- Model has instance but empty fields
return Ok();
}
My Model is:
public class Model
{
public string key { get; set; }
public string name { get; set; }
}
Somehow the brackets ("[key]" and "[name]") are not being parsed into my model instance. They are null both, although I provide them in body.
How to solve it?
You should set name in form for your properties:
public class Model
{
[FromForm(Name = "response[key]")]
public string key { get; set; }
[FromForm(Name = "response[name]")]
public string name { get; set; }
}

How to bind Json parameters to Web Api parameters in ASP.NET?

When I have this method in an MVC Controller
[HttpPost]
public async Task<ActionResult> MyMethod(int param1, string param2)
{
//....
}
I can send a Json object {param1:1, param2:"str"} it works just fine and parameters are resolved. However when I do this for a WebApi 2 it doesn't work. Because [FromBody] can only be used by 1 parameter according to following example on documentation.
At most one parameter is allowed to read from the message body
// Caution: Will not work!
public HttpResponseMessage Post([FromBody] int id, [FromBody] string name) { ... }
How can we obtain the same behavior of MVC controller from WebApi controller?
Edit: Creating corresponding classes and replacing parameters is not an option, because a messaging tool checks these methods for maintenance. Signatures should remain the same.
Try to compose one object from these values:
public class Foo
{
public int id {get;set;}
public int name {get;set;}
}
public HttpResponseMessage Post([FromBody] Foo foo)
{
//some stuff...
}
If signature should remain the same, you can try to specify params in url, like that: myurl?id=1&name=Tom still via POST verb.
You can try like this:
public HttpResponseMessage Post([FromBody]dynamic value)
{
int id= value.id.ToString();
string name = value.name.ToString();
}
And pass json like following
{
"id":"1",
"name":"abc"
}
If you have to pass multiple parameter please use class object:
public class PortalClass
{
public ApplicationModel applicationModel { get; set; }
public string user_id { get; set; }
public string id { get; set; }
public object pageCollection { get; set; }
}
public object GetApplication(PortalClass data)
{
JsonSerializerSettings settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All, PreserveReferencesHandling = PreserveReferencesHandling.None };
var myObject=JsonConvert.DeserializeObject<PageCollection>(data.pageCollection.ToString(), settings)
return null;
}
Client Side:
var data = {
user_id: userId,
id: id
};
http.post(url, data).then(
function (response) {
}, function (err) {
callback.reject(err);
});

Categories

Resources