Search code examples
c#model-bindingtypeconverterasp.net-core-5.0jsonconverter

Which approach to use for model binding a custom type


My API controller has an endpoint that looks like this:

public async Task<IActionResult> Update([FromBody] UpdateCommand command) { /* ... */ }

That command looks like this:

public class UpdateCommand {
  public string Username { get; set; }
  public Id Id { get; set; }               // <--- here's the problem
}

That Id is a value object that sorta looks like this:

public class Id : SimpleValueObject<long> {
  // base class has: IComparable, equality, GetHashCode, etc.
  public Id(long value) : base(value) { }
  public Id(string value) : base(Parse(value)) { }
  private static long Parse(string value) { /* ... */ }
}

The client would send this:

{
  "username": "foo",
  "id": 1
}

Now I want model binding to automagically work. But I'm confused how to do that.

I implemented a IModelBinder and IModelBinderProvider, but that didn't help. Then I noticed the docs say this:

Typically shouldn't be used to convert a string into a custom type, a TypeConverter is usually a better option.

So I implemented a TypeConverter, and that also didn't help.

Then I thought to implement a JsonConverter<T>, but the framework now uses something other than Newtonsoft, so I didn't get far.

So my question is: what must I do to facilitate automatic binding for my custom type. I only need to know which path to pursue, I'll figure out the rest.

(As a side issue: please help me understand when I should implement a model binder vs type converter vs json converter.)


Solution

  • I still don't understand when to use a custom model binder vs custom type converter vs custom json converter.

    But it seems like the solution for this scenario is a custom JsonConverter<T>.

    This works for me:

    public class IdConverter : JsonConverter<Id> {
    
      public override Id? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
        if (reader.TokenType is not JsonTokenType.String and not JsonTokenType.Number)
          throw new JsonException();
        try {
          if (reader.TokenType is JsonTokenType.String) {
            var value = reader.GetString();
            return new Id(value);
          }
          else {
            var value = reader.GetInt64();
            return new Id(value);
          }
        }
        catch {
          throw new JsonException();
        }
      }
    
      public override void Write(Utf8JsonWriter writer, Id value, JsonSerializerOptions options) =>
        writer.WriteNumberValue(value.Value);
    
    }