Search code examples
c#json.net-corejson.netasp.net-core-webapi

JsonExtensionData attribute with default DotNetCore binder


I am trying to pass JSON with some dynamic fields to a controller action method in DotNetCore 3.1 Web API project. The class I am using when sending the payload looks something like this:

public class MyDynamicJsonPayload
{
    public string Id { get; set; }
    [JsonExtensionData]
    public IDictionary<string, object> CustomProps { get; set; }
}

I can see that object serialized correctly with props added to the body of JSON. So I send it from one service to another:

using var response = await _httpClient.PostAsync($"/createpayload", new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json"));

On the receiving end, however, when using same class in the controller action:

public async Task<ActionResult> CreatePayload([FromBody] MyDynamicJsonPayload payload)
{
    var payload = JsonConvert.SerializeObject(payload);

    return Ok(payload);
}

The object is parsed as something different where customProps is an actual field with JSON object containing my properties, plus instead of a simple value I get a JSON object {"valueKind":"string"} for string properties for example. I tried with both Newtonsoft.Json and System.Text.Json.Serialization nothing works as expected. Anyone has any ideas?


Solution

  • Thank you dbc for pointing me in the right direction, the problem was Newtownsoft vs System.Text.Json serialization/deserialization. I could not change the serializer in the Startup class because the service had many other methods and I didn't want to break existing contracts. However, I managed to write a custom model binder that did the trick:

    public class NewtonsoftModelBinder : IModelBinder
    {
        public async Task BindModelAsync(ModelBindingContext bindingContext)
        {
            if (bindingContext == null)
                throw new ArgumentNullException(nameof(bindingContext));
    
            string body = string.Empty;
            using (var reader = new StreamReader(bindingContext.HttpContext.Request.Body))
            {
                body = await reader.ReadToEndAsync();
            }
    
            var result = JsonConvert.DeserializeObject(body, bindingContext.ModelType);
    
            bindingContext.Result = ModelBindingResult.Success(result);
        }
    }
    

    And usage:

    public async Task<ActionResult> CreatePayload([ModelBinder(typeof(NewtonsoftModelBinder))] MyDynamicJsonPayload payload)
    {
    // Process payload...
        return Ok();
    }