1

Closed

WebAPI: DefaultModelBinder has different behavior GET/POST

description

Hi,

As per this forum thread: http://forums.asp.net/t/1795976.aspx/1?How+to+bind+this+querystring+to+parameters+on+an+API+Controller+Action+GET+

There seems to be a difference in behavior when binding complex models between GET and POST.

In a POST where the query is passed in the body of the message, the DefaultModelBinder will work fine when binding the following data:

filter[logic]=and&filter[filters][0][field]=FolderID&filter[filters][0][operator]=eq&filter[filters][0][value]=2

Which gets bound to this server side model:

public class GridFilter
{ 
    public string Operator { get; set; } 
    public string Field { get; set; } 
    public string Value { get; set; } 
} 

public class GridFilters 
{ 
    public List<GridFilter> Filters { get; set; } 
    public string Logic { get; set; } 
} 
//Action signature:
[System.Web.Mvc.HttpPost]
public IQueryable<Building> Get(WebApi1.Models.GridFilters filter = null)
{...}


On the other hand, when changing the ApiController to handle a GET where the query is passed in the querystring, then the DefaultModelBinder will pass null into the controller action (of course I did change my client code to do a GET as well).

I tried to nail down the problem by implementing a Custom Model Binder, and within the Bind method, I can see that when using POST, the data is passed in the NameValueCollectionValueProvider of the CompositeValueProvider, but when using a GET, the data is in the QueryStringValueProvider.

What I also noted is that if I call GetKeysFromPrefix("") on the CompositeValueProvider while doing GET, since there is already an empty key in the NameValueCollectionValueProvider, I get returned that one instead of the one in QueryStringValueProvider. This seems to be related to this code in CompositeValueProvider:

public virtual ValueProviderResult GetValue(string key)
    {
        return (from provider in this
                let result = provider.GetValue(key)
                where result != null
                select result).FirstOrDefault();
    }

    public virtual IDictionary<string, string> GetKeysFromPrefix(string prefix)
    {
        return (from provider in this
                let result = GetKeysFromPrefixFromProvider(provider, prefix)
                where result != null && result.Any()
                select result).FirstOrDefault() ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
    }
Since the NameValueCollectionValueProvider is at index 1 and QueryStringValueProvider is at index 2, using FirstOrDefault() will pick the one in NameValueCollectionValueProvider everytime.

I hope I'm not too way off track though :-)

Somehow, it seems that between the two use case there is something that doesn't work as expected, but I can't pinpoint further. I hope this will help you identify the source of the observed behavior.

Thank you.
Closed Jul 19, 2012 at 12:16 AM by DRavva
These issues are fixed a while back.

comments

mrlucmorin wrote Apr 25, 2012 at 1:26 AM

Edited mistake

mrlucmorin wrote Apr 30, 2012 at 10:50 PM

Hi,

I see this issue has been flagged as fixed, and I'd like to know if it's possible to find in which commit an issue has been fixed with the Codeplex tools.

Thanks

BradWilson wrote May 1, 2012 at 1:40 AM

It's spread out across several commits, as we reworked how formatters and model binders were integrated into the system between Beta and RC.

If you take a recent nightly build, you should be able to test whether things are now working for you:

http://blogs.msdn.com/b/henrikn/archive/2012/04/29/using-nightly-nuget-packages-with-asp-net-web-stack.aspx