Async, await and Task works in debug mode only - c#

The code below begins with the first line calling 'LoadNames' from a .net page. If I'm not in debug mode the interviews variable is set to null. If I add a debug point there and step through it gets a value from the api.
It was getting interviews in UAT. I'm pointing it now at LIVE.
I'm thinking it's likely something unresolved asynchronously. The old api being slower probably made it appear like it was working, and adding debug will slow it down making it appear correct too.
Page.RegisterAsyncTask(new PageAsyncTask(LoadNames));
private async Task LoadNames()
{
VideoInterviewRepository videoRepository = await Repository.CreateClient();
IEnumerable<Api> interviews = await Repository.GetEntityList<Api>(EndPoints);
CODE HERE RUNS BUT FAILS BECAUSE THE ABOVE CODE RETURNS NULL
var interviewList = interviews.ToDictionary(o => o.id, o => o.name);
}
public static Task<VideoInterviewRepository> CreateClient()
{
var videoInterviewRepository = new VideoInterviewRepository();
return videoInterviewRepository.InitializeClient();
}
private async Task<VideoInterviewRepository> InitializeClient()
{
client = VideoInterviewHttpClient.GetClient(VideoInterviewEndPoints.baseUrl);
var bearer = await Authenticate.GetBearerTokenAsync(client);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearer);
return this;
}
public static async Task<string> GetBearerTokenAsync(HttpClient client)
{
var bearerResult = await client.SendAsync(requestToken);
var bearerData = await bearerResult.Content.ReadAsStringAsync();
bearerToken = JObject.Parse(bearerData)["access_token"].ToString();
}
public async Task<IEnumerable<T>> GetEntityList<T>(string path)
{
IEnumerable<T> model = await GetAndParseApiResponse<IEnumerable<T>>(path);
return model;
}
private async Task<T> GetAndParseApiResponse<T>(string path)
{
HttpResponseMessage response = await client.GetAsync(path);
if (response.IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync();
model = JsonConvert.DeserializeAnonymousType<T>(content, model);
}
return model;
}

I found the bug! All the async and awaits and Tasks were correct. The 3rd party didn't register the token on their end fast enough which is why by stepping through I caused a delay. I should have handled response.IsSuccessStatusCode.

Related

Blazor Server Initiating Task Halts Application

