Search code examples
c#asp.net-core-mvcmodelbinderscustom-model-binder

How do I add a custom ModelBinder to a custom type inside a C# class?


I have a WarehouseDTO class with a Name field of a custom type called CustomString. I'm trying to create a custom ModelBinder to bind the type when it is passed from the front end via the controller. My controller:

public async Task<IActionResult> Create([FromBody] WarehouseDTO warehouseDTO)

My ModelBinderProvider and BindModelAsync methods:

using Microsoft.AspNetCore.Mvc.ModelBinding;

public class CustomStringModelBinder : IModelBinder
{
 public Task BindModelAsync(ModelBindingContext bindingContext)
{
    if (bindingContext == null)
    {
        throw new ArgumentNullException(nameof(bindingContext));
    }

    if (bindingContext.BindingSource == BindingSource.Body)
    {
        var fieldName = bindingContext.FieldName;
        string val = bindingContext.ValueProvider.GetValue(fieldName).FirstValue;
        if (val == null)
        {
            val = bindingContext.ValueProvider.GetValue("Value").FirstValue;
        }

        bindingContext.Result = ModelBindingResult.Success(new CustomString(val));

    }

    return Task.CompletedTask;
 }
 }

 public class CustomStringBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
    if (context == null)
    {
        throw new ArgumentNullException(nameof(context));
    }

    if (context.Metadata.ModelType == typeof(CustomString))
    {
        return new CustomStringModelBinder();
    }

    return null;
}
}

But when I debug the BinderProvider the only ModelType that comes through is the WarehouseDTO. How can I apply the binder to a field inside the warehouseDTO from the request body? I have added the required line to my Startup.cs to inject the binder provider. Here is my WarehouseDTO:

public class WarehouseDTO
{
    public int Id { get; set; }
    [ModelBinder(typeof(CustomStringModelBinder))]
    public CustomString Name { get; set; }
    ...
}

I'm trying to create a CustomtString type with which I will use dependency injection to determine how to format the strings of this type, it may be uppercase, title case etc.


Solution

  • As mentioned in the answer above, I was missing the piece which deserializes the json. Here is my json converter:

    public class CustomStringJsonConverter : JsonConverter<CustomString>
    {
    
    public override CustomString Read(ref Utf8JsonReader reader, Type 
    typeToConvert, JsonSerializerOptions options)
    {
        var data = JsonSerializer.Deserialize<string>(ref reader, options);
        var result = new CustomString(data!);
        return result;
    }
    
    public override void Write(Utf8JsonWriter writer, CustomString value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.Value);
    }
    }
    

    And here's the line in Startup.cs that provides the converter via Dependency Injection:

    services.AddControllers(
                options =>
                {
                    options.ModelBinderProviders.Insert(1, new 
            OodleStringBinderProvider());
                }
            ).AddJsonOptions(options =>
            {
                options.JsonSerializerOptions.Converters.Add(new 
            OodleStringJsonConverter());
            });
    

    Since I also use SignalR I also has to provide the converter for my signalR service in Startup.