How do I replace the behaviour of Web API model bi

2019-05-10 09:20发布

We have an API with many actions which take a Filter object. However, when someone calls an API method and doesn't pass any parameters we end up with a null reference. To avoid having to check for this everywhere we want to alter the behaviour of the model binding so that for that type it returns a new instance instead of null.

Additionally, we don't really want to write our own binder for the filter type as it is likely to change frequently.

We found a mechanism whereby we can write a ModelBinderParameterBinding but then I can't figure out how to add that item to the WebAPI config.

So, have we tried the correct approach and if so how do we tell WebAPI to use our new Parameter Binding?

For reference here's the ModelParameterBindingModel... I'm definitely not sure this is production code! Since I've no way to 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's 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;
       }
    }

1条回答
叛逆
2楼-- · 2019-05-10 09:56

So, I've managed to solve this a slightly different way. Although I'm still interested to see if there is a more idiomatic way to solve this!

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)
查看更多
登录 后发表回答