How to make controller action truly async - c#

I have the following controller:
public class PingController : ApiController
{
[Route("api/ping")]
[HttpGet]
public IHttpActionResult Ping()
{
var log = HostLogger.Get(typeof(PingController));
log.Info("Ping called.");
return Ok("Ping succeeded # " + DateTime.UtcNow);
}
[Route("api/long-ping")]
[HttpGet]
public async Task<IHttpActionResult> LongPing(CancellationToken cancelToken)
{
await Task.Delay(30 * 1000);
return Ok("Ping succeeded # " + DateTime.UtcNow);
}
}
If I execute LongPing, followed by Ping in different browser tabs, the Ping will execute and return before LongPing does -- which is exactly what I'm looking for. The problem is when I execute two LongPing calls the second one takes around 60s to complete (not 30 seconds). Chrome reports the second call has a latency of 58s (60s minus the time it took me to start the second request). It seems to me that both LongPing calls should execute in around 30s if I had this working correctly.
I also should mention that I'm hosting this in an OWIN hosting environment, not IIS. But I didn't think that made any difference but maybe someone will prove me wrong.
How do I make LongPing behave truly like an async request?

It's quite likely that your session state is causing your problems. There's a long-winded explanation for this behaviour, but the short version is that a particular user session can only do one request at a time because the session state locks to ensure consistent state. If you want to speed this up, disable your cookies to test the session state hypothesis (you'll get 1 session state per request that way), or disable session state in the application. Your code is otherwise a-ok async wise.

It turns out this is Chrome's behavior when calling the same URL. I always forget this when testing with Chrome. Normally I test with Fiddler, but this VM doesn't have Fiddler.
See this SO Q&A:
Chrome treating smart url and causing concurrent requests pend for each other

Related

Should I Have to Wait After Creating Team with Graph

I am using the MS Graph API from our web app to create an MS Teams Team in clients' systems and setup a few folders. But I will randomly get errors if I don't impose a hard-coded wait after creating the team. I call the following endpoints in the order shown:
//Create new Team and get basic info
POST teams
GET teams/{team-id}/primaryChannel
GET teams/{team-id}
GET teams/{team-id}/channels/{channel-id}/filesFolder
//Sometimes unknown users must be invited to join org as guest
POST invitations
//Everyone but the owner is added as a guest
POST teams/{team-id}/members
//This is done in a batch, because there is one folder per team guest + one for owner
POST groups/{team-id}/drive/items/{channel-folder-id}/children
//Team members' folders are permitted to them only. So all permissions are deleted and a single user added back
GET groups/{folder-id}/drive/items/{folder-id}/permissions
DELETE groups/{team-id}/drive/items/{folder-id}/permissions/{permission-id}
POST groups/{folder-id}/drive/items/{item-id}/invite
I will sporadically get Forbidden and/or Bad Request responses from:
POST teams/{team-id}/members
DELETE - groups/{team-id}/drive/items/{item-id}/permissions/{permission-id}
Obviously the return statuses of 403 are bugs, because the app definitely has permission to perform the action.
Imposing a 60 second wait after creating the Team seems to resolve this. However, I am currently testing on our Teams environment and am concerned that clients with larger Teams setups will require a longer wait period. I've seen other areas where the documentation says you should wait up to 15 minutes before using a Team that was created from a Group (I am not sure if this applies to creating a normal Team though).
Does anyone know what kind of latency I should be prepared for generally, and if there is any endpoint I can ping to see if the Team is ready for use?
Azure AD, Teams and Exchange are all different systems and need some kind of synchronization that sometimes needs some time.
Whenever you're going to create something in one of these systems, be prepared that it takes some time to access it.
One of the most awkward behaviour I came across is, when you create a group through Exchange Remote Powershell you'll get instantly the group object back. This object has an Azure Object ID. But if you immediately go to Graph and make a request for that group you'll get a 404. Also a look into Azure Portal shows nothing. But if you wait some time (minimum 30 secs, but up to 20!! minutes) the group suddenly appears.
The same also applies if you create a user in Azure through Graph. If you do this, you'll get back an object with the azure id. If you immediately try to add this user to a group or a directory role, it can also happen to get an error, but the timeout here is normally somewhere below 2 sec and I've never seen something above 10 secs.
So for everything, where I'm going to create something in Graph and immediately try to use it, I build some helper method, that tries it multiple times with some smaller timeout between each call:
internal static class Multiple
{
public static Task Try<TException>(int maxRetries, TimeSpan interval, Func<Task> task)
where TException : Exception
{
return Try<TException>(maxRetries, interval, task, exception => true);
}
public static async Task Try<TException>(int maxRetries, TimeSpan interval, Func<Task> task, Func<TException, bool> isExpectedException)
where TException : Exception
{
do
{
try
{
await task().ConfigureAwait(false);
return;
}
catch (Exception ex) when (ex.GetType() == typeof(TException) && isExpectedException((TException)ex))
{
maxRetries--;
if (maxRetries <= 0)
throw;
await Task.Delay(interval);
}
} while (true);
}
}
The usage of the class is as follows:
await Multiple.Try<ServiceException>(20, TimeSpan.FromSeconds(1), async () =>
{
educationClass = await serviceClient.Education.Classes[groupId.ToString()].Request().GetAsync();
}, ex => ex.Error.Code == "Request_ResourceNotFound");
This helper will call the inner method up to 20 times with a timeout of one second. Also the thrown exception must have the given error code. If the number of retries is exceeded or a different error is thrown, the call will rethrow the original exception and must be handled on a higher level.
Simply be aware that behind the Graph interface a highly distributed system works and it sometimes needs some time to get everything in sync.
I test it in my side and met same issues with yours. The 403 error should be a bug as you mentioned because I also have the permission to do the operation. But you mentioned that add guest user to owner, I test it with bad request response, I think it is by design.
Since you can request success after waiting 60 seconds, I think the solution is add a while loop in your code to request the graph api multiple times. In the while loop, if request fail, wait 10 seconds then request again(as Flydog57 mentioned in comments). But you also need to add a mechanism to break loop when request always fail in your code to avoid infinite loops.

OWIN Store update, insert, or delete statement affected an unexpected number of rows (0) Error

I'm running into a very odd issue where the refresh token "disappears" after a few hours on an Azure App Service which hosts my Wep Api project. I've implemented OAuth for my password flow. Our AccessToken expires after 1 hour and our RefreshToken expires after one week.
For some background. This is happening on an Azure App service where i'm hosting my Web Api and a mobile front end is making calls to it (there are more than one users/mobile devices making a call to this app service).
Here's what a sample initial call looks like using /token call:
My grant_type is password. Normally i get back a refresh_token field along with the access_token, token_type and expires_in.
Works fine for the first few hours after i push to the app service then refresh_token disappears. I am truly stumped by this issue.
Here's my CreateAsync Code:
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
var clientid = context.Ticket.Properties.Dictionary["as:client_id"];
if (string.IsNullOrEmpty(clientid))
{
return;
}
string refreshTokenId = await CreateRefreshTokenId(clientid, context);
if (refreshTokenId != null)
{
context.SetToken(refreshTokenId);
}
else
{
throw new Exception("refresh token could not be created");
}
}
private async Task<string> CreateRefreshTokenId(string clientId, AuthenticationTokenCreateContext context)
{
var ticket = context.Ticket;
var refreshTokenId = Guid.NewGuid().ToString("n");
var refreshTokenLifeTime = ConfigurationManager.AppSettings["as:clientRefreshTokenLifeTime"];
var token = new CreateRefreshTokenDTO
{
RefreshTokenId = refreshTokenId,
ClientId = clientId,
Subject = ticket.Identity.Name,
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.AddMinutes(Convert.ToDouble(refreshTokenLifeTime))
};
ticket.Properties.IssuedUtc = token.IssuedUtc;
ticket.Properties.ExpiresUtc = token.ExpiresUtc;
token.ProtectedTicket = context.SerializeTicket();
var result = await createRefreshTokenManager.ManagerRequest(new CreateRefreshTokenRequest
{
RefreshToken = token
});
return result.IsError ? null : refreshTokenId;
}
I've added the exception in the else statement to see if it will throw and exception and it does in fact throw, which leads me to believe that the refreshTokenId is null. I've also added logging to a log table but for whatever reason, when this error is thrown it should save to the DB table (which i've tested locally and works) but on the App server it is not saving to the table. Very perplexing... UPDATE: PLEASE SEE UPDATE BELOW ON WHY NO LOGS WERE SAVING
Then, what is supposed to happen after this is that now that the front end (mobile, in this case) has the access and refresh tokens, when the access token expires, another call is made to /token but with grant_type = refresh_token:
UPDATE
Eventually I was able to reproduce the issue locally through trial and error and waiting for access token to expire (not entirely sure). But in any case, I was able to produce this error:
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=472540 for information on understanding and handling optimistic concurrency exceptions.
This error was the reason i was not able to save any logs to the DB.
Im using Castle Windsor as my IoC and EF6.
My calls are in this order:
1] Attempt to validate the context. In here i make another await call to a LoginUserManager where I basically get and verify user info
// This is used for validating the context
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
2] CreateAsync for creating access and refresh tokens from Context
public async Task CreateAsync(AuthenticationTokenCreateContext context)
Inside CreateAsync I make an await call CreateOrUpdateRefreshTokenManagerwhich either does an Update if entry exists or a Create. And ultimately make a SaveChanges(). This SaveChanges() is what causes the error If I don't call a SaveChanges() no entry is updated or created in that table. This is odd because in other parts of my code i dont call SaveChanges() at all at the end of the web request lifecycle yet an update/create/delete is made. Im assuming that EF/Windsor handles the saving for me.
My thoughts is that because this flow is different from all my other endpoints and that its handling two Async calls that somewhere in between I am disposing the DbContext and that is maybe why im seeing it failing on the second (CreateAsync) call. Not sure, just my thought here.
Anyway, sorry for the long winded post here. I wanted to post as much info as possible and am hoping that this may also help someone else facing this or similar issue.
Thanks!
UPDATE 2
I've noticed that after getting this error on /token call, any other (AllowAnonymous) calls i make work - even those that involve the DB. But the /token call in particular no longer works. My only way around this is to restart the server.
UPDATE 3
I was able to reproduce this issu ONLY on mobile testing (linked to Azure server) but cannot reproduce locally. Steps I used to reproduce:
Log in with one account
Logout
Log in with another account
Logout
Log in with the first account I tried) - This FAILS
Alright ya'll I was able to figure out this issue and i'll do my best to describe what was happening here.
For those of you who have followed a tutorial such as this one or any other similar one, you'll see that you basically have some repository structure set up along with maybe your own context which you inherit the base context, right?
In my case, I was handling the Dispose of the context at the end of the request by overriding the Dispose(bool disposing) method found in the ApiController class. If you're building a WebApi, you'll know what im talking about because any controllers you write inherit this. And if you've got some IoC set up with Lifetimes set to the end of a request, it'll dispose there :)
However, in this case of the /token call I noticed that we were never hitting any controllers...or at least none that utilized ApiController so i couldn't even hit that Dispose method. That meant that the context stayed "active". And in my second, third, fourth, etc calls to /token endpoint, I noticed in the watcher that the context calls were building up (meaning it was saving prior edits to the context i made from previous /token calls - they were never getting disposed! Thus making the context stale).
Unfortunately, the way around this for my situation was to wrap any context calls made within the /token request in a using statement, that way i knew after the using finished up it would dispose properly. And this seemed to work :)
So if you've got a similar project laid out to mine or similar to that tutorial i shared you may run into this issue.

