4

Hello all am trying to do a login to my xamarin api using RestSharp, the API ought to return status code 200 OK if the authentication works and status code 415 if the authentication fails(wrong password) and other codes depending on what the case scenario, but instead i get a status code 0 on all other case asides when the authentication pass(status code 200 ok), the source code below is how i implement

 //payload am sending to the api
   RequestPayload res = new RequestPayload();
   res.appid = appid;
   res.data = data;
   res.method = "Login";

   //convert to json object
   var MySerializedObject =  JsonConvert.SerializeObject(res);
   string APIUrl = ""http://142.168.20.15:8021/RouteTask";

   //create client
   RestClient client = new RestClient(APIUrl);

   //create request
   RestRequest request = new RestRequest(Method.POST);

   // set request headeer
   request.AddHeader("Content-Type", "application/x-www-form-urlencoded");

   //request.AddJsonBody(MySerializedObject); --i have also tried this

   request.AddParameter("application/json", MySerializedObject, ParameterType.RequestBody);
   request.JsonSerializer.ContentType = "application/json; charset=utf-8";
   request.AddParameter("RequestSource", "Web", "application/json", ParameterType.QueryString);
   client.Timeout = 2000000;
   var response =  client.Execute(request); // where the issue appears
   //RestResponse response =  client.Execute(request); // i have tried this
   //IRestResponse response =  client.Execute(request); // i have tried this
    if (response.IsSuccessful)
        {
         //use response data
        }

on all scenerio it comes back with a StatusCode: 0, Content-Type: , Content-Length: 0) and errorMessage

"Error getting response stream (ReadAsync): ReceiveFailure Value cannot be null. Parameter name: src"

screenshot below indicate when the api call fails enter image description here Response receieved when the authentication is valid Success Authentication

4
  • Here is a similar issue .May be you can refer tohere Commented Sep 20, 2018 at 8:18
  • MonkeySteve, i have seen that issue, however, solution doesn't work for me, my issue occurs if the server send any other response asides 200 OK Commented Sep 20, 2018 at 13:39
  • 1
    I am having a similar problem with 401 errors. If you turn on break for all exceptions, you can see there are several being thrown inside RestSharp but they aren't bubbling up and the status code ends up zero. In my case, the response coming back on the 401 is plain text "expired" instead of json, which leads me to believe it is something with the parser, but it never even hits the OnBeforeDeserialization method so that seems to rule out a parsing error. Commented Sep 26, 2018 at 20:06
  • starting with the initial 511 where the server indicates auth expired, and subsequent 401 indicating i have a bad key (because i didnt know the 511 came and needed to renew) i get this same error condition. But i do hit the OnBeforeDeserialization method and already the exceptions are thrown and all response data is lost, including the headers and status code... Commented Oct 23, 2018 at 17:11

2 Answers 2

4

I was finally able to find a workaround for this. Bear with the long-winded response.

The tags mention Xamarin, which is what I am working in as well - specifically with iOS. I think it may actually be a bug with Mono, but I didn't take it that far to confirm.

The problem lies with the default way of copying the response buffer. In the RestSharp code, this is done by an extension method in MiscExtensions.cs called ReadAsBytes. It appears that with certain response buffers, the call to the Stream.Read method is failing. When this happens, the exception causes RestSharp to "shortcut" the rest of the processing on the response, hence the status code never gets filled in since it happens after the call to ReadAsBytes.

The good news is RestSharp does give a way to replace this call to ReadAsBytes with one of your own. This is done via the ResponseWriter property on the IRestRequest object. If it has a function defined, it will bypass the ReadAsBytes call and call the function you gave it instead. The problem is, this is defined as an Action and you don't get a copy of the full response object, so it's somewhat useless. Instead you have to use the AdvancedResponseWriter property. This one includes both the response object and the response stream. But you still have to set the ResponseWriter property or it won't bypass the default handler and you'll still get the error.

Ok, so how do you make this work? I ended up implementing it as a wrapper to RestClient so I wouldn't have to implement the code all over the place. Here's the basic setup:

public class MyRestClient : RestClient
{
    public MyRestClient(string baseUrl) : base(baseUrl)
    { }

    public override IRestResponse Execute(IRestRequest request)
    {
        request.ResponseWriter = s => { };
        request.AdvancedResponseWriter = (input, response) => response.RawBytes = ReadAsBytes(input);

        return base.Execute(request);
    }

