Search code examples
xamarin.formstimeouthttprequest

Xamarin Http Request Timeout Issue


I have a mobile application based on Xamarin and a Web API based on .Net Core. Mobile app consumes methods of Web API via HttpClient. The code below is my base method to call any Web API method and the point is I want to set a timeout but could not achieved to set the exact timeout value whatever I have implemented. Tried Timespan.FromSeconds() or TimeSpan.FromMilliseconds() etc. When client makes a request to Web API, a loader is displayed to lock UI and removed after API response. Some clients gave me a feedback that the loader is displayed forever and request never ends. Maybe, the server is unreachable in this particular time or internet connection is broken for client etc. All I want to set a timeout and break the request and display an alert message to client. Of course, I googled and tried too much as mentioned but no result. If anyone can help me, will be appreciated.

    public async Task<BaseResponseModel> Post(BasePostModel postModel)
        {
            var responseModel = new BaseResponseModel();
            var json = postModel.ToString();
            var jsonParam = new StringContent(json, Encoding.UTF8, "application/json");
            var isPosted = true;
            var clientHandler = new HttpClientHandler()
            {
                AllowAutoRedirect = true,
                
            };

            var url = GetURL(postModel.UrlKey);

            var settings = new JsonSerializerSettings
            {
                NullValueHandling = NullValueHandling.Ignore,
                MissingMemberHandling = MissingMemberHandling.Ignore,
                ContractResolver = new DefaultContractResolver(),
                ReferenceLoopHandling = ReferenceLoopHandling.Ignore
            };

            var client = new HttpClient(clientHandler);
            //client.Timeout = TimeSpan.FromSeconds(10);
            //var cancellationTokenSource = new CancellationTokenSource();
            //cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(10));

            client.DefaultRequestHeaders.Add("Accept", "application/json");
            client.DefaultRequestHeaders.Add("X-Env", "MOBILE_API");
            AttachToken(ref client, responseModel.Id);

            try
            {
                if (Preferences.ContainsKey("UserJwtExprieDate"))
                {
                    var expiryDate = Preferences.Get("UserJwtExprieDate", null);
                    if (DateTime.Now > DateTime.Parse(expiryDate))
                    {
                        Preferences.Remove("UserJwtExprieDate");
                        Preferences.Remove("HomePageInformation");
                        int index = Xamarin.Forms.Application.Current.MainPage.Navigation.NavigationStack.Count - 1;
                        Page currPage = Xamarin.Forms.Application.Current.MainPage.Navigation.NavigationStack[index];
                        if (currPage as SigninForFactorOne != null)
                        {}
                        else
                        {
                            App.LogoutUser();
                        }    
                    }
                    else
                    {
                        var response = await client.PostAsync(url, jsonParam);
                        if (response.IsSuccessStatusCode)
                        {
                            string result = response.Content.ReadAsStringAsync().Result;
                            var resultModel = JsonConvert.DeserializeObject<BaseResponseModel>(result, settings);
                            if (resultModel.ErrorType == APIErrorTypes.NULL)
                            {
                                if (resultModel.IsSucceed)
                                {
                                    responseModel.Data = resultModel.Data;
                                }
                                else
                                {
                                    responseModel.Error = resultModel.Error;
                                }

                                responseModel.Message = resultModel.Message;
                            }
                            else
                            {
                                responseModel.Error = "Token Expried Date.";
                                Preferences.Remove("UserJwtExprieDate");
                                Preferences.Remove("HomePageInformation");
                                App.LogoutUser();
                            }
                        }
                        else
                        {
                            new AppException(new Exception("HTTP Client response is not succeed!"), responseModel.Id);
                            isPosted = false;
                        }
                    }
                }
                else
                {
                    var response = await client.PostAsync(url, jsonParam);
                    if (response.IsSuccessStatusCode)
                    {
                        string result = response.Content.ReadAsStringAsync().Result;
                        var resultModel = JsonConvert.DeserializeObject<BaseResponseModel>(result, settings);
                        if (resultModel.ErrorType == APIErrorTypes.NULL)
                        {
                            if (resultModel.IsSucceed)
                            {
                                responseModel.Data = resultModel.Data;
                            }
                            else
                            {
                                responseModel.Error = resultModel.Error;
                            }

                            responseModel.Message = resultModel.Message;
                        }
                        else
                        {
                            responseModel.Error = "Token Expried Date.";
                            Preferences.Remove("UserJwtExprieDate");
                            Preferences.Remove("HomePageInformation");
                            App.LogoutUser();
                        }
                    }
                    else
                    {
                        new AppException(new Exception("HTTP Client response is not succeed!"), responseModel.Id);
                        isPosted = false;
                    }
                }
               
            }
            catch (Exception ex)
            {
                new AppException(ex, responseModel.Id, 500, "anonymous.user", "Unable to post data to API!");
                isPosted = false;
            }
            finally
            {
                if (!isPosted)
                {
                    responseModel.Error = AppConfiguration.GetSystemMessage(contactYourSystemAdministratorMessage);
                    responseModel.Message = AppConfiguration.GetSystemMessage(contactYourSystemAdministratorMessage);
                }
            }

            return responseModel;
        }

Solution

  • I've used the solution below to manually set a time-out which works fine.

    internal class TimeOutHandler : DelegatingHandler
    {
        private readonly TimeSpan TimeOut;
    
        public TimeOutHandler(TimeSpan timeOut) => TimeOut = timeOut;
    
        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage req, CancellationToken ct)
        {
            using (var ctTimeOut = CancellationTokenSource.CreateLinkedTokenSource(ct))
            {
                ctTimeOut.CancelAfter(TimeOut);
    
                try
                {
                    return await base.SendAsync(req, ctTimeOut.Token);
                }
                catch (OperationCanceledException) when (!ct.IsCancellationRequested)
                {
                    throw new TimeoutException();
                }
            }
        }
    }
    

    How to use

    var interval = TimeSpan.FromSeconds(10);
    var handler = new TimeOutHandler(interval)
    {
        InnerHandler = new HttpClientHandler()
    };
    
    var client = new HttpClient(handler);
    

    For more information, check out: https://thomaslevesque.com/2018/02/25/better-timeout-handling-with-httpclient/