I have a Blazor Server app that calls a number of APIs. Everything works fine, but I am trying to wrap these calls in Tasks. As soon as my code gets to the call, everything just stops. I am sure I am doing something stupid, but no end of Googling is finding me the solution. The call comes from a Syncfusion Grid when selecting a row. Here is my minimum reproducable code:
public static IEnumerable<Quotation> customerQuotations = Array.Empty<Quotation>();
public async Task CustomerRowSelectHandler(RowSelectEventArgs<Customer> args)
{
GetCustomerQuotes(args.Data.customerId);
}
static async void GetCustomerQuotes(int customerId)
{
string url = string.Format(#"https://my.server.dns/quotations/customer/{0}", customerId);
var task = GetJsonString(url);
task.Wait();
customerQuotations = (IEnumerable<Quotation>)JsonConvert.DeserializeObject<Quotation>(task.Result);
}
private static async Task<string> GetJsonString(string url)
{
var TCS = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
var myClient = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });
HttpResponseMessage response = await myClient.GetAsync(url);
string streamResponse = await response.Content.ReadAsStringAsync();
TCS.SetResult(streamResponse);
return await TCS.Task;
}
If I do this how I am doing all of my other calls, i.e. not using a Task, it works fine, so I know it's not a silly error, it's something I am missing in my Task call.
Thanks in anticipation of any help offered.
The main problem, is Task.Wait(). That can deadlock.
public async Task CustomerRowSelectHandler(RowSelectEventArgs<Customer> args)
{
//GetCustomerQuotes(args.Data.customerId);
await GetCustomerQuotes(args.Data.customerId);
}
//static async void GetCustomerQuotes(int customerId)
async Task GetCustomerQuotes(int customerId)
{
string url = string.Format(#"https://my.server.dns/quotations/customer/{0}", customerId);
var task = GetJsonString(url);
// task.Wait();
await task;
customerQuotations = (IEnumerable<Quotation>)JsonConvert.DeserializeObject<Quotation>(task.Result);
}
and of course
var task = GetJsonString(url);
await task;
... (task.Result)
can (should) become
string result = await GetJsonString(url);
... (result)
And when you don't need the response object (for status code etc) then all this can be done in 1 line:
customerQuotations = await myClient.GetFromJsonAsync<Quotation[]>(url);
It looks like you are overcomplicating the async coding in the API call. Why do you need to construct a TaskCompletionSource? You may have reasons, but they are not evident in the code in your question.
Why not something like this:
public async Task CustomerRowSelectHandler(...)
{
await GetCustomerQuotes(...);
}
private async ValueTask GetCustomerQuotes(...)
{
string url = string.Format(#"....");
var http = new HttpClient(...);
HttpResponseMessage response = await http.GetAsync(url);
if (response.IsSuccessStatusCode)
customerQuotations = await response.Content.ReadFromJsonAsync<IEnumerable<Quotation>>() ?? Enumerable.Empty<Quotation>(); ;
// handle errors
}
Or even this, but you loose the error trapping.
customerQuotations = await http.GetFromJsonAsync<IEnumerable<Quotation>>(url);
You should also consider using the IHttpClientFactory to manage http instances.

Web API Controller returning Task not always waits for task completion (puppeteer-sharp)

I have Web API controller which returns Task which is orginally created in external library service. I return Task in all the chain from serice to controller, but the problem is that when i make the HTTP call to that controller, first time when i have started the API (it`s always takes a bit longer first time) it returns the expected result perfectly, bu when I make the request second time and so on.. it returns some partial result.
When I debug it it always returns the expected correct result. Obvously there is something that is now awaited..
here is the code:
public async Task<HttpResponseMessage> DownloadBinary(string content)
{
byte[] recordToDown = await ExternalLibraryConverter.GetAsync(content);
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ByteArrayContent(recordToDown)
};
result.Content.Headers.ContentDisposition =
new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
{
FileName = "Test file"
};
// added so Angular can see the Content-Disposition header
result.Headers.Add("Access-Control-Expose-Headers", "Content-Disposition");
result.Content.Headers.ContentType =
new MediaTypeHeaderValue("application/pdf");
return result;
}
and the service:
public static async Task<byte[]> GetAsync(string content)
{
await new BrowserFetcher().DownloadAsync(BrowserFetcher.DefaultRevision)
.ConfigureAwait(false);
var browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true,
}).ConfigureAwait(false);
using (var page = await browser.NewPageAsync().ConfigureAwait(false))
{
await page.SetCacheEnabledAsync(false).ConfigureAwait(false);
await page.SetContentAsync(content).ConfigureAwait(false);
await page.AddStyleTagAsync("https://fonts.googleapis.com/css?family=Open+Sans:300,400,400i,600,700").ConfigureAwait(false);
// few more styles add
var result = await page.GetContentAsync().ConfigureAwait(false);
PdfOptions pdfOptions = new PdfOptions()
{
PrintBackground = true,
MarginOptions = new PuppeteerSharp.Media.MarginOptions {
Right = "15mm", Left = "15mm", Top = "20mm", Bottom = "20mm" },
};
byte[] streamResult = await page.PdfDataAsync(pdfOptions)
.ConfigureAwait(false);
browser.Dispose();
return streamResult;
}
}
There are a lot of await in the service with extenral library as you can see. I tried using ConfigureAwait(false) everywhere where await is used, but this didnt help neither.
I think you should not do a .ConfigureAwait on the controller level, look at this article for more information: https://blog.stephencleary.com/2017/03/aspnetcore-synchronization-context.html.
ASP.NET team dropped the use of SynchronizationContext, so using it in your controller is pointless.
As the article states, you should still use it on your service level, as you don't know whether or not a UI could plug itself to the service and use it, but on your WEB API, you can drop it.

Refreshing claimsPrincipal after changing roles

I'm having some issues with changing role in dotnetcore identity.
I have the following code.
private async Task SetRoleToX(ClaimsPrincipal claimsPrincipal, string X)
{
var currentUser = await UserManager.GetUserAsync(claimsPrincipal);
var roles = await UserManager.GetRolesAsync(currentUser);
await UserManager.RemoveFromRolesAsync(currentUser, roles);
await UserManager.AddToRoleAsync(currentUser, X);
await SignInManager.RefreshSignInAsync(currentUser);
}
I cannot get the ClaimsPrincipal to update.
I have tried using sign in and sign out.
The role switch works fine if I manually sign in and out.
I have been searching the web and alot of people say this should work :(
Rather annoyingly all I had to do was send the token back with the request.
I cant believe i didn't think of it hope this helps someone.
Update with some code as requested
// In controller
public async Task SwapRole([FromBody]RoleSwapRequestDto dto)
{
await _service.SwapRole(
User,
dto.RoleName
);
return await AddCookieToResponse();
}
private async Task AddCookieToResponse()
{
// Make your token however your app does this (generic dotnet core stuff.)
var response = await _tokenService.RegenToken(User);
if (response.Data != null && response.Data.Authenticated && response.Data.TokenExpires.HasValue)
{
Response.Cookies.Append(AuthToken, response.Data.Token, new CookieOptions
{
HttpOnly = false,
Expires = response.Data.TokenExpires.Value
});
}
return response;
}
/// inside _service
public async Task SwapRole(ClaimsPrincipal claimsPrincipal, string X)
{
var currentUser = await UserManager.GetUserAsync(claimsPrincipal);
var roles = await UserManager.GetRolesAsync(currentUser);
await UserManager.RemoveFromRolesAsync(currentUser, roles);
await UserManager.AddToRoleAsync(currentUser, X);
await SignInManager.RefreshSignInAsync(currentUser);
}

async getting no where

I am refactoring my ASP MVC code in session_start in Global.asax.cs with an async call to external service. I either get a white page with endless spinning in IE, or execution immediately returns to the calling thread. In the Session_start() when I tried .Result, I got white page with spinning IE icon. When I tried .ContinueWith(), the execution return to the next line which depends on the result from the async. Thus authResult is always null. Can someone help? Thanks.
This is from the Session_Start()
if (Session["userProfile"] == null) {
//call into an async method
//authResult = uc.checkUserViaWebApi(networkLogin[userLoginIdx]).Result;
var userProfileTask = uc.checkUserViaWebApi(networkLogin[userLoginIdx])
.ContinueWith(result => {
if (result.IsCompleted) {
authResult = result.Result;
}
});
Task.WhenAll(userProfileTask);
if (authResult.Result == enumAuthenticationResult.Authorized) {
This is from User_Controller class
public async Task < AuthResult > checkUserViaWebApi(string networkName) {
UserProfile _thisProfile = await VhaHelpersLib.WebApiBroker.Get < UserProfile > (
System.Configuration.ConfigurationManager.AppSettings["userWebApiEndpoint"], "User/Profile/" + networkName);
AuthResult authenticationResult = new AuthResult();
if (_thisProfile == null) /*no user profile*/ {
authenticationResult.Result = enumAuthenticationResult.NoLSV;
authenticationResult.Controller = "AccessRequest";
authenticationResult.Action = "LSVInstruction";
}
This is helper class that does the actual call using HttpClient
public static async Task<T> Get<T>(string baseUrl, string urlSegment)
{
string content = string.Empty;
using(HttpClient client = GetClient(baseUrl))
{
HttpResponseMessage response = await client.GetAsync(urlSegment.TrimStart('/')).ConfigureAwait(false);
if(response.IsSuccessStatusCode)
{
content = await response.Content.ReadAsStringAsync();
}
return JsonConvert.DeserializeObject<T>(content);
}
It doesn't make sense to call User_Controller from Session_Start.
You want to call VhaHelpersLib directly inside Session_Start, if VhaHelpersLib doesn't have any dependencies.
Since Session_Start is not async, you want to use Result.
var setting = ConfigurationManager.AppSettings["userWebApiEndpoint"];
UserProfile profile = await VhaHelpersLib.WebApiBroker.Get<UserProfile>(
setting, "User/Profile/" + networkName).Result;
if (profile == enumAuthenticationResult.Authorized) {
...
}

WebException on HTTP request while debugging

I have a ASP.NET project which involves sending HTTP requests via the Web-API Framework. The following exception is only raised when debugging:
The server committed a protocol violation. Section=ResponseStatusLine
The project runs perfectly if I "Start Without Debugging".
How should I resolve this exception?
Any help is appreciated!
Update
The problem seems related to the ASP.NET MVC Identity Framework.
To access other Web-API methods, the client application has to first POST a login request (The login request does not need to be secure yet, and so I am sending the username and password strings directly to the Web-API POST method). If I comment out the login request, no more exception is raised.
Below are the relevant code snippets:
The Post method:
UserManager<ApplicationUser> UserManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
AccountAccess ac = new AccountAccess();
public async Task<HttpResponseMessage> Post()
{
string result = await Request.Content.ReadAsStringAsync();
LoginMessage msg = JsonConvert.DeserializeObject<LoginMessage>(result);
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
var user = UserManager.Find(msg.username, msg.password);
if (user == null)
return response;
if (user.Roles == null)
return response;
var role = from r in user.Roles where (r.RoleId == "1" || r.RoleId == "2") select r;
if (role.Count() == 0)
{
return response;
}
bool task = await ac.LoginAsync(msg.username, msg.password);
response.Content = new StringContent(task.ToString());
return response;
}
The Account Access class (simulating the default AccountController in MVC template):
public class AccountAccess
{
public static bool success = false;
public AccountAccess()
: this(new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())))
{
}
public AccountAccess(UserManager<ApplicationUser> userManager)
{
UserManager = userManager;
}
public UserManager<ApplicationUser> UserManager { get; private set; }
public async Task<bool> LoginAsync(string username, string password)
{
var user = await UserManager.FindAsync(username, password);
if (user != null)
{
await SignInAsync(user, isPersistent: false);
return true;
}
else
{
return false;
}
}
~AccountAccess()
{
if (UserManager != null)
{
UserManager.Dispose();
UserManager = null;
}
}
private IAuthenticationManager AuthenticationManager
{
get
{
return HttpContext.Current.GetOwinContext().Authentication;
}
}
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
}
Below are the relevant code snippets:
In client application:
public static async Task<List<T>> getItemAsync<T>(string urlAction)
{
message = new HttpRequestMessage();
message.Method = HttpMethod.Get;
message.RequestUri = new Uri(urlBase + urlAction);
HttpResponseMessage response = await client.SendAsync(message);
string result = await response.Content.ReadAsStringAsync();
List<T> msgs = JsonConvert.DeserializeObject<List<T>>(result);
return msgs;
}
In Web-API controller:
public HttpResponseMessage Get(string id)
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
if (id == "ItemA")
{
List<ItemAMessage> msgs = new List<ItemAMessage>();
// some code...
response.Content = new StringContent(JsonConvert.SerializeObject(msgs));
}
else if (id == "ItemB")
{
List<ItemBMessage> msgs = new List<ItemBMessage>();
// some code...
response.Content = new StringContent(JsonConvert.SerializeObject(msgs));
}
return response;
}
Some observations I have:
I thought that I may need to send the request asynchronously (with the async-await syntax), but the exception still persists that way.
If I step through the code, the request does enter the HTTP method, but the code breaks at random line (Why?!) before returning the response, so I assume no response is being sent back.
I have tried the following solutions, as suggested in answers to similar questions, none of which works for me:
Setting useUnsafeHeaderParsing to true
Adding the header Keep-Alive: false
Changing the port setting of Skype (I don't have Skype, and port 80 and 443 are not occupied)
Additional information, in case they matter:
Mac OS running Windows 8.1 with VMware Fusion
Visual Studio 2013
.NET Framework 4.5
IIS Express Server
Update 2
The exception is resolved, but I am unsure of which modification did the trick. AFAIK, either one or both of the following fixed it:
I have a checkConnection() method, which basically sends a GET request and return true on success. I added await to the HttpClient.SendAsync() method and enforced async all the way up.
I retracted all code in the MainWindow constructor, except for the InitializeComponent() method, into the Window Initialized event handler.
Any idea?
Below are relevant code to the modifications illustrated above:
the checkConnectionAsync method:
public static async Task<bool> checkConnectionAsync()
{
message = new HttpRequestMessage();
message.Method = HttpMethod.Get;
message.RequestUri = new Uri(urlBase);
try
{
HttpResponseMessage response = await client.SendAsync(message);
return (response.IsSuccessStatusCode);
}
catch (AggregateException)
{
return false;
}
}
Window Initialized event handler (retracted from the MainWindow constructor):
private async void Window_Initialized(object sender, EventArgs e)
{
if (await checkConnectionAsync())
{
await loggingIn();
getItemA();
getItemB();
}
else
{
logMsg.Content = "Connection Lost. Restart GUI and try again.";
}
}
Update 3
Although this may be a little off-topic, I'd like to add a side note in case anyone else falls into this – I have been using the wrong authentication approach for Web-API to start with. The Web-API project template already has a built-in Identity framework, and I somehow "replaced" it with a rather simple yet broken approach...
This video is a nice tutorial to start with.
This article provides a more comprehensive explanation.
In the Client Application you are not awaiting task. Accessing Result without awaiting may cause unpredictable errors. If it only fails during Debug mode, I can't say for sure, but it certainly isn't the same program (extra checks added, optimizations generally not enabled). Regardless of when Debugging is active, if you have a code error, you should fix that and it should work in either modes.
So either make that function async and call the task with the await modifier, or call task.WaitAndUnwrapException() on the task so it will block synchronously until the result is returned from the server.
Make sure URL has ID query string with value either as Item A or Item B. Otherwise, you will be returning no content with Http status code 200 which could lead to protocol violation.
When you use SendAsync, you are required to provide all relevant message headers yourself, including message.Headers.Authorization = new AuthenticationHeaderValue("Basic", token); for example.
You might want to use GetAsync instead (and call a specific get method on the server).
Also, are you sure the exception is resolved? If you have some high level async method that returns a Task and not void, that exception might be silently ignored.

Categories

Resources