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.
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.
here is a very simply MVC asynchronous action:
[HttpPost]
public async Task<ContentResult> Trial()
{
ContentResult contentResult = await new Task<ContentResult>(()=>new ContentResult{Content="Hi"});
return contentResult;
}
I would expect a post to this action to respond with the content "Hi". But it does not. It responds with the content "System.Threading.Tasks.Task`1[System.Web.Mvc.ContentResult]".
Here is the full response:
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store
Pragma: no-cache
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Expires: -1
Vary: Accept-Encoding
X-UA-Compatible: IE=Edge
Date: Mon, 23 Nov 2020 17:02:37 GMT
Content-Length: 166
System.Threading.Tasks.Task`1[System.Web.Mvc.ContentResult]
Would love some insights into why, and how to get the content "Hi" as expected, using an Async function. Thanks.
This can happen if you're using a very old version of MVC. You must be running (at least) ASP.NET 4.5 on .NET Framework 4.5 for Task-returning action method to work. Microsoft.Bcl.Async does not work for ASP.NET projects.
If you have .NET Framework 4.5 or newer on your web servers, you can upgrade your project to ASP.NET 4.5. After upgrading, you must turn off "quirks mode" for async to work properly. I prefer to do this by setting httpRuntime.targetFramework in your web.config to 4.5 (or whatever version you upgrade to).
I upgraded a .net core 1 project to .net core 2. Everything is working great however my POST method won't return any data. There is no payload. I must be missing something simple.
The API does receive what I send it, so no issues there. But there is nothing in the response (using Chrome and IE dev tools), no matter what I try.
Here are the response headers:
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Content-Encoding: gzip
Server: Kestrel
Access-Control-Allow-Origin: *
X-SourceFiles: =?UTF-8?B?
For instance this returns no payload:
[HttpPost]
public IActionResult PostOrder([FromBody]OrderDto dto)
{
return new OkObjectResult(dto);
}
Caching policy was setting "Location" to none and "NoStore" to true. Removing this policy solved the issue.
I am attempting to use the Azure Rest API to update a Scheduled Job. I've successfully been able to get a list of the of the Jobs properties, so I know the authentication is working. I'm basing this on their example here:
https://msdn.microsoft.com/en-us/library/azure/dn528934.aspx
Here is what I see in Fiddler when preforming the PATCH request.
REQUEST:
PATCH https://management.azure.com/subscriptions/[[mysub]]/resourceGroups/CS-WebJobs-NorthCentralUS-scheduler/providers/Microsoft.Scheduler/jobCollections/WebJobs-NorthCentralUS/jobs/[[myjob]]?api-version=2016-01-01 HTTP/1.1
Authorization: Bearer
[[my token here]]
Content-Type: application/json; charset=utf-8
Host: management.azure.com
Content-Length: 20
Expect: 100-continue
{"state":"disabled"}
RESPONSE:
{"error":{"code":"BadRequest","message":"Malformed Job Object"}}
Based on their example the JSON passed should work.
Any idea what's going on here? I'm hoping its something simple.
Try the following payload:
{
"properties": {
"state": "disabled"
}
}
I'm responsible for updating a client-side API using WCF. This is because of changes to the API on the server (an outside company). When I get the response, my client-side throws an exception. Using Fiddler, I came up with the following problem: a duplicate Content-Type.
HTTP/1.1 200 OK
Date: Thu, 05 Jan 2012 21:15:16 GMT
Connection: close
Content-Type: text/xml; charset=utf-8
Content-Type: text/xml; charset=UTF-8
Content-Length: 538
...
Using Fiddler, I removed the extra Content-Type, and the client continued happily. So, I wrote an IClientMessageInspector, with the intention of capturing the response and removing the duplicate. And therein lies my issue. My IClientMessageInspector gets a call to BeforeSendRequest, but not one to AfterReceiveRequest.
Is there some place other than AfterReceiveRequest that I should be handling my task of removing the extra Content-Type? Am I barking up the wrong tree altogether?