I need to normalized the string data (replace some characters with each other like: 'ی' with 'ي' or trim it). To do so, I have created the following model binder like the following:
public class StringModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult == ValueProviderResult.None)
return Task.CompletedTask;
var value = Normalize(valueProviderResult.FirstValue);
bindingContext.Result = ModelBindingResult.Success(value);
return Task.CompletedTask;
}
}
This binder works for both Query
and Route
binding but fails if I use FromBody
attribute. It fails because BindModelAsync
never gets called. I found another question raised for this issue here and sadly does not have an answer.
I tried to extend the ComplexObjectModelBinder
but it is a sealed
class (and also does not provide any constructor). So I tried to extend ComplexTypeModelBinder
which is annotated as obsoleted.
I have copied the logic from ComplexTypeModelBinderProvider
from the source code and to my surprise, the BindModelAsync
of my StringModelBinder
receives calls now. But still fails because of the bindingContext.ValueProvider
contains only a provider for route and the result remains null.
My binder provider at this stage:
public class MyModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType)
{
var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();
for (var i = 0; i < context.Metadata.Properties.Count; i++)
{
var property = context.Metadata.Properties[i];
propertyBinders.Add(property, context.CreateBinder(property));
}
var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();
return new ComplexTypeModelBinder(
propertyBinders,
loggerFactory,
allowValidatingTopLevelNodes: true);
}
if (context.Metadata.ModelType == typeof(string))
{
return new StringModelBinder();
}
return null;
}
}
I also tried to create a provider from the body and changed my StringModelBinder
to:
public class StringModelBinder : IModelBinder
{
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult == ValueProviderResult.None)
{
var context = new ValueProviderFactoryContext(bindingContext.ActionContext);
await new FormValueProviderFactory().CreateValueProviderAsync(context);
valueProviderResult = context.ValueProviders
.Select(x => x.GetValue(bindingContext.ModelName))
.FirstOrDefault(x => x != ValueProviderResult.None);
if (valueProviderResult == ValueProviderResult.None) return;
}
var value = valueProviderResult.FirstValue.Replace("A", "B");
bindingContext.Result = ModelBindingResult.Success(value);
}
}
To whom may concern: This question may seem duplication but I could not find anything related to .Net 5 and if there is a question answering to the ComplexTypeModelBinder
issue, it won't be suitable for .Net 5 as it is obsoleted.
FromBody
is different from the FromQuery
and other HTTP verbs.
In the complex model binding (FromBody), you can get them in bindingContext.HttpContext.Request.Body
.
public class StringModelBinder : IModelBinder
{
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
using (var reader = new StreamReader(bindingContext.HttpContext.Request.Body))
{
var body = reader.ReadToEndAsync();
var mydata = body.Result;
//...
bindingContext.Result = ModelBindingResult.Success(mydata);
}
//...
}
}
action
[HttpPost]
public IActionResult test1([ModelBinder(binderType: typeof(StringModelBinder))]string model)
{
return Ok(model);
}
Then, pass a string into action.
Get it in StringModelBinder
.