问题
I am building a RESTful API using the ServiceStack framework. A lot of the resources that I need to update are quite big, with up to 40 attributes per class, so I would like to do partial updates instead of replacing the entire resource. Often the client will only need to update one or two attributes out of the 40, so I would like to just send a JSON body consisting of the few attributes.
Since all combinations of attributes are possible, it is not feasible to make an "Update" class per class as suggested here: https://github.com/ServiceStack/ServiceStack/wiki/New-Api#patch-request-example
In the Microsoft ASP.NET WebAPI OData package there is a Delta class that takes a subset of a class and updates the resource based on this subset (http://www.strathweb.com/2013/01/easy-asp-net-web-api-resource-updates-with-delta/). This is the functionality I would like to have, as I will be having quite a few classes so a generic method would be best.
Basically, if I have a class
public class MyClass {
public int a { get; set; }
public int b { get; set; }
...
public int z { get; set; }
}
I would like to update a resource of MyClass with a PATCH request with body
{"a":42,"c":42}
Is there a standard or recommended way to accomplish this with ServiceStack?
回答1:
Declare any scalar values in your DTO as nullable. This will allow you to determine which fields were actually sent in the request:
public class MyClass {
public int? a { get; set; }
public int? b { get; set; }
public int? c { get; set; }
// etc.
// object-type properties are already nullable of course
public string MyString { get; set; }
}
Now if a client sends a partial request, like so:
{ "a": 1, "b": 0 }
You'll be able to determine which properties were actually sent when inspecting your DTO:
myClass.a == 1
myClass.b == 0
myClass.c == null
myClass.MyString == null
etc.
Set up a PATCH
route for your DTO and implement a Patch
method in your service:
public object Patch(MyClass request)
{
var existing = GetMyClassObjectFromDatabase();
existing.PopulateWithNonDefaultValues(request);
SaveToDatabase(existing);
...
}
PopulateWithNonDefaultValues
is key here. It will copy values from your request object onto the database entity, but will only copy properties that are not the default values. Thus, if a value is null, it won't copy it, because the client didn't send a value for it. Notice that it will copy an integer value of zero though, because we made it a nullable int, and the default value for a nullable int is considered by this method to be null, not zero. Declaring your DTO properties as nullable shouldn't cause much of a hassle in the rest of your code.
Note that this approach works easily with JSON. If you need to support XML requests/responses, you may need need to do some additional work with DataContract/DataMember
attributes to insure that nulls are handled correctly.
回答2:
While esker's response is fine I would like to add that it might not be enough for nullable fields - since you don't know if the deserializer or the user have created that null field.
One approach would be to peek at the raw request.
A different approach is to ask the user to provide additional request (querystring) parameter to clearly specify which fields to patch. Something like: patch_fields=name,description,field3 The bonus of that approach is that the end user has more control over the patching and is not overriding a value by mistake (because he used the original entity and forgot to clear some fields)
来源:https://stackoverflow.com/questions/18350932/what-is-the-recommended-way-to-do-partial-updates-with-patch-in-servicestack