'Why is Refit with custom handlers converting my GET requests into POST requests? (405 Method Not Allowed)

I have a Xamarin app in which I am using Refit to call my ASP.NET Core Web API backend (.NET 6). On the iOS side of things, I use a custom System.Net.Http.HttpClientHandler to add the api key and bearer token to the requests. On Android, I do something very similar, but the handler inherits from Xamarin.Android.Net.AndroidClientHandler. The code in each file is identical, line for line, except the base classes that each inherits from, which are the standard base handler classes that are typically prescribed for iOS and Android when using Xamarin.

One of the primary GET endpoints on my API takes a content body, which contains some potentially large filters. I realize that it's not standard to put a content body in a GET request, but I'm doing it because my query filters can be large and complex, and the filters are not suitable for placing in the query string of the request URL.

In the iOS app, these requests work just fine: the requests are issued with the GET verb, and the backend processes the request and inspects the body content successfully, and returns a successful 200 OK response.

On the Android side of things, I'm getting a 405 Method Not Allowed. The weird thing about this is that the debug output from my handler clearly shows that the request verb is GET, but the response from the backend indicates that a POST is being sent.

[7de9dcf3-79c2-4b76-a03e-f51af36b1a7d -   Request]======= Request Begin =======
[7de9dcf3-79c2-4b76-a03e-f51af36b1a7d -   Request] GET /api/v1/event/lite?pageIndex=1&pageSize=25 https/2.0
[7de9dcf3-79c2-4b76-a03e-f51af36b1a7d -   Request] Host: [omitted]
[7de9dcf3-79c2-4b76-a03e-f51af36b1a7d -   Request] apiKey: [omitted]
[7de9dcf3-79c2-4b76-a03e-f51af36b1a7d -   Request] Authorization: Bearer
[7de9dcf3-79c2-4b76-a03e-f51af36b1a7d -   Request] Content-Type: application/json; charset=utf-8
[7de9dcf3-79c2-4b76-a03e-f51af36b1a7d -   Request] Content:
[7de9dcf3-79c2-4b76-a03e-f51af36b1a7d -   Request] {"includeUnpublished":false,"includeOnlyOwnedItems":false,"includeDeleted":false,"startDate":"2022-03-04T00:00:00","duration":"1.00:00:00","isFree":null,"favoriteOption":2,"ageGroupIds":[],"eventIds":[],"venueIds":[],"partyIds":[],"tags":[]}...
[7de9dcf3-79c2-4b76-a03e-f51af36b1a7d -   Request] Duration: 00:00:01.1710510
[7de9dcf3-79c2-4b76-a03e-f51af36b1a7d -   Request]======= Request End =========

[7de9dcf3-79c2-4b76-a03e-f51af36b1a7d - Response]======= Response Start ======
[7de9dcf3-79c2-4b76-a03e-f51af36b1a7d - Response] HTTPS/1.1 405 Method Not Allowed
[7de9dcf3-79c2-4b76-a03e-f51af36b1a7d - Response] api-supported-versions: 1.0
[7de9dcf3-79c2-4b76-a03e-f51af36b1a7d - Response] Connection: keep-alive
[7de9dcf3-79c2-4b76-a03e-f51af36b1a7d - Response] Date: Fri, 04 Mar 2022 16:42:42 GMT
[7de9dcf3-79c2-4b76-a03e-f51af36b1a7d - Response] Request-Context: appId=cid-v1:8968a593-b085-4979-81da-5c5918483840
[7de9dcf3-79c2-4b76-a03e-f51af36b1a7d - Response] Server: Microsoft-IIS/10.0
[7de9dcf3-79c2-4b76-a03e-f51af36b1a7d - Response] X-Android-Received-Millis: 1646412160790
[7de9dcf3-79c2-4b76-a03e-f51af36b1a7d - Response] X-Android-Response-Source: NETWORK 405
[7de9dcf3-79c2-4b76-a03e-f51af36b1a7d - Response] X-Android-Selected-Protocol: http/1.1
[7de9dcf3-79c2-4b76-a03e-f51af36b1a7d - Response] X-Android-Sent-Millis: 1646412160706
[7de9dcf3-79c2-4b76-a03e-f51af36b1a7d - Response] X-Powered-By: ASP.NET
[7de9dcf3-79c2-4b76-a03e-f51af36b1a7d - Response] Allow: GET, DELETE
[7de9dcf3-79c2-4b76-a03e-f51af36b1a7d - Response] Content-Length: 225
[7de9dcf3-79c2-4b76-a03e-f51af36b1a7d - Response] Content-Type: application/json; charset=utf-8
[7de9dcf3-79c2-4b76-a03e-f51af36b1a7d - Response] Content:
[7de9dcf3-79c2-4b76-a03e-f51af36b1a7d - Response] {"error":{"code":"UnsupportedApiVersion","message":"The HTTP resource that matches the request URI '[omitted]' with API version '1' does not support HTTP method 'POST'.","innerError":null}}...
[7de9dcf3-79c2-4b76-a03e-f51af36b1a7d - Response] Duration: 00:00:00.0284520
[7de9dcf3-79c2-4b76-a03e-f51af36b1a7d - Response]======= Response End ========
EventApiClient.ReadLite failed: Response status code does not indicate success: 405 (Method Not Allowed). Retying in 0 ms...
Response status code does not indicate success: 405 (Method Not Allowed).
Error: Response status code does not indicate success: 405 (Method Not Allowed).

So, I fired up Charles Proxy and intercepted the request, just to make sure. And sure enough, the outbound request is a POST, even though I'm telling Refit to send a GET. It's as if Refit is seeing that I've specified body content, and is automatically (and silently) changing the verb of the request from GET to POST.

I have even manually composed the request in Postman, and I am able to successfully get back data with a 200 OK, just as I do in my iOS scenario.

Is there something specific to the AndroidClientHandler that mangles requests by changing the verb when a content body is present? Or is there something inside of Refit itself that causes this behavior?

UPDATE: After narrowing it down to being something within AndroidClientHandler, I opened an issue Xamarin.Android team here: https://github.com/xamarin/xamarin-android/issues/6813

UPDATE UPDATE: Both AndroidClientHandler and NSURLSessionHandler don't like when a body exists in a GET request. So, I simply decided to make my endpoints respond to the POST verb instead of GET. The entire impetus for this is that my query filters can be so large that they're not friendly for putting in a query string, which is typically where filter params go. So, I decided to pass the filters in the request body. That's where problems arose. So, now I'm just using POST for making my queries (because I control both the client and the server), even though that goes against convention. But I don't care...as long as the HTTP calls succeed, I'm not going to be a stickler for HTTP conventions.



Solution 1:[1]

Scratch all this. It appears that on the iOS side of things, NSUrlSessionHandler also does the same thing: it essentially doesn't allow a body in a GET request. I guess perhaps the most straightforward way for me to deal with my issue is to change my GET endpoint to a POST endpoint...even though that feels dirty because it violates the principles of what POST is intended for.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 NovaJoe