How to wait after a database value then continue

I got an e-commerce website that uses the PayPal Adaptive Payment. The Adaptive Payment requests a PayKey to be generated from PayPal to create an invoice. The delay to get the PayKey is long enough so I had the idea to put the code in a separate thread, while the user answer some other questions before being redirected to PayPal, see code below:
await Task.Run<Task>(async () =>
{
var payResponse = await _payPalApplicationService.ProceedWithPayPal(currentEvent.Id, order.InvoiceId, order.TrackingId, owners.Single(), vModel.TotalPrice, vModel.DeliveryPriceTotal, orderToAdd.TotalTaxes, orderToAdd.SalesRate + orderToAdd.SalesRateTaxes, vModel.SKUViewModels, _payPalApplicationService.PayPalCore._serviceEndPointUrl);
order.PayKey = payResponse.payKey;
_orderService.Update(order);
await _unitOfWorkAsync.SaveChangesAsync();
});
The problem I got is some users can go quick enough so the PayKey has not been generated before being redirected to PayPal.
Did you know anything I can do to make sure I got the PayKey before redirect the users to PayPal? The thread task is done in a different controller action than the one with the redirection.
Thank you
David
It arguably violates an MVC principle of statelessness but a really simple solution would be to store the PayKey retrieval task against the session.
So change the above code to:
Session["PaypalTask"] = await Task.Run<Task>(async () =>
{
var payResponse = await _payPalApplicationService.ProceedWithPayPal(currentEvent.Id, order.InvoiceId, order.TrackingId, owners.Single(), vModel.TotalPrice, vModel.DeliveryPriceTotal, orderToAdd.TotalTaxes, orderToAdd.SalesRate + orderToAdd.SalesRateTaxes, vModel.SKUViewModels, _payPalApplicationService.PayPalCore._serviceEndPointUrl);
order.PayKey = payResponse.payKey;
_orderService.Update(order);
await _unitOfWorkAsync.SaveChangesAsync();
});
Then you can later retrieve the task in your other controller and await it there. If it has already completed exectution will just continue immeadiately, else it will wait for it to complete before continuing.
Something like this
public async Task<ActionResult> Index()
{
var payPalTask = Session["PaypalTask"] as Task;
await payPalTask;
return RedirectToAction("CompltedPayment");
}
Of course, you may also want to consider error handling etc.
UPDATE
I should mention that the above method stores session state in memory. There are some complications if your application uses multiple servers where you may want to research sticky sessions, or some kind of distributed cache like redis.
UPDATE2:
I have published a demo mocking out the async method with a simple Task.Delay to here. If you build this web app then navigate to /InitiatePp it will start the request. Then /PpResult/Result will give the status of the running task and /PpResult/Wait will await completion of the task.

