ComplexTypeModelBinder has unfortunately been deprecated in .Net 5.0, and it's counterpart, ComplexObjectModelBinder, is sealed, so you can't extend from it.
But, you can work around that. ComplexObjectModelBinderProvider is public, and you can use that to create a ComplexObjectModelBinder
. Thus, if you make your own custom IModelBinderProvider, you can have the constructor accept a ComplexObjectModelBinderProvider
argument, and make use of that to make a ComplexObjectModelBinder
. Then, you can pass that to your custom IModelBinder, where it'll happily do its custom work before falling back to the ComplexObjectModelBinder
you supplied.
Here's an example. First, your IModelBinder
. This example shows that you can use DI if you want to. (In this example, say we needed a DbContext.)
public class MyCustomModelBinder : IModelBinder
{
private readonly IModelBinder _defaultBinder;
private readonly DbContext _dbContext;
public MyCustomModelBinder(IModelBinder defaultBinder, DbContext dbContext)
{
_defaultBinder = defaultBinder;
_dbContext = dbContext;
}
public override Task BindModelAsync(ModelBindingContext bindingContext)
{
// -do custom work here-
return _defaultBinder.BindModelAsync(bindingContext);
}
}
However, in order to use DI on your custom model binder, you'll need a helper class. The problem is, when IModelBinderProvider
is called, it won't have access to all the services in a typical request, like your DbContext for example. But this class will help:
internal class DIModelBinder : IModelBinder
{
private readonly IModelBinder _rootBinder;
private readonly ObjectFactory _factory;
public DIModelBinder(Type binderType, IModelBinder rootBinder)
{
if (!typeof(IModelBinder).IsAssignableFrom(binderType))
{
throw new ArgumentException($"Your binderType must derive from IModelBinder.");
}
_factory = ActivatorUtilities.CreateFactory(binderType, new[] { typeof(IModelBinder) });
_rootBinder = rootBinder;
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var requestServices = bindingContext.HttpContext.RequestServices;
var binder = (IModelBinder)_factory(requestServices, new[] { _rootBinder });
return binder.BindModelAsync(bindingContext);
}
}
Now you're ready to write the custom IModelBinderProvider
:
public class MyCustomModelBinderProvider : IModelBinderProvider
{
private readonly IModelBinderProvider _rootProvider;
public MyCustomModelBinderProvider(IModelBinderProvider rootProvider)
{
_rootProvider = rootProvider;
}
public IModelBinder? GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType == typeof(MyModel))
{
var rootBinder = _rootProvider.GetBinder(context)
?? throw new InvalidOperationException($"Root {_rootProvider.GetType()} did not provide an IModelBinder for MyModel.");
return new DIModelBinder(typeof(MyCustomModelBinder), rootBinder);
}
return null;
}
}
Finally, in your startup file where you configure services, you can grab the ComplexObjectModelBinderProvider
instance, use that to create a new instance of your MyCustomModelBinderProvider
, and insert that into the ModelBinderProviders
.
services.AddMvc(options =>
{
var fallbackProvider = options.ModelBinderProviders
.First(p => p is ComplexObjectModelBinderProvider);
var myProvider = new MyCustomModelBinderProvider(fallbackProvider);
options.ModelBinderProviders.Insert(0, myProvider);
})