✅ Binding Nested values in RequestBody to Model

Consider the following model I use inside the MVC structure:
public class Collection
{
/// <summary>
/// The UUID of this Collection
/// </summary>
public required string Id { get; set; }
/// <summary>
/// The external Url representing the entries of this collection
/// </summary>
public required string Url { get; set; }
/// <summary>
/// The date the Collection was created at.
/// </summary>
public required DateOnly CreatedAt { get; set; }
}
public class Collection
{
/// <summary>
/// The UUID of this Collection
/// </summary>
public required string Id { get; set; }
/// <summary>
/// The external Url representing the entries of this collection
/// </summary>
public required string Url { get; set; }
/// <summary>
/// The date the Collection was created at.
/// </summary>
public required DateOnly CreatedAt { get; set; }
}
However, when responding to requests, this is instead formatted as application/hal+json, which would look like this:
{
"_links": {
"self": {
"href": "string"
},
"external": {
"href": "string"
}
},
"createdAt": "2024-11-18"
}
{
"_links": {
"self": {
"href": "string"
},
"external": {
"href": "string"
}
},
"createdAt": "2024-11-18"
}
As you can see, the property Url has been moved into _links:external:href. Now I would like to receive a Collection in a [HttpPut] request, but I'm having trouble figuring out how to properly parse the incoming application/hal+json to my Model. I've read online that Model Binding is probably the solution to this, but the vast majority of examples and explanations I could find were centered around Routes / Query parameters, not RequestBody formatting. The part I did find mentioned ValueProviders, but I couldn't find out how to obtain the JSON data from the body and store it as keys there. Any suggestions are appreciated, as I'm rather new to MVC architecture and C# in general.
1 Reply
Luca | LeCarbonator
It looks like Model Binding is the way to go, but getting the request body's text is more difficult than I thought it enforces the read to be async, but IModelBuilder throws an error even though it's async
'CollectionBinder' does not implement interface member 'IModelBinder.BindModelAsync(ModelBindingContext)'. 'CollectionBinder.BindModelAsync(ModelBindingContext)' cannot implement 'IModelBinder.BindModelAsync(ModelBindingContext)' because it does not have the matching return type of 'Task'.
'CollectionBinder' does not implement interface member 'IModelBinder.BindModelAsync(ModelBindingContext)'. 'CollectionBinder.BindModelAsync(ModelBindingContext)' cannot implement 'IModelBinder.BindModelAsync(ModelBindingContext)' because it does not have the matching return type of 'Task'.
Is there another way to obtain it other than bindingContext.HttpContext.Request.BodyReader? I seem to have find a way to do it in the model binder:
public class FooBinder : IModelBinder
{
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext.ModelType != typeof(Foo))
{
bindingContext.Result = ModelBindingResult.Failed();
return;
}

var readResult = await bindingContext.HttpContext.Request.BodyReader.ReadAsync();
var content = Encoding.UTF8.GetString(readResult.Buffer);

if (content == null)
{
bindingContext.Result = ModelBindingResult.Failed();
return;
}

var jo = JObject.Parse(content);

// Set all properties with a [JsonProperty] attribute attached
Foo? output = jo.ToObject<Foo>();

if (output == null)
{
bindingContext.Result = ModelBindingResult.Failed();
return;
}

output.BarProp = (string)jo.SelectToken("some.nested.property.from.json")!;

bindingContext.Result = ModelBindingResult.Success(output);
return;
}
}
public class FooBinder : IModelBinder
{
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext.ModelType != typeof(Foo))
{
bindingContext.Result = ModelBindingResult.Failed();
return;
}

var readResult = await bindingContext.HttpContext.Request.BodyReader.ReadAsync();
var content = Encoding.UTF8.GetString(readResult.Buffer);

if (content == null)
{
bindingContext.Result = ModelBindingResult.Failed();
return;
}

var jo = JObject.Parse(content);

// Set all properties with a [JsonProperty] attribute attached
Foo? output = jo.ToObject<Foo>();

if (output == null)
{
bindingContext.Result = ModelBindingResult.Failed();
return;
}

output.BarProp = (string)jo.SelectToken("some.nested.property.from.json")!;

bindingContext.Result = ModelBindingResult.Success(output);
return;
}
}
Want results from more Discord servers?
Add your server