Why use post to keep session alive?

I'm developing a web application with C# MVC and using Session to persist data between multiple requests.
Sometimes the session timed out so I looked for way to keep it alive and found some solutions here in stackoverflow. Being reluctant to simply copy-paste code into my project I attempted to rewrite the code to fit my needs and understand it better.
At first I attempted to keep the session alive using the following code:
JS + jQuery - client side:
function keepAliveFunc(){
setTimeout("keepAlive()", 300000);
};
function keepAlive() {
$.get("/Account/KeepAlive", null, function () { keepAliveFunc(); });
};
$(keepAliveFunc());
C# - server side:
[HttpGet]
public bool KeepAlive()
{
return true;
}
This however did not seem to keep my session alive, it expired normally.
After a while of fiddling around I changed the code to:
JS + jQuery - client side:
function keepAliveFunc(){
setTimeout("keepAlive()", 10000);
};
function keepAlive() {
$.post("/Account/KeepAlive", null, function () { keepAliveFunc(); });
};
$(keepAliveFunc());
C# - server side:
[HttpPost]
public JsonResult KeepAlive()
{
return new JsonResult { Data = "Success" };
}
The latter worked well which has me conclude, with some uncertainty, that the Session is kept alive because of the POST request instead of the GET. Which raises the question: Why do I need to use POST when trying to keep my Session alive? What's the difference? Am I making some other mistake which I do not comprehend?
I've looked for answers but I cannot seem to find any on this matter, merely solutions without much explanation. Reading up on Session on MSDN also didn't help me much. This makes me conclude that there are some "words" related to Session and this perticular problem that I haven't encountered yet which makes me unable to google effectively.
With either GET or POST, the browser does send the SessionId cookie with the request. So for keep-alive purposes it doesn't matter which one you use. Most likely you are seeing the difference in behavior because of the different interval you and "pinging" the server.
With the GET request you did it at an interval of 300000 ms, while with the POST request you did it at an interval of 10000 ms.
Most likely, your server's session lifespan is somewhere between the two values.
You could, however, configure the session lifespan to fit your needs (as in increasing it), but keep in mind that expiring sessions is a security feature so try to find a small value that is big enough to let your application work ok, but still allow the session to expire in a safe interval of time.

