refit icon indicating copy to clipboard operation
refit copied to clipboard

Complex object list in Query params object doesn't get put into query string format properly

Open gdodd1977 opened this issue 3 years ago • 16 comments

I've got the following QueryParams object:

`public class GetLocationsQueryParams { public GetLocationsQueryParams() { LocationDeviceData = new List<LocationDeviceData>(); }

    public BasicAddress Address { get; set; }

    public int Distance { get; set; }

    public DistanceType DistanceType { get; set; }

    public List<LocationDeviceData> LocationDeviceData { get; set; }

    public int Limit { get; set; }
}`

LocationDeviceData is as follows:

public class LocationDeviceData { public string ServiceProvider { get; set; } public string VendorDeviceId { get; set; } public string VendorRepairId { get; set; } }

When I pass this into a Get call, the query string generated is as follows:

repair/locations?Address.AddressType=None&Address.Line1=1407 Fleet St.&Address.Line2=&Address.Region=MD&Address.City=Baltimore&Address.Country=US&Address.PostalCode=21231&Distance=30&DistanceType=Miles&LocationDeviceData=LocationDeviceData&Limit=20&ProgramId=a4e24585-a63c-4597-be12-a6cfdafa15ee&FulfillmentOption=WalkInRepair

Is refit unable to parse query string objects like this into a proper query string?

gdodd1977 avatar Mar 05 '21 20:03 gdodd1977

@clairernovotny can anyone follow this issue? we have the same issue.

HosseinSalmanian avatar Jan 04 '22 16:01 HosseinSalmanian

I'm not sure what you would expect to see here for this? Query strings are ultimately key/value pairs, not rich complex data. Those are better done in a body with a format like json, xml, or yaml.

clairernovotny avatar Jan 04 '22 17:01 clairernovotny

You can't pass a body to a GET.

gdodd12 avatar Jan 04 '22 17:01 gdodd12

The question remains, what does the query expect?

clairernovotny avatar Jan 04 '22 17:01 clairernovotny

@clairernovotny complex type can serialize to query string with dot as property separator and serialization format for collections (and collection of complex types)can be done with square bracket and index.

    public class ListOption
    {
        public List<FilterModel> Filter { get; set; } = new List<FilterModel>();
        public List<SortModel> Sort { get; set; } = new List<SortModel>();
        public int Page { get; set; }
        public int Limit { get; set; }
    }
    
    public class FilterModel
    {
        public FilterModel(string property, string value)
        {
            Property = property;
            Value = value;
        }
        public string Property { get;}
        public string Value { get;}
    }


    public class SortModel
    {
        public SortModel(string property,string direction)
        {
            Property = property;
            Direction = direction;
        }
        public string Property { get;  }
        public string Direction { get;}
    }
    

query string for "ListOption" model can be as like below. Page=25&Limit=0&Sort[0].Direction=asc&Sort[0].Property=entryDate&Sort[1].Direction=desc&Sort[1].Property=Age

HosseinSalmanian avatar Jan 05 '22 06:01 HosseinSalmanian

we need to bind our model from query in .netCore API https://docs.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-6.0#dictionaries

Learn how model binding in ASP.NET Core works and how to customize its behavior.

HosseinSalmanian avatar Jan 05 '22 06:01 HosseinSalmanian

Something like Sort[1] is a very specific representation of a property; it's not something that can be generalized between REST backends/clients so if you need that, I'd recommend using a custom formatter.

clairernovotny avatar Jan 05 '22 14:01 clairernovotny

Can you provide me with an example or any documentation of a custom formatter?

HosseinSalmanian avatar Jan 07 '22 10:01 HosseinSalmanian

@clairernovotny I'm not sure I follow your last answer re: Sort[1]. @HosseinSalmanian is asking for Refit to model bind for a .NET Core API. .NET binding has used square bracket notation for arrays and dictionaries since its inception. If I understand you correctly, you're saying Sort[1] is a non-standard format? What I don't understand is how can a .NET library not support the default .NET format and say it's nonstandard and won't be implemented?

Regardless, I'm in the same boat and I'd like to implement it. I have a third-party API that takes in a filter dictionary in the querystring in the following format: ?filter[email][email protected]&filter[company]=github

My data type is a Params class with a Dictionary<string, object> filters { get; set; } That's fine and all, except I have no clue how to format the dictionary with bracket notation instead of dot notation.

Looking at overriding DefaultUrlParameterFormatter doesn't seem to help, I don't think. From what I can tell it doesn't distinguish between the key and the value, and by the time this gets called, it's already on the string key of my dictionary and not on the dictionary itself.

Soooooo yea... documentation or an example of a custom formatter would be helpful :) FWIW I looked at the test cases that override/implement DefaultUrlParameterFormatter and it wasn't helpful for the above case.

grexican avatar Jan 19 '22 00:01 grexican

Refit supports REST API's all up and does not make .NET-specific assumptions about formatting. It is bad idea to put a platform/language-specific feature in a URL/query as leaks implementation details into a public surface area.

That said, it should be possible for a custom formatter implementation to generate what's needed and if that extension point is missing, then we can take a PR to add it.

clairernovotny avatar Jan 19 '22 15:01 clairernovotny

I'm not suggesting to put platform-specific assumptions in. But I am suggesting that, if a .NET platform supposes to support all APIs it should at least support the APIs of said platform.

I'd hate to see something hardcoded, but I'd also expect to see some formatting options built in to support an API built on the same platform.

Apologies if my explanation is confusing.

I think it could be solved by making the UrlParamterFormatter aware of if it's formatting the key or the value. Right now it says "I have something to format... how do I format it?" And instead of should say "I have a key to format? I'll do it like this; I have a value to format? I'll do it like that."

Having not yet contributed to Refit, I'm not sure if I've understood the architecture. If what I'm saying makes sense, @clairernovotny, then I'd be happy to try my hand at a PR for it. My gut says it would be some kind of breaking change, though, as I expect the underlying interface would need to change, or be broken out into two parts (Key formatter vs Value formatter).

grexican avatar Jan 20 '22 21:01 grexican

@grexican @HosseinSalmanian Hi, I have the same issue, could you share your solution here, please? If it exists, of course

I think it can be useful for many refit users

kruzzze avatar May 27 '22 15:05 kruzzze

@kruzzze no I never did find a solution. I ended up circumventing the issue by declaring all of the individual filters I needed and specifying the querystring param, e.g. a DTO with { Key1 { get; set; } { Key2 { get; set; } } and decorating each param with the url param "filter[Key1]" and "filter[Key2]" for example. My needs were limited to a few keys, so I could get away with it.

grexican avatar May 28 '22 17:05 grexican

Hi there, were any customizable formatter been added? I also would like to know how such kind of Query params customization can be achieved?

TimonSP avatar Sep 15 '22 10:09 TimonSP

I have a similar problem. Is this issue resolved?

karthikchintala1 avatar Aug 18 '23 05:08 karthikchintala1