/// <summary> /// Executes this request asynchronously. /// </summary> /// <returns> The async task with the type of the response. </returns> public virtual async Task <T> ExecuteAsync() { var response = await ExecuteUnparsedAsync().ConfigureAwait(false); if (OverrideRedirect) { string newLoc = string.Empty; foreach (var header in response.Headers) { if (header.Key.ToLower().Equals("location")) { newLoc = header.Value.FirstOrDefault(); break; } } //string newlocation = response.Headers.FirstOrDefault(stringToCheck => stringToCheck.ToString().Equals("Location")).ToString(); return(await onRedirectAsync(newLoc).ConfigureAwait(false)); } // special case to handle void or empty responses if (response.Content == null) { return(default(T)); } string path = response.RequestMessage.RequestUri.AbsolutePath; if (path != null && path.Contains(Constants.STR_PATH_CUSTOM_ENDPOINT) && (((int)response.StatusCode) < 200 || ((int)response.StatusCode) > 302)) { // Seems like only Custom Endpoint/BL would result in having a successful response // without having a successful status code. The BL executed successfully, but did // produce a successsful outcome. try { response.EnsureSuccessStatusCode(); } catch (Exception ex) { var ke = new KinveyException( EnumErrorCategory.ERROR_CUSTOM_ENDPOINT, EnumErrorCode.ERROR_CUSTOM_ENDPOINT_ERROR, response, ex ); throw ke; } } if (path != null && path.Contains(Constants.STR_PATH_REALTIME_STREAM) && (((int)response.StatusCode) < 200 || ((int)response.StatusCode) > 302)) { // Appears as though there is a stream error. A stream error could result in having a successful response // without having a successful status code, such as a 401. The request was successful, but the response // indicates that there is an issue with what was being requested try { response.EnsureSuccessStatusCode(); } catch (Exception ex) { var ke = new KinveyException( EnumErrorCategory.ERROR_REALTIME, EnumErrorCode.ERROR_REALTIME_ERROR, response, ex ); throw ke; } } if (((int)response.StatusCode) < 200 || ((int)response.StatusCode) > 302) { try { response.EnsureSuccessStatusCode(); } catch (Exception ex) { var kinveyException = new KinveyException( EnumErrorCategory.ERROR_BACKEND, EnumErrorCode.ERROR_JSON_RESPONSE, response, ex ) { RequestID = HelperMethods.getRequestID(response) }; throw kinveyException; } } string json = null; try { json = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var result = JsonConvert.DeserializeObject <T>(json); RequestStartTime = HelperMethods.GetRequestStartTime(response); return(result); } catch (JsonException ex) { KinveyException kinveyException = new KinveyException( EnumErrorCategory.ERROR_DATASTORE_NETWORK, EnumErrorCode.ERROR_JSON_PARSE, HelperMethods.GetCustomParsingJsonErrorMessage(json, response.RequestMessage.RequestUri.ToString(), typeof(T).FullName), null, ex ) { RequestID = HelperMethods.getRequestID(response) }; throw kinveyException; } catch (ArgumentException ex) { Logger.Log(ex.Message); return(default(T)); } catch (NullReferenceException ex) { Logger.Log(ex.Message); return(default(T)); } }
/// <summary> /// Operating with delta set data. /// </summary> /// <param name="cacheItems">Cache items.</param> /// <param name="networkItems">Network items.</param> /// <param name="mongoQuery">Mongo query.</param> /// <returns>The async task with the list of entities.</returns> protected async Task <List <T> > RetrieveDeltaSet(List <T> cacheItems, List <DeltaSetFetchInfo> networkItems, string mongoQuery) { List <T> listDeltaSetResults = new List <T>(); #region DSF Step 2: Pull all entity IDs and LMTs of a collection in local storage Dictionary <string, string> dictCachedEntities = new Dictionary <string, string>(); foreach (var cacheItem in cacheItems) { var item = cacheItem as IPersistable; if (item.Kmd?.lastModifiedTime != null) //if lmt doesn't exist for cache entity, avoid crashing { dictCachedEntities.Add(item.ID, item.Kmd.lastModifiedTime); } } List <string> listCachedEntitiesToRemove = new List <string>(dictCachedEntities.Keys); #endregion #region DSF Step 3: Compare backend and local entities to see what has been created, deleted and updated since the last fetch List <string> listIDsToFetch = new List <string>(); foreach (var networkEntity in networkItems) { string ID = networkEntity.ID; string LMT = networkEntity.KMD.lastModifiedTime; if (!dictCachedEntities.ContainsKey(ID)) { // Case where a new item exists in the backend, but not in the local cache listIDsToFetch.Add(ID); } else if (HelperMethods.IsDateMoreRecent(LMT, dictCachedEntities[ID])) { // Case where the backend has a more up-to-date version of the entity than the local cache listIDsToFetch.Add(ID); } // Case where the backend has deleted an item that has not been removed from local storage. // // To begin with, this list has all the IDs currently present in local storage. If an ID // has been found in the set of backend IDs, we will remove it from this list. What will // remain in this list are all the IDs that are currently in local storage that // are not present in the backend, and therefore have to be deleted from local storage. listCachedEntitiesToRemove.Remove(ID); // NO-OPS: Should never hit these cases, because a Push() has to happen prior to a pull // Case where a new item exists in the local cache, but not in the backend // Case where the local cache has a more up-to-date version of the entity than the backend // Case where the local cache has deleted an item that has not been removed from the backend } #endregion #region DSF Step 4: Remove items from local storage that are no longer in the backend Cache.DeleteByIDs(listCachedEntitiesToRemove); #endregion #region DSF Step 5: Fetch selected IDs from backend to update local storage // Then, with this set of IDs from the previous step, make a query to the // backend, to get full records for each ID that has changed since last fetch. int numIDs = listIDsToFetch.Count; if (numIDs == networkItems.Count) { //Special case where delta set is the same size as the network result. //This will occur either when all entities are new/updated, or in error cases such as missing lmts return(await RetrieveNetworkResults(mongoQuery).ConfigureAwait(false)); } int start = 0; int batchSize = 200; while (start < numIDs) { int count = Math.Min((numIDs - start), batchSize); string queryIDs = BuildIDsQuery(listIDsToFetch.GetRange(start, count)); List <T> listBatchResults = await Client.NetworkFactory.buildGetRequest <T>(Collection, queryIDs).ExecuteAsync().ConfigureAwait(false); start += listBatchResults.Count(); listDeltaSetResults.AddRange(listBatchResults); } #endregion return(listDeltaSetResults); }
/// <summary> /// Executes the request asynchronously without any parsing. /// </summary> /// <returns> The async task with Http response. </returns> public async Task <HttpResponseMessage> ExecuteUnparsedAsync() { var httClient = InitializeRestClient(); var request = BuildRestRequest(); RequestAuth.Authenticate(request); Logger.Log(request); var response = await httClient.SendAsync(request).ConfigureAwait(false); Logger.Log(response); var contentType = response.Headers .Where(x => x.Key.ToLower().Equals("content-type")) .Select(x => x.Value) .FirstOrDefault()? .FirstOrDefault(); if (contentType != null && !contentType.Contains("application/json")) { var kinveyException = new KinveyException( EnumErrorCategory.ERROR_REQUIREMENT, EnumErrorCode.ERROR_REQUIREMENT_CONTENT_TYPE_HEADER, contentType ) { RequestID = HelperMethods.getRequestID(response) }; throw kinveyException; } lastResponseCode = response.StatusCode; lastResponseMessage = response.StatusCode.ToString(); lastResponseHeaders = new List <KeyValuePair <string, IEnumerable <string> > >(); foreach (var header in response.Headers) { lastResponseHeaders.Add(header); } if ((int)response.StatusCode == 401) { ServerError error = null; try { var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false); error = JsonConvert.DeserializeObject <ServerError>(json); } catch (Exception) { } if (error != null && !error.Error.Equals(Constants.STR_ERROR_BACKEND_INSUFFICIENT_CREDENTIALS)) { // Attempt to get the refresh token Credential cred = Client.Store.Load(Client.ActiveUser.Id, Client.SSOGroupKey); string refreshToken = null; string redirectUri = null; string micClientId = null; if (cred != null) { refreshToken = cred.RefreshToken; redirectUri = cred.RedirectUri; micClientId = cred.MICClientID; if (!string.IsNullOrEmpty(refreshToken) && !refreshToken.ToLower().Equals("null")) { if (!hasRetried) { // Attempting retry - set flag to prevent additional attempts hasRetried = true; //use the refresh token for a new access token JObject result = await Client.ActiveUser.UseRefreshToken(refreshToken, redirectUri, micClientId).ExecuteAsync().ConfigureAwait(false); // log out the current user without removing the user record from the credential store Client.ActiveUser.LogoutSoft(); //login with the access token Provider provider = new Provider(); provider.kinveyAuth = new MICCredential(result["access_token"].ToString()); User u = await User.LoginAsync(new ThirdPartyIdentity(provider), Client).ConfigureAwait(false); //store the new refresh token Credential currentCred = Client.Store.Load(Client.ActiveUser.Id, Client.SSOGroupKey); currentCred.AccessToken = result["access_token"].ToString(); currentCred.RefreshToken = result.GetValidValue("refresh_token"); currentCred.RedirectUri = redirectUri; Client.Store.Store(Client.ActiveUser.Id, Client.SSOGroupKey, currentCred); // Retry the original request RequestAuth = new KinveyAuthenticator(currentCred.AuthToken); var retryResponse = await ExecuteUnparsedAsync().ConfigureAwait(false); return(retryResponse); } else { Client.ActiveUser.Logout(); } } else { //logout the current user Client.ActiveUser.Logout(); } } else { Client.ActiveUser.Logout(); } } } try { response.EnsureSuccessStatusCode(); } catch (Exception ex) { throw new KinveyException( EnumErrorCategory.ERROR_BACKEND, EnumErrorCode.ERROR_JSON_RESPONSE, response, ex ); } return(response); }