Session state unexpected behavior with async

I am investigating async and await in the context of ASP.NET MVC controller methods, and am getting some unexpected behavior.
I have the following controller class:
[SessionState(System.Web.SessionState.SessionStateBehavior.Required)]
public class HomeController : Controller
{
// GET: Home
public ActionResult Index()
{
return View();
}
public async Task<ActionResult> Check1()
{
Session["Test"] = "Check";
System.Diagnostics.Debug.WriteLine("Session: " + Session["Test"]);
await Task.Delay(20000);
return View();
}
public ActionResult Check2()
{
var test = Session["Test"];
ViewBag.Test = test;
return View();
}
}
And simple views for each method Check1 and Check2:
Check1
#{
ViewBag.Title = "Check1";
}
<h2>View with Delay</h2>
Check2
#{
ViewBag.Title = "Check2";
var check = ViewBag.Test;
}
<h2>Immediate View #check</h2>
Now immediately after starting the application, when I open http://localhost:23131/Home/Check1 in one tab and http://localhost:23131/Home/Check2 in 2nd tab, the call to Check1 returns after 20 seconds as expected, and call to Check2 returns immediately, but it doesn't have the value set in the session state, which I fail to understand why. It should be set immediately since it is before the delay. However, correct value is printed on output window.
Now after Check1 returns, hitting refresh on Check2 tab brings value from session in viewbag and displays it.
After this, if I again refresh both tabs, Check2 doesn't return after Check1 is completed (20 seconds delay) despite Check2 being async.
My questions are:
First time when both tabs are opened, Check2 returns immediately due to Check1 being async, and it is not blocked despite SessionStateBehavior.Required since await statement returns control until awaited task completes. Why doesn't Check2 gets the value before Check1 returns?
After 1st time, Check2 gets stuck until Check1 returns despite Check1 being async, why is this? Upon re-running the application, Check1 returns immediately as stated in the previous question but only once.
Is there any concept I have missed or stated wrong in my explanation.
Using ASP.NET with .NET Framework 4.5, Visual Studio 2013 running on Windows 7 Ultimate.
For your question 2, if I understand correctly what you are doing is you refresh both tabs (presumably the one with check1 just before check2), and observe that check2 is not finishing loading until after check1 is finished.
The reason for this is that asp.net processes requests that are (potentially) writing to the same session in serial, not in parallel. That means, as long as any one request is pending, a second request will not even start processing until the first request is finished. Using tasks, async or manual thread handling will not work around this behaviour. The reason is that accessing the session is not a thread-safe operation. Potential workarounds are to not use session state in pages that do not require them, or use ReadOnly session state in pages that do not need to write to the session.
Both are accomplished by using the EnableSessionState property on the page, either setting to False to disable session state, or to ReadOnly to indicate that you do not write to the session in any requests to this page.
Also see for example answers here.
For question 1, it may be the reason is that session state is persisted only after the ReleaseRequestState life-cycle event (which is executed at the very end of the request), even though I would have expected this to to only matter when using a non-inproc session store. The more likely reason is however, that the request to check2 executes first (and blocks even the start of processing for check1). Perhaps you can elaborate how exactly you test this and how you make sure which request is executed first.
Edit:
what may be happening is this: in your first try, you do not actually use the same session (the session is not yet created when you start and a new one is created for each request. That also explains why the second request is not blocked until the first is finished). Then, on the second try, both will use the same session (because the last written session-cookie is used by both tabs). You can confirm that theory by printing the asp session (Session.SessionID) id and/or using cookieless session state

Categories