How to replace the binding behavior of the Web API model, so that instead of Null I get a new instance when the parameters are not passed to

We have an API with many actions that take an object Filter. However, when someone calls the API method and does not pass any parameters, we get a null reference. To avoid the need to check this everywhere, we want to change the model binding behavior so that for this type it returns a new instance instead of a null one.

In addition, we really do not want to write our own binder for the filter type, since it can change often.

We found a mechanism by which we can write ModelBinderParameterBinding , but then I can’t figure out how to add this item to the WebAPI configuration.

So, have we tried the right approach, and if so, how do we tell WebAPI to use our new parameter binding?

For reference, here is ModelParameterBindingModel ... I'm definitely not sure if this is production code! Since I can not run or test it :)

public class QueryFilterModelBinderParameterBinding : ModelBinderParameterBinding
    {
        private readonly ValueProviderFactory[] _valueProviderFactories;
       private readonly IModelBinder _binder;

       public QueryFilterModelBinderParameterBinding(HttpParameterDescriptor descriptor,
           IModelBinder modelBinder,
           IEnumerable<ValueProviderFactory> valueProviderFactories)
           : base(descriptor, modelBinder, valueProviderFactories)
       {
           if (modelBinder == null)
           {
               throw new ArgumentNullException("modelBinder");
           }
           if (valueProviderFactories == null)
           {
               throw new ArgumentNullException("valueProviderFactories");
           }

           _binder = modelBinder;
           _valueProviderFactories = valueProviderFactories.ToArray();
       }

       public new IEnumerable<ValueProviderFactory> ValueProviderFactories
       {
           get { return _valueProviderFactories; }
       }

       public new IModelBinder Binder
       {
           get { return _binder; }
       }

       public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken)
       {
           var ctx = GetModelBindingContext(metadataProvider, actionContext);

           var haveResult = _binder.BindModel(actionContext, ctx);
           //here where we instantiate an empty filter if we cannot bind one
           var model = haveResult ? ctx.Model : new QueryFilter();
           SetValue(actionContext, model);

           return Task.FromResult(model);
       }

       private ModelBindingContext GetModelBindingContext(ModelMetadataProvider metadataProvider, HttpActionContext actionContext)
       {
           var name = Descriptor.ParameterName;
           var type = Descriptor.ParameterType;

           var prefix = Descriptor.Prefix;

           var vp = new CompositeValueProviderFactory(_valueProviderFactories).GetValueProvider(actionContext);

           var ctx = new ModelBindingContext()
           {
               ModelName = prefix ?? name,
               FallbackToEmptyPrefix = prefix == null, // only fall back if prefix not specified
               ModelMetadata = metadataProvider.GetMetadataForType(null, type),
               ModelState = actionContext.ModelState,
               ValueProvider = vp
           };

           return ctx;
       }
    }
+3
source share
1 answer

So, I managed to solve it a little differently. Although I'm still interested to know if there is a more idiomatic way to solve this problem!

First we create an HttpParameterBinding

public class QueryFilterParameterBinding : HttpParameterBinding
{
    private readonly HttpParameterBinding _modelBinding;
    //private readonly HttpParameterBinding _formatterBinding;

    public QueryFilterParameterBinding(HttpParameterDescriptor descriptor) : base(descriptor)
    {
        _modelBinding = new ModelBinderAttribute().GetBinding(descriptor);
        //_formatterBinding = new FromBodyAttribute().GetBinding(descriptor);
    }

    public override async Task ExecuteBindingAsync(
        ModelMetadataProvider metadataProvider, 
        HttpActionContext actionContext,
        CancellationToken cancellationToken)
    {
        await _modelBinding.ExecuteBindingAsync(metadataProvider, actionContext, cancellationToken);
        var queryFilter = GetValue(actionContext) as QueryFilter;
        if (queryFilter == null)
        {
            queryFilter = new QueryFilter();
            SetValue(actionContext, queryFilter);
        }
    }
}

and in the WebApiConfig class add a method

//returning null here tells another binding or the default binding to handle this request
private static HttpParameterBinding GetQueryFilterBinding(HttpParameterDescriptor descriptor)
{
    return descriptor.ParameterType == typeof (QueryFilter) 
        ? new QueryFilterParameterBinding(descriptor) 
        : null;
}

and in the WebApiConfig.Configure method we call:

config.ParameterBindingRules.Add(typeof(QueryFilter), GetQueryFilterBinding)
+3
source

All Articles