Azure Cosmos Pagination: FeedResponse.ContinuationToken not working in the next request - c#

below is the code that I do pagination in Azure Cosmos. In that function I return the ContinuationToken of the FeedResponse. The first request to get the first page is fine and it return the Continuation Token. However if I used that token in the next request then the API return error 500.
I also notice that the ContinuationToken return from FeedRespone seem like in Json format like that. I have tried to get the token section only, or even copy the whole json but no cigar though
"nextToken": "[{"token":"+RID:~UVURALkfIb4FAAAAAAAAAA==#RT:1#TRC:3#RTD:hCgamV5sp6dv/pVR3z0oBTMxMzIuMTQuNDFVMTY7MjY7NDIvOTk3MzIxMlsA#ISV:2#IEO:65567#QCF:1#FPC:AQEAAAAAAAAACAAAAAAAAAA=","range":{"min":"","max":"FF"}}]"
Response from the First Page with Token return
Enter Return Token to next request and error 500
Function Code
public virtual async Task<(IEnumerable<TDomain>, string token)> ListAsync(List<ISpecification<TEntity>> specifications, PageOptions pageOptions, CancellationToken cancellationToken)
{
var container = await GetContainer(cancellationToken);
string token = null;
var result = new List<TDomain>();
QueryRequestOptions options = new QueryRequestOptions()
{
MaxItemCount = pageOptions.MaxResults
};
options.MaxItemCount = pageOptions.MaxResults;
try
{
var query = container
.GetItemLinqQueryable<TEntity>(false, pageOptions.NextToken, options)
.Specify(specifications);
var iterator = _cosmosLinqQuery.GetFeedIterator(query);
var response = await iterator.ReadNextAsync(cancellationToken);
token = response.ContinuationToken; // return a token
foreach (var item in response)
{
var mapped = _mapper.ToDomain(item);
result.Add(mapped);
}
}
catch (Exception ex)
{
var exception = new DataAccessException("Unexpected error while listing items", ex);
exception.Data["ContainerName"] = ContainerName;
throw exception;
}
return (result,token);
}

