0

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.

  • Is there any article you are following? – Qiang Fu Jun 27 '23 at 14:03
  • https://learn.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-model-binding?view=aspnetcore-7.0 It does not explain how to bind to a property of an object, just mentions it can be done – Sora Teichman Jun 28 '23 at 09:01

2 Answers2

1

Please check this document related

When [FromBody] is applied to a complex type parameter, any binding source attributes applied to its properties are ignored

In my opinion , binding data from request body is a process that deserialize jsonstring to object which is different form binding data from other source that could be implemented with C# reflection,you should create the binder for WarehouseDTO instead of CustomString.Deserialize request body to an object and add your codes for your requirement

And modify your provider:

if (context.Metadata.ModelType == typeof(WarehouseDTO))
    {
        return new CustomStringModelBinder();
    }
Ruikai Feng
  • 6,823
  • 1
  • 2
  • 11
0

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.