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);
});
Related
I have a controller method to accept multipart/form-data request with primitive types, File and List of objects. I saw that I need custom model binder when I have complex data type using FromForm and I made one, but the problem is that binding works from Swagger, but not from Postman.
Here is my controller method first:
public async Task<IActionResult> Post([FromForm] AddRequest addRequest)
{
var result = await _service.AddAsync(addRequest);
if (result.Success)
{
return Ok(result);
}
return BadRequest();
}
My request looks like this:
public class AddRequest
{
public string? Name { get; set; }
public string? Description { get; set;}
public IFormFile? File { get; set; }
public IEnumerable<LocaleViewModel>? Locales { get; set; }
}
And here is my viewmodel:
[ModelBinder(BinderType = typeof(MetadataValueModelBinder))]
public class LocaleViewModel
{
public long Id { get; set; }
public string Code { get; set; }
}
And finally model binder:
public class MetadataValueModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
var values = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (values.Length == 0)
return Task.CompletedTask;
var options = new JsonSerializerOptions() { PropertyNameCaseInsensitive = true };
var deserialized = JsonSerializer.Deserialize(values.FirstValue, bindingContext.ModelType, options);
bindingContext.Result = ModelBindingResult.Success(deserialized);
return Task.CompletedTask;
}
}
So, when I fire request from Swagger, binding is done correctly and model name is Locales, but when I do it from Postman or my SPA, binding is not working and model name is Locales[0]. Am I missing something in my model binder or I'm doing everything completely wrong?
I was trying to understand how ValueProvider.GetValue works but with no success.
How do you pass parameters in postman? Please try like this:
IValueProvider.GetValue uses the specified key to retrieve the value object. When there are multiple Locales in the parameter, MetadataValueModelBinder will execute multiple times, each time obtaining the value of a Locales.
First execution:
Second exeution:
Test Result:
made this webapi.
[HttpPost]
public void Post([FromBody] Models.IHero hero)
{
Models.Heroes heroes = new Models.Heroes();
heroes.AddHeroes(hero);
}
calling it from Postman.
https://localhost:44320/api/values?Id=1&Name=Shankar
but hero received in method returns null.
Interface:
public interface IHero
{
int Id { get; set; }
string Name { get; set; }
}
Update:
Converted IHero to Hero class
public class Hero
{
int Id { get; set; }
string Name { get; set; }
}
and used in the Post method.
public void Post([FromBody] Models.Hero hero)
{
Models.Heroes heroes = new Models.Heroes();
heroes.AddHeroes(hero);
}
You have [FromBody] attribute, which means you need to pass data in the body of the Request, but not in Query params
You can not use interface since controller should create an instance of input parameter. It can't create instance from interface. So fix your action
public void Post([FromBody] Models.Hero hero)
{
var heroes = new Models.Heroes();
heroes.AddHeroes(hero);
}
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; }
}
I try to make GET request via WebApi with complex object.
Request is like this:
[HttpGet("{param1}/{param2}")]
public async Task<IActionResult> GetRequest(string param1, int param2, [FromBody] CustomObject[] obj)
{
throw new NotImplementException();
}
Where CustomObject is:
[DataContract]
public class CustomeObject
{
[DataMember]
public string Name { get; set; }
[DataMember]
public string Email { get; set; }
}
How do I compose a valid GET request?
[FromBody] CustomObject[] obj ... GET request has no message body and thus you should change it to FromUri.
Sure, take a look at Documentation
public class GeoPoint
{
public double Latitude { get; set; }
public double Longitude { get; set; }
}
public ValuesController : ApiController
{
public HttpResponseMessage Get([FromUri] GeoPoint location) { ... }
}
Request would be like below, essentially you pass the entire object data as query string
http://localhost/api/values/?Latitude=47.678558&Longitude=-122.130989
An array of object example can be found in another post pass array of an object to webapi
If your complex object is defined by the server, you can model bind to it through the URI and dot notate the properties in the routing template. My advice is to keep this model to one level of properties. You can bind to more complex objects, but you'll quickly find yourself having to write your own model binder.
Note that your argument decorator will need to be changed to [FromUri] to bind a complex object through the Uri. Servers are not required to support GET bodies and most do not.
public class CustomObject
{
public string Name { get; set; }
public string Email { get; set; }
}
[HttpGet]
[Route("{foo.Name}/{foo.Email}")]
public HttpResponseMessage Get([FromUri]CustomObject foo)
{
//...body
return Request.CreateResponse(HttpStatus.OK, foo);
}
You can pass it as a stringified json or use the request body via post instead of get.
I have this function in jQuery
var uri = "api/queries";
function test(){
var params = {
origin: $('#depair').val(),
destination: $('#destair').val(),
departure_date: $('#depdate').val(),
currency: $('#currency').val(),
}
$.getJSON(uri, params)
.done(function (data) {
console.log(data);
});
}
Which sends the request to this Controller:
public class QueriesController : ApiController
{
[HttpGet]
public string GetInfo()
{
return "blah";
}
}
So, the request looks like this
http://localhost:55934/api/queries?origin=&destination=&departure_date=¤cy=
How do I access the parameters of the request from inside the controller GetInfo method?
You can include them as parameters to your function.
[HttpGet]
public string GetInfo(string origin, string destination, string departure_date, string currency)
{
return "blah";
}
You can use Model Binding. First create a class (ViewModel) like this:
public class Querie
{
public string Origin { get; set; }
public string Destination { get; set; }
public string Departure_date { get; set; }
public string Currency { get; set; }
}
Then include this class as parameter to your method like this:
public class QueriesController : ApiController
{
[HttpGet]
public string GetInfo(Querie querie)
{
//querie.Origin
return "blah";
}
}
Model binding maps data from HTTP requests to action method parameters. The parameters may be simple types such as strings, integers, or floats, or they may be complex types. This is a great feature of MVC because mapping incoming data to a counterpart is an often repeated scenario, regardless of size or complexity of the data.
var origin = Request.QueryString["origin"];
Replacing "origin" with your parameter.