Your second screenshot is showing that you are passing a token that starts with +RID... which is not how the previous token starts (previous token starts with [{"token").
Could you be dropping the JSON wrapping attributes that are part of the token?
The second call should be passing exactly [{"token":"+RID:~UVURALkfIb4FAAAAAAAAAA==#RT:1#TRC:3#RTD:hCgamV5sp6dv/pVR3z0oBTMxMzIuMTQuNDFVMTY7MjY7NDIvOTk3MzIxMlsA#ISV:2#IEO:65567#QCF:1#FPC:AQEAAAAAAAAACAAAAAAAAAA=","range":{"min":"","max":"FF"}}].
Keep in mind that you are also sending it in the URL, so there might be character escaping there too.

Related

Asp.Net Core Web API returns the result while client is hanging

I have a simple Asp.Net Core Web API with .NET 6, running on IIS 10, Windows 10. The web API calls another API and returns the results. Below is a simplified version of its code but I tried to keep the most important parts.
[ApiController]
[Produces("application/json")]
public class SomeController
{
private async Task<ApiOutput> RunApiForClientAsync(ApiInput input)
{
try
{
//create a httpclient with a lot of configuration
var response = await client.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();
return new ApiOutput
{
Data = content,
Error = null,
StatusCode = 200,
Input = input,
};
}
catch(Exception ex)
{
return new ApiOutput
{
Data = null,
Error = new ApiError("Error Getting the Result from the Server", ex.Message),
StatusCode = 400,
Input = input,
};
}
}
private async Task<List<ApiOutput>> RunApiCallsAsync(string requestId, IEnumerable<ApiInput> items)
{
var result = new List<ApiOutput>();
var tasks = new List<Task<ApiOutput>>();
foreach(var item in items)
{
tasks.Add(RunApiForAsync(item));
}
var taskResults = await Task.WhenAll(tasks);
result.AddRange(taskResults);
return result;
}
[HttpPost]
[Route("rest/multiple")]
public async Task<IActionResult> PostMultiple(ApiInput[] models, string? requestId)
{
_logger.LogInformation(ApiLoggingEvents.PostMultiple, "Request received with ID {requestId}", requestId);
var result = await RunApiCallsAsync(requestId, models);
try
{
_logger.LogDebug(ApiLoggingEvents.PostMultiple, "Request ID {requestId} Generating JSONs.", requestId);
var resultJson = GetJson(result);
await SaveResultAsync(resultJson, requestId);
_logger.LogDebug(ApiLoggingEvents.PostMultiple, "Request ID {requestId} Everything is finished. Returning....", requestId);
return Content(resultJson, "application/json");
}
catch (Exception ex)
{
_logger.LogDebug(ApiLoggingEvents.PostMultiple, "Exception while returning {requestId}, message {msg}", requestId, ex.Message);
throw new Exception("Try again");
}
}
}
Every once in a while, the caller sends the request to the API but never gets the result back. However, when I read the logs, I see the last line for the request is the line containing the text "Everything is finished. Returning" which means everything was successful. In addition, the output JSON is saved on the server's local drive (the await SaveResultAsync(resultJson, requestId); call is successful too).
I should mention that these types of requests are the ones that take a long while to respond. Usually around 10 minutes. Is there a setting that I need to change on the application or the IIS?
I tried to use the following but it doesn't work with the In-Process model:
builder.WebHost.UseKestrel(o =>
{
o.Limits.MaxConcurrentConnections = 100;
o.Limits.KeepAliveTimeout = TimeSpan.FromMilliseconds(timeout);
o.Limits.MaxRequestBodySize = int.MaxValue;
o.Limits.MaxResponseBufferSize = int.MaxValue;
});
Note:
The requestId is a unique GUID for every request to help me keep track of each request on the log file and see whether it was successful or not and if it has created the output file.
Update:
Upon further investigation, it seems like the requests that have a runtime more than 5 minutes are failing. Any idea what might be related to this number?
Update 2:
I created a very simple endpoint that waits for a specified amount of seconds, then returns back with a simple message:
[HttpPost]
[Route("rest/testpost")]
public IActionResult TestPost(int delay)
{
_logger.LogInformation(1, "Delay for {delay} started.", delay);
Thread.Sleep(delay * 1000);
_logger.LogInformation(1, "Delay for {delay} ended.", delay);
return Ok($"Delay for {delay} worked.");
}
I then added the requestTimeout="00:20:00" to the web.config file, just to make sure.
Interestingly, for values such as 310 seconds, sometimes I get the result, but sometimes I don't. (Postman still hangs)
To your web.config, add the following (the value is in seconds--so this will allow runtimes up to 20 minutes):
<system.web>
<httpRuntime executionTimeout="1200" />
</system.web>
Here is a link to the documentation: https://learn.microsoft.com/en-us/dotnet/api/system.web.configuration.httpruntimesection.executiontimeout?view=netframework-4.8

How to cancel async Task from the client

I have on ASP.Net C# web API with an endpoint for the import. Javascript client sends a list of items to this API and API process this list in another thread (long task) and immediately returns unique id (GUID) of process. Now I need the cancel the background task from the CLIENT. Is possible to somehow send the cancelation token from the client? I have tried to add CancellationToken as a parameter to my controller async action but I don't know how to pass it from the client. For simplification, we can use as the client the Postman app.
Sample server-side
[HttpPost]
[UserContextActionFilter]
[RequestBodyType(typeof(List<List<Item>>))]
[Route("api/bulk/ImportAsync")]
public async Task<IHttpActionResult> ImportAsync()
{
var body = await RequestHelper.GetRequestBody(this);
var queue = JsonConvert.DeserializeObject<List<List<Item>>>(body);
var resultWrapper = new AsynckResultWrapper(queue.Count);
HostingEnvironment.QueueBackgroundWorkItem(async ct =>
{
foreach (var item in queue)
{
var result = await ProcessItemList(item, false);
resultWrapper.AddResultItem(result);
}
});
return Ok(new
{
ProcessId = resultWrapper.ProcessId.ToString()
});
}
private async Task<ItemResult> ProcessItemList(<List<Item>>itemList, bool runInOneTransaction = false)
{
try
{
var result = await PerformBulkOperation(true, itemList);
return new ResultWrapper(result);
}
catch (Exception ex)
{
// process exception
return new ResultWrapper(ex);
}
}
On a high level what you could do is store the process id along with a cancellation token source when you queue the work. Then you can expose a new endpoint that accepts a process id, gets the cancellation token source from the store and cancels the associated token:
[HttpPost]
[UserContextActionFilter]
[RequestBodyType(typeof(List<List<Item>>))]
[Route("api/bulk/ImportAsync")]
public async Task<IHttpActionResult> ImportAsync()
{
var body = await RequestHelper.GetRequestBody(this);
var queue = JsonConvert.DeserializeObject<List<List<Item>>>(body);
var resultWrapper = new AsynckResultWrapper(queue.Count);
HostingEnvironment.QueueBackgroundWorkItem(async ct =>
{
var lts = CancellationTokenSource.CreateLinkedTokenSource(ct);
var ct = lts.Token;
TokenStore.Store(resultWrapper.ProcessId, lts);
foreach (var item in queue)
{
var result = await ProcessItemList(item, ct, false);
resultWrapper.AddResultItem(result);
}
TokenStore.Remove(processId) // remove the cancellation token source from storage when doen, because there is nothing to cancel
});
return Ok(new
{
ProcessId = resultWrapper.ProcessId.ToString()
});
}
private async Task<ItemResult> ProcessItemList(<List<Item>>itemList, CancellationToken token, bool runInOneTransaction = false)
{
try
{
var result = await PerformBulkOperation(true, itemList, token);
return new ResultWrapper(result);
}
catch (Exception ex)
{
// process exception
return new ResultWrapper(ex);
}
}
[Route("api/bulk/CancelImportAsync")]
public async Task<IHttpActionResult> CancelImportAsync(Guid processId)
{
var tokenSource = TokenStore.Get(processId);
tokenSource.Cancel();
TokenStore.Remove(processId) // remove the cancellation token source from storage when cancelled
}
In the above example I modified the ProcessItemList to accept a cancellation token and pass it to PerformBulkOperation, assuming that method has support for cancellation tokens. If not, you can manually call ThrowIfCancellationRequested(); on the cancellation token at certain points in the code to stop when cancellation is requested.
I've added a new endpoint that allows you to cancel a pending operation.
Disclaimer
There are for sure some things you need to think about, especially when it is a public api. You can extend the store to accepts some kind of security token and when cancellation is requested you check whether it matches with the security token that queued the work. My answer is focused on the basics of the question
Also, I left the implementation of the store to your own imagination ;-)

Code: TokenNotFound Message: User not found in token cache. Maybe the server was restarted

I have the following function to call users from active directory use graph api.
This function is hit on each keyup of a text box. But i am getting following error
Code: TokenNotFound Message: User not found in token cache. Maybe the
server was restarted.
at the line
var user = await graphClient.Users.Request().GetAsync();
Entire function Below:
public async Task<string> GetUsersJSONAsync(string textValue)
{
// email = email ?? User.Identity.Name ?? User.FindFirst("preferred_username").Value;
var identifier = User.FindFirst(Startup.ObjectIdentifierType)?.Value;
var graphClient = _graphSdkHelper.GetAuthenticatedClient(identifier);
string usersJSON = await GraphService.GetAllUserJson(graphClient, HttpContext, textValue);
return usersJSON;
}
public static async Task<string> GetAllUserJson(GraphServiceClient graphClient, HttpContext httpContext, string textValue)
{
// if (email == null) return JsonConvert.SerializeObject(new { Message = "Email address cannot be null." }, Formatting.Indented);
try
{
// Load user profile.
var user = await graphClient.Users.Request().GetAsync();
return JsonConvert.SerializeObject(user.Where(u => !string.IsNullOrEmpty(u.Surname) && ( u.Surname.ToLower().StartsWith(textValue) || u.Surname.ToUpper().StartsWith(textValue.ToUpper()))), Formatting.Indented);
}
catch (ServiceException e)
{
switch (e.Error.Code)
{
case "Request_ResourceNotFound":
case "ResourceNotFound":
case "ErrorItemNotFound":
//case "itemNotFound":
// return JsonConvert.SerializeObject(new { Message = $"User '{email}' was not found." }, Formatting.Indented);
//case "ErrorInvalidUser":
// return JsonConvert.SerializeObject(new { Message = $"The requested user '{email}' is invalid." }, Formatting.Indented);
case "AuthenticationFailure":
return JsonConvert.SerializeObject(new { e.Error.Message }, Formatting.Indented);
case "TokenNotFound":
await httpContext.ChallengeAsync();
return JsonConvert.SerializeObject(new { e.Error.Message }, Formatting.Indented);
default:
return JsonConvert.SerializeObject(new { Message = "An unknown error has occured." }, Formatting.Indented);
}
}
}
// Gets an access token. First tries to get the access token from the token cache.
// Using password (secret) to authenticate. Production apps should use a certificate.
public async Task<string> GetUserAccessTokenAsync(string userId)
{
_userTokenCache = new SessionTokenCache(userId, _memoryCache).GetCacheInstance();
var cca = new ConfidentialClientApplication(
_appId,
_redirectUri,
_credential,
_userTokenCache,
null);
if (!cca.Users.Any()) throw new ServiceException(new Error
{
Code = "TokenNotFound",
Message = "User not found in token cache. Maybe the server was restarted."
});
try
{
var result = await cca.AcquireTokenSilentAsync(_scopes, cca.Users.First());
return result.AccessToken;
}
// Unable to retrieve the access token silently.
catch (Exception)
{
throw new ServiceException(new Error
{
Code = GraphErrorCode.AuthenticationFailure.ToString(),
Message = "Caller needs to authenticate. Unable to retrieve the access token silently."
});
}
}
Can you help whats going wrong?
I know this is 4 months old - is this still an issue for you?
As the previous respondent pointed out, the error you're seeing is being thrown in the catch block in your code meant to handle an empty users collection.
In case you're stuck on this, or anyone else comes here - if you used this sample (or using ConfidentialClientApplication in any respect) and are throwing this exception, it's because your _userTokenCache has no users*. Of course, it's not because your AD has no users, otherwise you wouldn't be able to authenticate. Most likely, it is because a stale cookie in your browser is being passed as the access token to your authProvider. You can use Fiddler (or just check your localhost browser cookies) to find it (should be called AspNetCore.Cookies, but you may want to clear all of them).
If you're storing the tokencache in session (as the example is), remember that each time you start and stop the application, your working memory will be thrown out so the token provided by your browser will no longer match the new one your application will retrieve upon starting up again (unless, again, you've cleared the browser cookies).
*cca.Users is no longer used or supported by MSAL - you have to use cca.GetAccountsAsync(). If you have a deployed application running with the deprecated IUser implementation, you'll have to change this. Otherwise, in development your compiler will complain and not let you build, so you'll already know about this.
Looking at the code, it seems some chunks of logic are missing. For example, you got the method
public async Task<string> GetUserAccessTokenAsync(string userId)
but I can't see where this is being called. Besides that, I don't see the code for fetching a token from Azure AD either. Lastly, the error message you mention
Code: TokenNotFound Message: User not found in token cache. Maybe the server was restarted.
Seems like the error you're throwing
if (!cca.Users.Any()) throw new ServiceException(new Error
{
Code = "TokenNotFound",
Message = "User not found in token cache. Maybe the server was restarted."
});
Since the code isn't complete, I will try and make an assumption on what might be going wrong.
Firstly, assuming you're using MSAL.Net, a step in the acquisition of a token is missing.
The general flow is (Using GetTokenByAuthorizationCodeAsync())
Client challenges the user
User gets redirected and logs in
Callback is called and the client receives a code from the login process
Pass the code to GetTokenByAuthorizationCodeAsync() to obtain an id_token and depending on the permissions an access token.
GetTokenByAuthorizationCodeAsync() will store the token in the cache
that has been provided to the ConfidentialClientApplication
Retrieve the token from the cache with AcquireTokenSilentAsync()
If we fail to retrieve a token from the cache with AcquireTokenSilentAsync(), we'll request a new one from via
AcquireTokenAsync()
Most of this flow seems to be in place in your code, but it could be you're missing the actual token acquisition. Since no token is retrieved, no user is added to the ConfidentialClientApplication, which means cca.Users.Any() returns false, resulting in an ServiceError
Assuming the whole flow is in place, and you're actually acquiring a token, my second assumption would be that the _memoryCache are different. The _memoryCache in which you saved your token differs from the one you use to acquire a token silently.
I would recommend reading the documentation on token acquisition to determine the type of retrieving is the right fit for your application.
EDIT
Actually, I assume your code is inspired by this example.
What's especially interesting is this part
public GraphServiceClient GetAuthenticatedClient(string userId)
{
_graphClient = new GraphServiceClient(new DelegateAuthenticationProvider(
async requestMessage =>
{
// Passing tenant ID to the sample auth provider to use as a cache key
var accessToken = await _authProvider.GetUserAccessTokenAsync(userId);
...
}
return _graphClient;
}
What seems to be happening is that calling var user = await graphClient.Users.Request().GetAsync(); invokes the delegate that is provided to the GraphServiceClient. This in turn calls _authProvider.GetUserAccessTokenAsync(userId); which brings us to the public async Task<string> GetUserAccessTokenAsync(string userId) method. Our error most likely originates here, due to no Users being present in the ConfidentialClientApplication.Users collection
Hope this helps!

Document Db Request Continuation Token Not set on the 1st iteration

I have a document db collection that contains about 1500 docs. My eventual goal is to build an api with the above as my persistence layer. I read the docs (https://azure.microsoft.com/en-us/blog/documentdb-paging-support-with-top-and-more-query-improvements/) that mentions to use request continuation token via feed options for paging.
Have the following method on a console app to understand the same: The problem or the issue here is that the first time the query is executed the continuation token=null but the second time the query is executed the continuation token = "some value"
So my question is shouldn't the continuation token be set the first time itself? or am I missing something obvious
//field
private static readonly FeedOptions DefaultOptions = new FeedOptions { EnableCrossPartitionQuery = true,MaxItemCount = 1};
//method
private static async Task<bool> QueryItemDocuments(string collectionLink)
{
IDocumentQuery<Item> query = client.CreateDocumentQuery<Item>(collectionLink, new SqlQuerySpec()
{
QueryText = "SELECT * FROM items",
},DefaultOptions).AsDocumentQuery();
var loopNumber = 0;
while (query.HasMoreResults)
{
loopNumber++;
var results=(await query.ExecuteNextAsync<Item>()).ToList();
Console.WriteLine("Count {0}",results.Count);
Console.WriteLine("Loopnumber :{0} Token {1}", loopNumber, DefaultOptions.RequestContinuation ?? "Null");
Console.WriteLine("Id: {0},Name: {1}", results[0].Id, results[0].Name);
}
return query.HasMoreResults;
}
sample results from console :
Count 1
Loopnumber :1 Token Null //why is this null?
Id: 1dbaf1d0-0125-11e0-88a8-005056957023,Name:Energy Drink
Count 1
Loopnumber :2 Token - RID:jq4gAIZqMwACBBBBBBB==#RT:1#TRC:1#PKRID:0
Id: 244a8230-0231-11e0-8c8b-005056957023,Name: Gluten Free Dish
Edits: Ideally, I would send back the continuation token on the response headers allowing the client to fetch more if needed (on my web api) - so my method would like
public Get(int? page=1,int? size=20)
so when I send the response back with the first 20 I need to send back the continuation token for the client to request more: so if the continuation token is not set the first time - meaning when I retrieve the 1st batch of 20, how do I do this?
You are on the right track. Two thoughts:
You don't need to manually process the continuation token when using HasMoreResults and ExecuteNext. HasMoreResults checks if there is a continuation token and when you call ExecuteNext, it's automatically added.
I suspect that the reason you don't see a continuation on the first round is that it's the outgoing DefaultOptions and it's not updated until it goes out again on the second ExecuteNext. I'm thinking that your output on the second loop is actually the first continuation token.
okay, so i figured that it might be a bug in the SDK or by design - do not know, however when I query for the result using Document Db's rest end points - the continuation token is indeed set on the first batch's response. Here is the sample code that used:
private static async Task QueryItemDocumentsUsingRestApi()
{
//About rest end points to query documents here https://msdn.microsoft.com/en-us/library/azure/mt670897.aspx
IEnumerable<string> continuationTokens = null;
var continToken = string.Empty;
var verb = "POST";
var resourceType = "docs";
var resourceLink = string.Format("dbs/{0}/colls/{1}/docs", DatabaseName, CollectionName);
var resourceId = string.Format("dbs/{0}/colls/{1}", DatabaseName, CollectionName)
var authHeader = GenerateAuthSignature(verb, resourceId, resourceType, authorizationKey, "master", "1.0"); // look here for how this is generated https://github.com/Azure/azure-documentdb-dotnet/blob/master/samples/rest-from-.net/Program.cs
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("x-ms-date", _utcDate);
client.DefaultRequestHeaders.Add("x-ms-version", "2015-08-06");
client.DefaultRequestHeaders.Remove("authorization");
client.DefaultRequestHeaders.Add("authorization", authHeader);
client.DefaultRequestHeaders.Add("x-ms-documentdb-isquery", "True");
client.DefaultRequestHeaders.Add("x-ms-max-item-count", "5");
client.DefaultRequestHeaders.Add("x-ms-continuation", string.Empty);
var qry = new RestSqlQuery { query = "SELECT * FROM items" };
var result = client.PostWithNoCharSetAsync(new Uri(new Uri(endpointUrl), resourceLink), qry).Result;
var content = await result.Content.ReadAsStringAsync();
var pagedList = JsonConvert.DeserializeObject<PagedList>(content);
if (result.Headers.TryGetValues("x-ms-continuation", out continuationTokens))
{
continToken = continuationTokens.FirstOrDefault();
}
}
}

code not getting executed from a class method

I have an interesting issue... For my windows phone 8.1 universal app, I call a method (CommAuthState), defined in my class, from my code:
Here is truncated code which is calling this method.
public async void ShowInitialPosts(object sender, RoutedEventArgs e)
{
#if WINDOWS_PHONE_APP
Windows.Phone.UI.Input.HardwareButtons.BackPressed += HardwareButtons_BackPressed;
#endif
//Get reference to the App setting container...
Windows.Storage.ApplicationDataContainer appRoamingSettings = Windows.Storage.ApplicationData.Current.RoamingSettings;
//Get the value for the logged in state and remembering option....
string commLoggedInValue = (string)appRoamingSettings.Values["CommLoggedIn"];
//If not logged in, redirect to the Community sign in page...
if (commLoggedInValue != "Yes")
{
this.Frame.Navigate(typeof(CommSignIn));
}
else if (commLoggedInValue == "Yes")
{
await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
//Check the access token and its validity...
forum_Hepler.CommAuthState(errorMessage, pgbar, pgText, ServerNetworkError);
});
//Get the access token value...
string myAccessTokenValue = (string)appRoamingSettings.Values["MyAccessToken"];
//Set the access token to the page variable...
accesstoken = myAccessTokenValue;
// Show the progress bar
mycontrols.progressbarShow(pgbar, pgText);
//Set the initial forum URL and page index..
downloadedPostsIndex = 0;
forumUrl = forumUrl + downloadedPostsIndex;
// Make a REST API call with Oauth token........
string telligentResult = await forum_Hepler.MyForumPostsOauth(forumUrl, accesstoken, errorMessage);
.....
.......
}
Here is my class method:
public async void CommAuthState(string errormessage, ProgressBar myprogressbar, TextBlock mytextblock, TextBlock myservernetworkerror) {
// Start showing the progress bar...
mycontrols.progressbarShow(myprogressbar, mytextblock);
//Get the access token value...
string myAccessTokenValue = (string) appRoamingSettings.Values["MyAccessToken"];
//Get the refresh token value...
string myRefreshTokenValue = (string) appRoamingSettings.Values["MyRefreshToken"];
//Get the original access token obtain time....
long myAccessTokenObtainedTimeValue = (long) appRoamingSettings.Values["MyAccessTokenObtainedTime"];
//Convertig date/time back to DateTime object....
origAccessTokenObtainedTime = DateTime.FromBinary(myAccessTokenObtainedTimeValue);
currentDateTime = DateTime.Now;
//Check to see if access token has expired....
accessTokenTimeElasped = currentDateTime - origAccessTokenObtainedTime;
accessTokenTimeElapsedSecs = accessTokenTimeElasped.TotalSeconds;
//Get the value of the access token expiration....
int MyAccessTokenExpiresValue = (int) appRoamingSettings.Values["MyAccessTokenExpires"];
if (accessTokenTimeElapsedSecs <= MyAccessTokenExpiresValue) {
//Get the long GUID value to be used for the GetOauthStuff function below...
string myLongGuidValue = (string) appRoamingSettings.Values["MyLongGuid"];
//Make a GET call to the OAUTH endpoint to get another Access Token by sending the refresh token ....
// string telligentOauthResult = await GetOauthStuff(oauthEndPoint, MyLongGuidValue, keyName, keySecret, errormessage);
string telligentOauthResult = await GetRefreshedOauthStuff(oauthEndPoint, myLongGuidValue, myRefreshTokenValue, keyName, keySecret, errormessage);
if (telligentOauthResult != null) {
// Creating a list out of the JSON object returned by the Telligent API endpoint...
ForumHelper.OAuthObject mytelligentResult = JsonConvert.DeserializeObject < ForumHelper.OAuthObject > (telligentOauthResult);
var accessToken = mytelligentResult.OAuthToken.access_token;
// Get the access token and the time and save it to settings
accessTokenObtainedTime = DateTime.Now;
var accessTokenError = mytelligentResult.OAuthToken.error;
var accessTokenTime = mytelligentResult.OAuthToken.expires_in;
var refreshToken = mytelligentResult.OAuthToken.refresh_token;
//Save access token to the app settings...
appRoamingSettings.Values["MyAccessToken"] = accessToken;
//Converting to binary format before saving since app settings cannot save in DateTime format and also we can convert it back later to the DateTime object
appRoamingSettings.Values["MyAccessTokenObtainedTime"] = accessTokenObtainedTime.ToBinary();
} else {
// Stop showing the progress bar...
mycontrols.progressbarNoShow(myprogressbar, mytextblock);
//Show the error message...
myservernetworkerror.Visibility = Windows.UI.Xaml.Visibility.Visible;
}
}
}
All works fine and I can step through my code to see how it is functioning till I get into the GetRefreshedOauthStuff method. here is the GetRefreshedOauthStuff method for reference:
public async Task < string > GetRefreshedOauthStuff(string url, string MyGUID, string MyResfreshToken, string KeyName, string KeySecret, string ErrorMessage) {
//Setting up Httpclient to send to the Telligent API call.....
var client = new System.Net.Http.HttpClient();
var adminKey = String.Format("{0}:{1}", KeySecret, KeyName);
var adminKeyBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(adminKey));
// Send customized headers with the token and impersonation request
client.DefaultRequestHeaders.Add("Rest-User-Token", adminKeyBase64);
client.DefaultRequestHeaders.Add("Cache-Control", "no-cache");
//Adding the long GUID to the OAUTH endpoint URL...
url = url + MyGUID + "&refresh_token=" + MyResfreshToken;
//For getting the list of posts....
System.Net.Http.HttpResponseMessage telligentResponse = await client.GetAsync(url);
if (telligentResponse.StatusCode == System.Net.HttpStatusCode.OK || telligentResponse.StatusCode == System.Net.HttpStatusCode.Forbidden) {
// Read the HTTP response content....
HttpContent responseContent = telligentResponse.Content;
// Read the response content as string.....
return await responseContent.ReadAsStringAsync();
} else {
throw new Exception("Error connecting to " + url + " ! Status: " + telligentResponse.StatusCode);
//Pop up the server or network issue message...
//mycontrols.popupMessages(ErrorMessage, "Network or Server error!");
////Navigate back to the main page....
//this.rootFrame.Navigate(typeof(MainPage));
//return null;
}
}
As soon as my code executes this line:
System.Net.Http.HttpResponseMessage telligentResponse = await client.GetAsync(url);
it steps out of this method instead of fully completing it and goes to this line in my starting method ShowInitialPosts:
string telligentResult = await forum_Hepler.MyForumPostsOauth(forumUrl, accesstoken, errorMessage);
This obviously creates problems and fails my logic. what am I doing wrong here? How do I fix this?
Any help or pointers are much appreciated
Thanks
That's exactly what await is supposed to do. It doesn't block the thread from executing - it tells the method to return control to whoever called it and then resume execution when the async method has finished.
Eric Lippert has a great blog post about this.
http://blogs.msdn.com/b/ericlippert/archive/2010/10/29/asynchronous-programming-in-c-5-0-part-two-whence-await.aspx

Categories