I'm using WebAPI to to update some entities in the database (.net core 3.0, mvc webapi, efcore, vs2019). I'm having trouble figuring out which properties have actually been sent in JSON request to my webapi endpoint, so I do not update them in the database if they were not sent (i.e. leave them as they are).
Here's my code:
Model:
public class UpdateModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
}
WebAPI method:
[HttpPut("entity/{id}")]
public async Task<SomeResult> Update(int id, [FromBody]UpdateModel model)
{
...
}
Request payload (PUT method, example URL: https://myserver/api/entity/1):
{
FirstName: "John"
}
In case with above JSON, only FirstName would get updated in the DB.
If, for example, this JSON was sent:
{
FirstName: "John",
Address: null
}
That would mean that (besides FirstName gets updated) Address would also be updated to null (which wouldn't be in the first case).
There are actually not just string objects in the Model, they are just used here as an example.
My API gets called and everything (with LastName and Address being set to NULL, of course), I just want to know which fields have actually been sent in a JSON request. Must I check every field for NULL value or is there any other method for checking (more generic)?
Maybe there is some other approach to this kind of problem and I'm just not familiar with it?
Related
I'm trying to get some data from the request body in a POST Controller, but the console shows empty props:
The Post Controller:
[HttpPost("{id}/features")]
public ActionResult<bool> AddFeatureAsync(Guid Id, [FromBody] AddRoleFeatureRequest request)
{
Console.WriteLine(request.Name);
Console.WriteLine(request.Description);
Console.WriteLine(request.Id);
return true;
}
The AddRoleFeatureRequest class:
public class AddRoleFeatureRequest
{
public Guid Id;
public string? Name;
public string? Description;
}
The JSON data from Postman (Using body raw as Json):
{
"name": "Feature ABC",
"description": "description",
"id": "7e12b0ad-2c82-46f0-a69e-8538efb0aa60"
}
What am I doing wrong?
I'm trying to get some data from the request body in a POST
Controller, but the console shows empty props:
Your reason for getting null data on your console or in controller is pretty obvious because you have defined your AddRoleFeatureRequest class field only which doesn't allow to set any value on it. For instance, public string? Name; is a field not property. To set value, you must implement valid setter. Thus, it can be treated as valid property and able to assign value into it.
Solution:
public class AddRoleFeatureRequest
{
public Guid Id { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
}
Note: Property without getter and setter will always consider as field, it will not allow you to assign value from outside.
Output:
Note: Modifying your class defination would completely resolve your issue. No other changes required.
Your "AddRoleFeatureRequest" class has capitals and your json data does not. This could be the source of your problems.
the attribute names might be the reasons because they ar different then the json keys, retry it while considering letters cases,
I am very new to ASP.NET Core, C#, and RESTful APIs. I'm trying to create a simple practice application right now to practice the different REST commands. The "database" I am working with is a List of objects that have a Name, Age, and Weight.
Currently I am trying to implement a POST method. I could potentially use a [FromQuery] in order to get the data I need for creating the new object and adding it. However, I think it would be better to access it FromBody, especially if I want to add more fields later on.
I don't quite understand/know how I would be able to put stuff/ask the user (??) for data for this in the body. I think I grasp that, when the URL is called, it parses through whatever is in the body of the page/the returned .json and finds it that way, but I don't know how to populate this in the first place.
So far this is just my code for the POST:
[HttpPost]
public ActionResult<List<Objects>> Post([FromQuery] String NewName, [FromQuery] int NewAge, [FromQuery] double NewWeight)
{
return MyList.AddItem(NewName, NewAge, NewWeight);
}
I would love any descriptions on how this works or how I can make this happen...thanks!
Create a model that holds all the required data
public class NewModel {
public String NewName { get; set; }
public int NewAge { get; set; }
public double NewWeight { get; set; }
}
update the action to expect that data and annotate it with [FromBody]
[HttpPost]
public ActionResult<MyModelType> Post([FromBody] NewModel data) {
if (ModelState.IsValid) {
var model = MyList.AddItem(data.NewName, data.NewAge, data.NewWeight);
return model;
}
return BadRequest(ModelState);
}
Consumers of your service would then post a request with the required data in the body of the request and the model binder should populate the model in the controller action.
I'm building a web api microservice using Core 3. I have a class defined as follows:
public class UserSourceList
{
[JsonIgnore]
public string UserId { get; set; }
[JsonIgnore]
public string ListId { get; set; }
public string Name { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public ListTypes ListType { get; set; }
public List<string> Ids { get; set; }
public DateTimeOffset CreationTime { get; set; }
}
When the framework attempts to bind the data provided by a HTTP PUT, it will not populate the UserId and ListId fields. As a result, model binding is failing during validation and returning a HTTP 400, stating that UserId and ListId are required.
The controller's action method is defined as follows:
[HttpPut("{userId:userid}/{listId:listid}", Name = "ReplaceUserList")]
public ActionResult Replace(string userId, string listId, UserSourceList model)
{
return Ok(_listManager.ReplaceUserList(model.UserId, model.ListId, model));
}
A typical call to the API would look similar to this:
PUT /api/v1/listmgmt/abc123def456/c788f2f7b7984424910726d4a290be26
PUT Body
{
"name": "Test",
"listType": "Eans",
"ids": ["97814571867716", "9781430257615", "9780982550670"],
"userId":"abc123def456",
"listId":"c788f2f7b7984424910726d4a290be26"
}
If I removed the JsonIgnore Attribute from the UserId and ListId properties of the model, everything binds as expected.
Is it expected behavior that model binding will ignore fields flagged with JsonIgnore?
I know I can work around it by changing how my validation code works or I can split my model. I would like to understand the current behavior as it is different from what I expected and experienced with ASP.NET MVC 4 and WebApi 2.
Thanks
Short answer, Newtonsoft Json.Net is being used to deserialize the post/put body when the content type is application/json. Therefore, the userId and listId parameters are being ignored during deserialization, but evaluated during model validation.
I removed the JsonIgnore Attribute as well as all the Data Annotations, and changed to the FluentValidation package which provided the ability at runtime to configure how the body should be validated based up the type of call made.
I think the reason is because of this:
[HttpPut("{userId:userid}/{listId:listid}", Name = "ReplaceUserList")]
userId and listId are required and cannot be ignored because they are defined in the annotation HttpPut. I think you need to remove them from HttpPut's parameters and find another way to get around this.
Hope this helps!
I have the following OData builder config. Which defines an Action Create with a few parameters. Note that I ignore the property Password. That's important later.
var userEntitySetName = nameof(UsersController).Replace("Controller", string.Empty);
var users = builder.EntitySet<User>(userEntitySetName);
users.EntityType.HasKey(x => x.Id);
users.EntityType.Ignore(x => x.Password);
var userCreateAction = users.EntityType.Collection.Action("Create").ReturnsFromEntitySet<User>(userEntitySetName);
userCreateAction.Parameter<int>("number");
I can then choose to do either
userCreateAction.EntityParameter<User>("user");
Or
userCreateAction.Parameter<User>("user");
The description for Parameter says: "Adds a new non-binding parameter". EntityParameter doesn't have a description. But I assume it's a binding parameter. Whatever that means. (Please explain)
More on these two choices later.
My User class
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string PhoneNumber { get; set; }
public string Email { get; set; }
public string Password { get; set; } // remember this is ignored
}
My controller action
[ODataRoute("Users/Create")]
public IHttpActionResult PostCreate(ODataActionParameters parameters)
{
// omitted
}
My request payload
{
"number": 1,
"user":{ "FirstName": "foo", "LastName": "bar", "Password": "123" }
}
When I POST the above request parameters is null. However if I POST it without the ignored property Password then it works as expected.
This is where I assumed that using EntityParameter would use the User entity defined by the OData builder. The one without the Password property.
And Parameter would use the actual User type, including the Password property. Allowing me to do the above request.
But that's not the case :(
So I'm confused about the difference between Parameter and EntityParameter. Can someone explain their intended use?
But hold on there's more.
This is where I got really confused.
If I reduce the number of parameters to my action to only have the complex User type and change my controller action to:
[ODataRoute("Users/Create")]
public IHttpActionResult PostCreate(User user)
{
// omitted
}
Then I can do a POST request with the Password property included. Note it's no longer wrapped in another object.
{ "FirstName": "foo", "LastName": "bar", "Password": "123" }
Which is nice. But I need the additional parameters :(
I guess the real question boils down to: "How can I POST ignored properties to my multi parameter action?"
Thanks
Update
I can't get the POST with Password to work anymore. I'm sure I had it working earlier. So please take the last part with a grain of salt.
The question is still valid though :)
The first question: EntityParameter also is adding an non-binding entity type parameter. If you add a binding entity type, it means you bind this action to a entity or entityset, see function: SetBindingParameter.
So why you use Parameter function can get password property? This is because when you call Parameter we will search User type in complex types and primitive types in your model and we can't get it, so we build a complex type for your User type using the CLR type which have password property, and use EntityParameter function will get the User entity type in the model which doesn't have the password property.
I was going through a action method code and i saw one attribute was used there but i really did not understand the use. here is the code
public ActionResult User([Bind(Include = "Username,FullName,Email")]User user)
{
if (!ModelState.IsValid()) return View(user);
try
{
user.save()
// return the view again or redirect the user to another page
}
catch(Exception e)
{
ViewData["Message"] = e.Message;
return View(user)
}
}
([Bind(Include = "Username,FullName,Email")]User user)
i just do not understand the above line Bind include etc
so please help me to understand this kind of attribute used & when people write this kind of code in mvc. it will be really good help if some one make me understand with sample small code where they will use this Bind attribute.
Update:
Suppose i have form from where user can enter only FirstName,LastName & Gender then my action method looks like
public ActionResult Edit(string FirstName,string LastName,string Gender)
{
// ...
}
this will work i think. then why i should use a Bind Attribute because my above action method will works fine.
Bind attribute lets you "fine-tune" the model-binding process of certain parameter Type, without registering a custom ModelBinder specific to the Type.
For example, assume your Action is expecting a Person parameter defined as follows:
public class Person
{
public Person(string firstName, string lastName, Gender gender)
{
this.FirstName = firstName;
this.LastName = lastName;
if (gender == Gender.Male)
this.FullName = "Mr. " + this.FirstName + " " + this.LastName;
else
this.FullName = "Mrs. " + this.FirstName + " " + this.LastName;
}
public string FirstName { get; set; }
public string LastName { get; set; }
public Gender Gender { get; set; }
// 'FullName' is a computed column:
public string FullName { get; set; }
}
And the Action:
public ActionResult Edit(Person person)
{
...
}
Now, if someone is posting the following JSON:
{
"FirstName":"John",
"LastName":"Smith",
"Gender":"Male",
"FullName":"Mrs. John Smith"
}
Your Action will now have a person with the wrong FullName ('Mrs' instead of 'Mr').
To avoid such behavior you can use the Bind attribute and explicitly exclude the FullName property from the binding process ('Black-list'):
public ActionResult Edit([Bind(Exclude="FullName")] Person person)
{
...
}
Alternatively, you can use Include to ignore ('Black-list') all properties and only include ('White-list') the specified properties:
public ActionResult Edit([Bind(Include="FirstName,LastName,Gender")] Person person)
{
...
}
More info on MSDN.
When this action is executed the MVC model binder will use the request parameters to populate the user parameter's properties, as you may already know. However, the Bind attribute tells the model binder to only populate properties with names specified.
So in this case only the Username, FullName and Email properties will be populated. All others will be ignored.
See here for more details: http://ittecture.wordpress.com/2009/05/01/tip-of-the-day-199-asp-net-mvc-defining-model-binding-explicitly/
The Bind attribute is one way to protect against over-posting in create scenarios. For example, suppose the Student entity includes a Secret property that you don't want this web page to set.
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public string Secret { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Even if you don't have a Secret field on the web page, a hacker could use a tool such as fiddler, or write some JavaScript, to post a Secret form value. Without the Bind attribute limiting the fields that the model binder uses when it creates a Student instance, the model binder would pick up that Secret form value and use it to create the Student entity instance. Then whatever value the hacker specified for the Secret form field would be updated in your database. The following image shows the fiddler tool adding the Secret field (with the value "OverPost") to the posted form values.
The value "OverPost" would then be successfully added to the Secret property of the inserted row, although you never intended that the web page be able to set that property.
It's a security best practice to use the Include parameter with the Bind attribute to whitelist fields. It's also possible to use the Exclude parameter to blacklist fields you want to exclude. The reason Include is more secure is that when you add a new property to the entity, the new field is not automatically protected by an Exclude list.