Reputation: 2247
In a .netcore application, I would like to offer the following (simplified):
// Create a new record, assume it returns an ID=1
https://site/MyController/Save?FirstName=John&LastName=Doe&Status=Active
// Update the record without full state
PUT https://site/MyController/1
{
'DOB': '1/1/1970',
'Status': null
}
I would like to translate this second call to:
UPDATE MyModel SET DOB = '1/1/1970' AND Status=NULL WHERE Id = 1
I can certainly code my Create
method in MyController
to parse the request (querystring/form/body) for the submitted values, and create my SQL accordingly.
However, I'd prefer to follow MVC conventions and leverage the binding that MVC offers out of the box:
public async Task<MyModel> Save(string id, [FromBody]MyModel instance)
{
await _MyRepository.UpdateAsync(id, message);
return message;
}
The problem here is that instance will look like this:
{
'FirstName': null,
'LastName': null,
'DOB': '1/1/1970',
'Status': null
}
At which point I cannot determine which fields should be NULLed in the Db, and which should be left alone.
I've implemented a wrapper class that:
This would change my method signature a bit, but not impose a burden on developers:
public async Task<MyModel> Save(string id, [FromBody]MyWrapper<MyModel> wrapper
{
await _MyRepository.UpdateAsync(id, wrapper.Instance, wrapper.DirtyProperties);
return wrapper.Instance;
}
My two questions are:
Upvotes: 4
Views: 598
Reputation: 49789
You may look into custom model binding.
create own model binder: class that implements IModelBinder interface:
/// <summary>
/// Defines an interface for model binders.
/// </summary>
public interface IModelBinder
{
/// <summary>
/// Attempts to bind a model.
/// </summary>
/// <param name="bindingContext">The <see cref="ModelBindingContext"/>.</param>
/// <returns>
/// <para>
/// A <see cref="Task"/> which will complete when the model binding process completes.
/// </para>
/// <para>
/// If model binding was successful, the <see cref="ModelBindingContext.Result"/> should have
/// <see cref="ModelBindingResult.IsModelSet"/> set to <c>true</c>.
/// </para>
/// <para>
/// A model binder that completes successfully should set <see cref="ModelBindingContext.Result"/> to
/// a value returned from <see cref="ModelBindingResult.Success"/>.
/// </para>
/// </returns>
Task BindModelAsync(ModelBindingContext bindingContext);
}
register your binder:
services.AddMvc().Services.Configure<MvcOptions>(options => {
options.ModelBinders.Insert(0, new YourCustomModelBinder());
});
MVC github repo and "Custom Model Binding" article may help:
Upvotes: 1
Reputation: 2293
The PUT verb requires the whole entity, but you can send an HTTP PATCH with a delta. There is little official documentation on exactly HOW this is done, but I did find this link that lays out how to accomplish this with a JSONPatchDocument that does basically what your intercepting class does.
Upvotes: 0