Ali Faris
Ali Faris

Reputation: 18630

Custom model binding - ASP.NET Core

I have this incoming request with this payload

{
    count : 10,
    supplier : {
        id : 342,
        name : 'test'
    },
    title : 'some title'
}

and I have this model in my c# code

class SomeModel 
{
    public string Title { get; set; } 
    public int SupplierId { get; set; }
    public double Amount { get; set; }
}

This is my controller method

public IActionResult Foo(SomeModel data) 
{
    //...
}

I would like to map the property count in request payload to Amount property in my c# model and mapping the value of supplier.id into SupplierId.

I'm using Newtonsoft Json.NET library for binding

Upvotes: 0

Views: 96

Answers (1)

Alexander
Alexander

Reputation: 9642

Obviously the simplest way is to create a class corresponding to payload stucture like this

public class SomeModel
{
    public string Title { get; set; }

    public double Count { get; set; }

    public Supplier Supplier { get; set; }
}

public class Supplier
{
    public int Id { get; set; }

    public string Name { get; set; }
}

Another iteration could be using JsonProperty attribute for Amount and SupplierId property making use of Supplier

class SomeModel
{
    public string Title { get; set; }

    [JsonProperty("count")]
    public double Amount { get; set; }

    public int SupplierId => Supplier.Id;

    public Supplier Supplier { get; set; }
}

class Supplier
{
    public int Id { get; set; }

    public string Name { get; set; }
}

But if you like to stick with your current model you will need to create a custom converter. And what I can suggest you

public class NestedPropertyConverter : JsonConverter
{
    private string[] _properties;

    public NestedPropertyConverter(string propertyChain)
    {
        //todo: check if property chain has valid structure
        _properties = propertyChain.Split('.');
    }

    public override bool CanWrite => false;

    public override bool CanConvert(Type objectType) => true;

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JToken token = (JToken)serializer.Deserialize(reader);
        foreach (string property in _properties)
        {
            token = token[property];
            if (token == null) //if property doesn't exist
                return existingValue; //or throw exception
        }
        return token.ToObject(objectType);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

Basically, the converter allows to bind nested properties. Usage

class SomeModel
{
    public string Title { get; set; }

    [JsonConverter(typeof(NestedPropertyConverter), "id")]
    [JsonProperty("supplier")] //its necessary to specify top level property name
    public int SupplierId { get; set; }

    [JsonProperty("count")]
    public double Amount { get; set; }
}

Note

I also tested the following payload

{
    count : 10,
    supplier : {
        id : 342,
        name : "test",
        test: {
            val: 55
        }
    },
    title : "some title"
}

and config for property

[JsonConverter(typeof(NestedPropertyConverter), "test.val")]
[JsonProperty("supplier")]
public int SupplierId { get; set; }

and it works fine.

Upvotes: 2

Related Questions