Reputation: 1653
I'm using web api method to update database using entity framework. I faced with a problem when implementing the update method.
The BoxDto is has 20 properties but in a case, client wants to update only 1 property. I use Postman to send this request.
PUT - {{ServerPath}}/api/boxes/{id}
row data:
{
"LastPrintedBy" : "ABC"
}
here is my webapi controller method:
public HttpResponseMessage UpdateBox(int id, BoxDto box)
{
}
Here, BoxDto box will retrieve the LastPrintedBy value correctly, but all other remaining properties get assigned Null. This is going to be a problem in AutoMapper class, because I have implemented a rule to ignore null values when auto-mapping with DB table. Becouse, in a case client wants to set some field null, Controller unable to understand that is auto set Null value by the serializer or Client passed value.
Please advise way to resolve this issue.
Upvotes: 0
Views: 1461
Reputation: 1653
Thank you for all of your ideas and advice. And I figured out bellow alternative by getting all of your thoughts and advice. Hope this option useful for some of you according to your scenario.
[HttpPut]
public HttpResponseMessage UpdateBox(HttpRequestMessage requestMsg, int boxId, JObject boxObj) // Step 1 : Change the input type C# Object to JObject
{
//Step 2: Get the Box entity instance by calling Get method. Save to Dto
BoxDto boxDto = _boxCommonService.GetBox(boxId);
try
{
// Step 3: Iterate the JObject and find is there any user set, Null values.
foreach (var prop in boxObj)
{
string key = prop.Key;
JToken value = prop.Value;
PropertyInfo propertyInfo = boxDto.GetType().GetProperty(key);
if (boxDto.GetType().GetProperty(key) == null)
{
return requestMsg.CreateResponse(HttpStatusCode.BadRequest, "invalid property name");
}
else
{
// Step 4: If there is any, update the Dto
boxDto.GetType().GetProperty(key).SetValue(boxDto, Convert.ChangeType(value, propertyInfo.PropertyType), null);
}
}
// Step 5: Now you can call the service with auttomapper to map the entity with Dto. (Remove any autoMapper rule to ignore null values)
_boxCommonService.UpdateBox(boxId, boxDto);
);
I believe that this is the convenient way to Client App, without set responsibility to client by asking to call Get method before update. And use the automapper built-in capabilities too.
Upvotes: 0
Reputation: 971
What you are looking for is commonly known as partial resource updates, and can be achieved by doing JSON Patch.
Please refer to this article https://dotnetcoretutorials.com/2017/11/29/json-patch-asp-net-core/.
Upvotes: 1
Reputation: 34908
Two options to consider:
Option 1: One update method, but it has to accept a complete representation of the DTO. If the user updates 1 value, all unmodified values are included. In your example, how would you expect the client to be able to tell the server "Set this value to #null"?
Option 2: Arrange API into actions. Often a client cannot update every detail in an entity. They can perform actions that result in some of the fields being updated. The action of "PrintBox" or "UpdateBoxLabel" can either explicitly or implicitly update the one, or a select few properties on a box entity.
The advantage of option 2 is that it breaks things down explicitly, but it does result in more code, but simple code, in your API. The issue with Option 1 is that in most cases a client should not have cart blanche permissions to change anything and everything about an entity. Your client-side code may only allow for a few details to be updated, written to a DTO and sent to the server, but your server must absolutely validate that only allowed values are updated, and that they are valid. Web requests can be intercepted by a user in the browser or even malware plugins which can tamper with data before the server receives it. With option 2 you still need to validate, but the footprint is a lot smaller.
For instance with option 2 if you have an action called "PrintBoxLabel(boxId)", that action could (as an example): - Assert that the box record for boxId exists. - Assert that the current user session has access to that box. - Prepare the PDF/Report for the box label. - Get the user ID from the current session and update the Box record's LastPrintedBy value, and LastPrintedOn value. - Return the PDF.
Rather than having a client call to do a print, then call an "UpdateBox" method. The previous example just needs the Box ID, the rest is derived from the server session. If you do want to send data from the client, the API methods are designed to accept only what data is allowed to be updated, validates them, then takes the appropriate action. The system is less vulnerable to unexpected tampering.
Upvotes: 0
Reputation: 121
It is impossible to automapper knows if the null value is from the serialize or was set by the client, what you could do is to remove the auto mapper`s rule that ignores null values then pass all values (even if you are not updating it) into the DTO.
PUT - {{ServerPath}}/api/boxes/{id}
row data:
{
"LastPrintedBy" : "ABC",
"Property two" : "Updated Value",
"Property three" : "Same value",
"Property four" : null <- null set by client
}
Upvotes: 2