    private static byte[] ReadAsBytes(Stream input)
    {
        var buffer = new byte[16 * 1024];

        using (var ms = new MemoryStream())
        {
            int read;
            try
            {
                while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
                { ms.Write(buffer, 0, read); }

                return ms.ToArray();
            }
            catch (WebException ex)
            { return Encoding.UTF8.GetBytes(ex.Message); }
        };
    }
}

The ReadAsBytes method is actually just a copy/paste of the RestSharp ReadAsBytes method with the addition of a try/catch. If it fails, it returns the exception reason in to the response buffer. This may or may not be what you want, so modify as needed. You may also need to override other methods for Execute, but in my case this is the only one we're using so it was enough.

So far this seems to be doing the trick for me. Perhaps if someone got ambitious they could trace it all the way in to Mono to try and see what it doesn't like about the stream, but I don't have the time for it at the moment.

Good luck!

Sign up to request clarification or add additional context in comments.

4 Comments

Hello steve i have tried you solution, it does not work rather i get the error "Error getting response stream (ReadAsync): ReceiveFailure Value cannot be null. Parameter name: src" in the Content which is still not what am expecting.
It will still throw the error, but the catch will allow it to continue and finish populating the response object so you will at least get the status code back. Also found this. github.com/mono/mono/issues/9511 It looks like it might indeed be an issue with Mono.
I came across some additional scenarios that don't go through the ResponseWriter and still end up returning a zero status code, so apparently this isn't a complete solution. I haven't had time to trace through and see if I can figure out a workaround for those yet. One I saw it on was a "connection closed by peer" error.
hi Steve, i used an alternative .net http client to achieve the result, i have the implementation in the answer below, thank you for assisting
0

OK so after toying around with RestSharp for a bit, i realize just as @steve_In_Co mentioned earlier there were compatibility issues with MONO (we presume this is a bug) so i did it in a basic way using the .Net HTTP library and it works for me, so in case someone is still looking for a way out, find the working .net http implementation code below.

//payload am sending to the api 
   RequestPayload res = new RequestPayload();
   res.appid = appid;
   res.data = data;
   res.method = "Login";

   //convert to json object
   var MySerializedObject =  JsonConvert.SerializeObject(res);

   string APIUrl = ""http://142.168.20.15:8021/RouteTask";

   //create basic .net http client
   HttpClient client = new HttpClient();
   client.BaseAddress = new Uri(APIUrl);

   // this was required in the header of my request, 
   // you may not need this, or you may need to adjust parameter
   //("RequestSource","Web") or you own custom headers
   client.DefaultRequestHeaders.Add("RequestSource", "Web");
    // this class is custom, you can leave it out
   connectionService = new ConnectionService();
  //check for internet connection on users device before making the call
  if (connectionService.IsConnected)
    {
       //make the call to the api
        HttpResponseMessage response = await 
        client.PostAsJsonAsync(ApiConstants.APIDefault, res);
        if (response.IsSuccessStatusCode)
            {
                string o = response.Content.ReadAsStringAsync().Result;
                dynamic payload = JsonConvert.DeserializeObject(o);
                string msg = payload["valMessage"];
                resp.a = true;
                resp.msg = payload["responseDescription"];
            }
        else
            {
                string o = response.Content.ReadAsStringAsync().Result;
                dynamic payload = JsonConvert.DeserializeObject(o);
                resp.a = false;
                resp.msg = payload["response"];
            }
    }

3 Comments

I don't see how this works at all as you suggest in your original question the problem arises when the content is null (len = 0) and in your solution you deserialize the null and then proceed to use it. it would seem to me you tired to deserialize a null value and it failed appropriately. And your solution is even worse than the OP. please advise
hello axa, my interest lies in the content of response i.e (Response.Content) but with rest sharp if my login request fails and i get a status code different from 200 OK, Response.Content would be null (i would not get the original response the backend programmer intended as the cause of the failure ) but using plain .net httpClient i am able to read an appropriate response if my request fails for any reason (perhaps you meant to post this in Steve In CO's answer as your concern would apply there) mind you my solution works 100%
perhaps i just misunderstand as i see if NOT response.IsSuccessStuatusCode it reads content anyway.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.