I'm creating a web api with ASP.NET Core and I've encountered a problem. I have a post request and I want to pass a Municipality object to it with JSON format. The problem is that I have property Name with attribute [Required]. I call the endpoint by using Postman with this JSON payload {"Name": "London"} and when validating the model, it says "The Name field is required." even though it was definitely provided.
I've tried using [FromBody] attribute, but the problem with it is that it doesn't give me validation errors and only says, that "input was invalid" and gives a null object, so not using this attribute gives a lot better errors.
Github: https://github.com/DeividasBrazenas/Taxes/blob/master/Taxes/Taxes/Controllers/BaseController.cs
BaseModel.cs
public class BaseModel
{
public int Id { get; set; }
}
Municipality.cs
public class Municipality : BaseModel
{
[Required]
public string Name { get; set; }
public ICollection<Tax> Taxes { get; set; }
}
MunicipalitiesController.cs
[EnableQuery]
public async Task<IActionResult> Post(Municipality baseObject)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
await Context.Set<Municipality>().AddAsync(baseObject);
await Context.SaveChangesAsync();
return Created(baseObject);
}
Screenshot of POST request -
Make changes below for your current MunicipalitiesController
Add public async Task<IActionResult> Post(Municipality baseObject) with FromBody
[EnableQuery]
[HttpPost]
public async Task<IActionResult> Post([FromBody]Municipality baseObject)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
await Context.Set<Municipality>().AddAsync(baseObject);
await Context.SaveChangesAsync();
return Created(baseObject);
}
Change the json request to lowercase.
{
"name":"1231"
}
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:
I created ASP.net core 3.0 web API application and added HttpPost endpoint to it.
When I post using postman to this post endpoint, the endpoint doesn't get the JSON I pass to it and instead gets null.
Is there something that has changed in .NET Core 3.0 that has changed/broken HTTP post endpoints?
The JSON I posted:
{
"status": "0",
"operation":"",
"filter":"",
"currentOrderList": [
]
}
Controller code:
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET: api/<controller>
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/<controller>/5
[HttpGet("{id}")]
public string Get(int id)
{
return "value";
}
// POST api/<controller>
[HttpPost]
public void Post([FromBody]string value)
{
}
// PUT api/<controller>/5
[HttpPut("{id}")]
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/<controller>/5
[HttpDelete("{id}")]
public void Delete(int id)
{
}
}
The url I am posting to is https://localhost:44336/api/values. I can see that the endpoint is being hit by the fact that the method is being hit during debugging in visual studio. The only issue is that the parameter is coming in as null
Create a model to match the given data
public class MyClass {
[JsonProperty("status")]
public int Status { get; set; }
[JsonProperty("operation")]
public string Operation { get; set; }
[JsonProperty("filter")]
public string Filter { get; set; }
[JsonProperty("currentOrderList")]
public string[] CurrentOrderList { get; set; }
}
Then update the controller action to expect the desired type
//POST api/values
[HttpPost]
public IActionResult Post([FromBody]MyClass value) {
if(ModelState.IsValid) {
//...
return Ok();
}
return BadRequest();
}
In addition to adding the [JsonProperty] annotations as Nkosi said, I also had to add the nuget package
Microsoft.AspNetCore.Mvc.NewtonsoftJson
and append .AddNewtonsoftJson() into Startup.cs. I found that in another stackoverflow question, but neither change by itself was enough to get my models to hydrate. It took both to get it working.
services.AddMvc().AddRazorRuntimeCompilation().AddNewtonsoftJson();
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; }
}
What's the right way to POST an Entity with a SPATIAL PROPERTY on ASP.NET Web API OData (v4)?
Serialization on GET works fine, but everything I try in the POST causes the model to go null.
Is POST supported at all?
Thanks in advance.
public class PlacesController : ODataController
{
[HttpGet]
[EnableQuery]
public virtual async Task<IHttpActionResult> Get([FromODataUri] string key)
{
var place = new Place()
{
Id = Guid.NewGuid().ToString(),
Location = GeographyPoint.Create(1, 1)
};
return Ok(place);
}
[HttpPost]
[EnableQuery]
public virtual async Task<IHttpActionResult> Post(Place place)
{
if (place == null)
{
return BadRequest();
}
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
return Created(place);
}
}
[DataContract]
public class Place
{
[DataMember]
public string Id
{
get;
set;
}
[DataMember]
public GeographyPoint Location
{
get;
set;
}
}
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
var edmBuilder = new ODataConventionModelBuilder();
edmBuilder.EntitySet<Place>("Places");
var model = edmBuilder.GetEdmModel();
config.MapODataServiceRoute(routeName: "ODataRoute", routePrefix: "api", model: model);
}
}
After reading through below mentioned article it seems possible. You will need to use oDataActionParameters type as input to the post and later in your method cast it to the required type. Mehtod signature will something like below:
public async Task<IHttpActionResult> RateProduct(ODataActionParameters parameters)
check this link for examples and deep dive on this
I have a model called PostUserAccount, and I'm trying to use it in an ApiController as a parameter the way it normally is when you generate a controller with read/write actions, using Entity Framework
Example generated by controller generator:
// POST api/Profile
public HttpResponseMessage PostUserProfile(UserProfile userprofile)
{
if (ModelState.IsValid)
{
db.UserProfiles.Add(userprofile);
...etc
code that I'm working with:
// POST gamer/User?email&password&role
public HttpResponseMessage PostUserAccount(PostAccountModel postaccountmodel)
{
if (!ModelState.IsValid)
{
return Request.CreateResponse(HttpStatusCode.BadRequest, ModelState);
}
if (postaccountmodel == null) return Request.CreateResponse(HttpStatusCode.BadRequest, "model is null");
...etc
for whatever reason, the postaccountmodel is null in this case, and running this api command returns "model is null". any ideas?
Here is the model in question
public class PostAccountModel
{
[Required]
public string Email { get; set; }
[Required]
public string Password { get; set; }
public string Role { get; set; }
public string Avatar { get; set; }
public string DisplayName { get; set; }
}
You're trying to send the model in the URI query string. Problems:
Your query string is not well formed - should be ?Email=xxx&Password=xxx& ...
You need to decorate the postaccountmodel parameter with the [FromUri] attribute, to tell Web API to bind the model from the URI
Another option is to send the model in the request body as JSON or XML. I'd recommend that, especially if you're really sending a password in the request. (And use SSL!)
This topic describes parameter binding in Web API: http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api
Try putting the [FromBody] attribute in front of your postaccountmodel parameter.
http://msdn.microsoft.com/en-us/library/system.web.http.frombodyattribute(v=vs.108).aspx