What I'm wanting to implement is a HttpClient that can get a new bearer token whenever a 401 is thrown by the server. The token expires every 30 minutes and I would like to just update that, but modifying the headers isn't threadsafe (as far as I'm aware) and will cause issues if one thread tries to update the authorisation whilst another is trying to make a request.
public class MyCustomClient : HttpClient
{
public MyCustomClient(Configuration config)
:base()
{
this.BaseAddress = new Uri(config.ServiceUrl);
this.Timeout = new TimeSpan(0, 2, 0);
this.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
public async Task UpdateBearerTokenAsync()
{
var token = "123" // assume this was retrieved from server successfully
this.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
}
}
What I ideally need is for only one re-authorization to be going on at once, and for all other calls to wait if the client is currently trying to re-authenticate.
The first part I think I can do by using a SemaphoreSlim
private readonly SemaphoreSlim _bearerTokenSemaphore;
public MyCustomClient(Configuration config)
:base()
{
_bearerTokenSemaphore = new SemaphoreSlim(0, 1);
_bearerTokenSemaphore.Release();
this.BaseAddress = new Uri(config.ServiceUrl);
this.Timeout = new TimeSpan(0, 2, 0);
this.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
public async Task<bool> UpdateBearerTokenAsync()
{
if (!_bearerTokenSemaphore.Wait(0))
{
return false;
}
try
{
var token = "123" // assume this was retrieved from server successfully using an asynchronous method
this.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
return true;
}
finally { _bearerTokenSemaphore.Release(); }
}
However I'm not sure of a good way of making all other calls wait if authorisation is currently occurring. I tried something like
public new async Task<HttpResponseMessage> GetAsync(string uri)
{
await _bearerTokenSemaphore.WaitAsync();
_bearerTokenSemaphore.Release();
return await base.GetAsync(uri);
}
But this just seemed to deadlock
EDIT:
The above didn't work because I never released the sempahore after instantiating it (woops). So this does seem to work now, but I am still open to better suggestions as I'm really not happy with this implementation
Related
I'm performing a simple GET request to a ASP.NET Core Web API, I created, within Xamarin.Forms 4.8. For this I'm using the following code:
public async Task<Result<bool>> GetSomeResult()
{
var client = service.Client;
HttpResponseMessage response = null;
try
{
response = await client.GetAsync(new UriHelper(endpoint, "someEndpoint")).ConfigureAwait(false);
response.EnsureSuccessStatusCode(); // will throw a exception on a non-success status code
return await response.Content.ReadAsAsync<bool>().ConfigureAwait(false);
}
catch (Exception ex)
{
if (response?.StatusCode == HttpStatusCode.InternalServerError)
{
// !!! The error occurs in the next line !!!
SomeErrorClass id = await response.Content.ReadAsAsync<SomeErrorClass>().ConfigureAwait(false);
return new Result<bool>(SomeErrorClass.ToString());
}
return new Result<bool>(ex);
}
}
The service is a singleton that's being injected into the constructor (via DryIoc) of the surrounding class. This service is a wrapper around a HttpClient instance and does nothing more than providing a facility to configure the HttpClient (as well as disposing and replacing the instance when its configuration has been chaned). So after configuration the same HttpClient instance will be returned whenever service.Client is used. The code is something like this:
public class ServiceConnection
{
private const string Localhost = "https://127.0.0.1";
private readonly PreferenceService preferences;
private readonly IHttpClientHandlerProvider handlerProvider;
public HttpClient Client { get; private set; }
public ServiceConnection(PreferenceService preferences, IHttpClientHandlerProvider handlerProvider)
{
this.preferences = preferences;
this.handlerProvider = handlerProvider;
Client = CreateClient();
}
private HttpClient CreateClient()
{
var handler = new TimeoutHandler()
{
DefaultTimeout = TimeSpan.FromSeconds(10),
InnerHandler = handlerProvider?.GetHandler(opt =>
{
opt.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
opt.UseDefaultCredentials = true;
opt.AllowAutoRedirect = true;
})
};
Uri.TryCreate($"{preferences.Server ?? Localhost}/api/", UriKind.Absolute, out var baseAddress);
var client = new HttpClient(handler, true)
{
BaseAddress = baseAddress,
Timeout = TimeSpan.FromSeconds(20),
};
client.DefaultRequestHeaders.Add(CustomHttpHeaders.DeviceId, preferences.UUID);
return client;
}
public void RefreshConnection()
{
Client?.Dispose();
Client = CreateClient();
}
}
The problem is the line in which I try to read the HttpContent from the HttpResponseMessage. Whenever I call it I get a System.ObjectDisposedExceptionsaying 'Cannot access a disposed object.
Object name: 'System.Net.Http.StreamContent'.'.
I already tried setting the disposeHandler parameter of the HttpClient to true and false since I've seen some people on the internet suggesting that that'll fix the issue, but no luck for me so far.
The problem isn't disposal. There are two problems here, one causing the other:
Exceptions are used for flow control. Instead of throwing on failure, you could check the response's status code
Before .NET Core 3, EnsureStatusCode closes the stream.
The root cause is the use of exceptions for flow control.
This can be solved by fixing the first problem, which would also improve performance a lot. Throwing exceptions is expensive, several orders of magnitude more expensive than an if. As in 100-1000x times faster :
if (response.IsSuccessStatusCode)
{
var value=await response.Content.ReadAsAsync<bool>();
return value; //Should this be `new Result(value) ??
}
else if (response.StatusCode == HttpStatusCode.InternalServerError)
{
var id = await response.Content.ReadAsAsync<SomeErrorClass>();
return new Result<bool>(SomeErrorClass.ToString());
}
else
{
var reason=$"{response.StatusCode}:{response.ReasonPhrase}";
return new Result<bool>(reason);
}
Throwing would obfuscate the response phrase too, which would make troubleshooting a lot harder
I have a client application built on Asp.net core MVC (V 1.1.1) and a Web API built on Asp.net core (v 2.1). I had hosted both on Azure.
While making some requests, the web app is failing and giving 502 Bad Gateway response.
Response status code does not indicate success: 502 (Bad Gateway).
The specified CGI application encountered an error and the server terminated the process.
This issue is intermittent, however, seems happening when request is taking more than 2 min to process. I have set up requestTimeout to 20 min on both client and API side in Web.config file still didn't get it resolved. Sometimes, the same request is being processed in less time and I am getting response.
Additionally, the 5 min timeout for Httpclient has also been set but no luck.
<aspNetCore requestTimeout="00:20:00"/>
_httpClient.Timeout = new TimeSpan(0, 5, 0);
I have tested the app locally and can't face this issue, also able to get response even it take more than 3 min to process.
It seems that Azure web app is not waiting for request to get processed if it's crossing 2 min. However, the azure session timeout specifies 230 seconds (3.8 min) but still it's not waiting and the app is not considering this case as an error and not logging anything.
Client Side code:
public class ApiClientFactory
{
private static Uri ApiUrl;
private static Lazy<ApiClient> restClient = new Lazy<ApiClient>(
() => new ApiClient(ApiUrl),
LazyThreadSafetyMode.ExecutionAndPublication);
static ApiClientFactory()
{
ApiUrl = new Uri(Convert.ToString(ConfigurationManager.AppSettings["WebAPIUrl"]));
}
public static ApiClient Instance
{
get
{
return restClient.Value;
}
}
}
public class ApiClient
{
private readonly HttpClient _httpClient;
private readonly Uri BaseEndPointUrl;
public ApiClient(Uri baseEndPointUrl)
{
if (baseEndPointUrl == null)
throw new ArgumentNullException("baseEndPointUrl");
BaseEndPointUrl = baseEndPointUrl;
_httpClient = new HttpClient();
_httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
_httpClient.Timeout = new TimeSpan(0, 5, 0); //Timeout needed for few modules to get results from db.
}
private HttpContent CreateHttpContent<T>(T content)
{
var json = JsonConvert.SerializeObject(content, MicrosoftDateFormatSettings);
return new StringContent(json, Encoding.UTF8, "application/json");
}
private static JsonSerializerSettings MicrosoftDateFormatSettings
{
get
{
return new JsonSerializerSettings
{
DateFormatHandling = DateFormatHandling.MicrosoftDateFormat
};
}
}
public async Task<T1> PostAsync<T1, T2>(string url, T2 content, string token)
{
try
{
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await _httpClient.PostAsync(url, CreateHttpContent<T2>(content));
if (response.StatusCode == HttpStatusCode.InternalServerError){
var exception = await response.Content.ReadAsStringAsync();
throw new Exception(exception);
}
response.EnsureSuccessStatusCode();
var data = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<T1>(data);
}
catch (Exception ex){throw ex;}
}
}
Please assist where it's having an issue.
You can implement a method that retry your requests, or simply use .Net Polly for this kind of errors. Is not a good practice to increase your timeout setup.
Method:
private async Task<bool> PostAsync()//Params you need
{
HttpStatusCode[] httpStatusCodesWorthRetrying =
{
HttpStatusCode.RequestTimeout,
HttpStatusCode.InternalServerError,
HttpStatusCode.BadGateway,
HttpStatusCode.ServiceUnavailable,
HttpStatusCode.GatewayTimeout
};
var maxAttempts = 0;
while (maxAttempts < 3)
{
var response = await _httpClient.PostAsync(requestUrl, CreateHttpContent<T2>(content));
if (!httpStatusCodesWorthRetrying.Contains(response.StatusCode))
{
return true;
}
maxAttempts ++;
await Task.Delay(300);
}
return false;
}
Polly:
private async Task PostAsync()//Params you need
{
HttpStatusCode[] httpStatusCodesWorthRetrying =
{
HttpStatusCode.RequestTimeout,
HttpStatusCode.InternalServerError,
HttpStatusCode.BadGateway,
HttpStatusCode.ServiceUnavailable,
HttpStatusCode.GatewayTimeout
};
_retryPolicy = Policy
.Handle<HttpRequestException>()
.OrResult<HttpResponseMessage>(r => httpStatusCodesWorthRetrying.Contains(r.StatusCode))
.WaitAndRetry(4, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
_retryPolicy.Execute(await _httpClient.PostAsync(requestUrl, CreateHttpContent<T2>(content)));
}
We thought that the following could be the possible reasons.
At the time of testing, Network related issue between two hosted applications while processing the given
request and to deliver the response. Before the API delivers the response the Web app
might be timing out.
Made the following code change in the API client class. (it's posted in the question).I am not certain about the subtle difference between awaiting the task and getting the Result from the task. But after this change we no longer experienced the issue, additionally did some load testing also but still didn't face that later on.
From
var response = await _httpClient.PostAsync(requestUrl, CreateHttpContent<T2>(content));
To
var response = _httpClient.PostAsync(requestUrl, CreateHttpContent<T2>(content)).Result;
Note: Enabling the Analytics options for the hosted domains in Azure would give us good insights to narrow down this type of issues.
How to efficiently limit request length timeout on server side ? I'm using Microsoft.Owin.Host.HttpListener and there are cases when (due to call to external service) serving request takes ridiculous amount of time. This is not a problem - but web server should give up sooner than - well never (I did some tests, but after 5 minutes I stopped it).
Is there a way how to limit time for serving single request (similar to <httpRuntime maxRequestLength="..." /> in IIS ecosystem) ?
Sample controller code:
public async Task<HttpResponseMessage> Get() {
// ... calls to 3pty services here
await Task.Delay(TimeSpan.FromMinutes(5));
}
Starting web server:
WebApp.Start(this.listeningAddress, new Action<IAppBuilder>(this.Build));
Note: I've read about limiting http listener, but that just limits incoming request properties, it doesn't cancel request that is slow due to slow server processing:
var listener = appBuilder.Properties[typeof(OwinHttpListener).FullName] as OwinHttpListener;
var timeoutManager = listener.Listener.TimeoutManager;
timeoutManager.DrainEntityBody = TimeSpan.FromSeconds(20);
timeoutManager.EntityBody = TimeSpan.FromSeconds(20);
timeoutManager.HeaderWait = TimeSpan.FromSeconds(20);
timeoutManager.IdleConnection = TimeSpan.FromSeconds(20);
timeoutManager.RequestQueue = TimeSpan.FromSeconds(20);
Related:
https://github.com/aspnet/AspNetKatana/issues/152
Conceptually "older" web server solutions - i.e. IIS are using one-thread-per-request separation and ThreadAbortException to kill slow requests. Owin is using different philosophy - i.e. it fires new task per request and forcibly cancelling task is best avoided. There are two sides of this problem:
shus client away if it takes too long
cancel server processing if it takes too long
Both can be achieved using middleware component. There also is a cancellation token provided directly by owin infrastructure for cases when client disconnects (context.Request.CallCancelled where context is IOwinContext)
If you're interested only in cancelling server flow ASAP when it takes to long, I'd recommend something like
public class MyMiddlewareClass : OwinMiddleware
{
// 5 secs is ok for testing, you might want to increase this
const int WAIT_MAX_MS = 5000;
public MyMiddlewareClass(OwinMiddleware next) : base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
using (var source = CancellationTokenSource.CreateLinkedTokenSource(
context.Request.CallCancelled))
{
source.CancelAfter(WAIT_MAX_MS);
// combined "client disconnected" and "it takes too long" token
context.Set("RequestTerminated", source.Token);
await Next.Invoke(context);
}
}
}
And then in controller
public async Task<string> Get()
{
var context = this.Request.GetOwinContext();
var token = context.Get<CancellationToken>("RequestTerminated");
// simulate long async call
await Task.Delay(10000, token);
token.ThrowIfCancellationRequested();
return "Hello !";
}
Shusing the client away is more complex. The middleware will look like this:
public static async Task ShutDownClientWhenItTakesTooLong(IOwinContext context,
CancellationToken timeoutToken)
{
await Task.Delay(WAIT_MAX_MS, timeoutToken);
if (timeoutToken.IsCancellationRequested)
{
return;
}
context.Response.StatusCode = (int)HttpStatusCode.ServiceUnavailable;
}
public async Task ExecuteMainRequest(IOwinContext context,
CancellationTokenSource timeoutSource, Task timeoutTask)
{
try
{
await Next.Invoke(context);
}
finally
{
timeoutSource.Cancel();
await timeoutTask;
}
}
public override async Task Invoke(IOwinContext context)
{
using (var source = CancellationTokenSource.CreateLinkedTokenSource(
context.Request.CallCancelled))
using (var timeoutSource = new CancellationTokenSource())
{
source.CancelAfter(WAIT_MAX_MS);
context.Set("RequestTerminated", source.Token);
var timeoutTask = ShutDownClientWhenItTakesTooLong(context, timeoutSource.Token);
await Task.WhenAny(
timeoutTask,
ExecuteMainRequest(context, timeoutSource, timeoutTask)
);
}
}
we have written a WinRT App connected to a Sharepoint 2013.
We are able to authenticate and login to the sharepoint, but we have problems with the logout 'process'. Login is implemented as follows:
We are setting up a HttpClient with the corresponding user credentials and domain information. The configuration is wrapped in the HttpClientConfig class an delivered to a the HttpClientService which holds the HttpClient object.
After that we retrieve the formdigestValue from the sharepoint and use the token in the X-RequestDigest Header in every request. If the token times out we retrieve a new one.
Here is some code how we implemented the above mentioned authentication.
public async Task Inialize()
{
var httpConfig = new HttpClientConfig();
httpConfig.Headers.Add("Accept", "application/json;odata=verbose");
httpConfig.Headers.Add("User-Agent", _userAgent);
httpConfig.DefaultTimeout = Statics.DEFAULT_NETWORK_TIMEOUT_SECONDS;
httpConfig.PreAuthenticate = true;
httpConfig.NetworkCredentials = new NetworkCredential(username, password, _domain);
_httpClientService.ResetCookies();
_httpClientService.ConfigureHttpClient(httpConfig);
}
The ConfigureHttpClient method disposes an old HttpClient instance and creates a new HttpClient instance, like this:
public void ConfigureHttpClient(HttpClientConfig config, bool disposeCurrent = true)
{
_config = config;
if (disposeCurrent)
{
DisposeHttpClient();
}
_httpClient = CreateHttpClient(config);
if (disposeCurrent)
{
//make sure remove old httpclient and httpclienthandler instances after they are not hold anywhere else
GC.Collect();
}
_httpClientDisposed = false;
}
public HttpClient CreateHttpClient(HttpClientConfig config)
{
_httpClientHandler = _httpClientFactoryService.CreateHttpClientHandler();
_httpClientHandler.CookieContainer = _cookieContainer;
_httpClientHandler.UseCookies = true;
_httpClientHandler.AllowAutoRedirect = config.AllowAutoRedirect;
_httpClientHandler.PreAuthenticate = config.PreAuthenticate;
if (config.NetworkCredentials != null)
{
_httpClientHandler.Credentials = config.NetworkCredentials;
}
var client = _httpClientFactoryService.CreateHttpClient(_httpClientHandler, true);
client.Timeout = TimeSpan.FromSeconds(config.DefaultTimeout);
if (config.UseGzipCompression)
{
if (_httpClientHandler.SupportsAutomaticDecompression)
{
_httpClientHandler.AutomaticDecompression = DecompressionMethods.GZip;
client.DefaultRequestHeaders.AcceptEncoding.Add(StringWithQualityHeaderValue.Parse("gzip"));
}
}
return client;
}
public void DisposeHttpClient()
{
var client = _httpClient;
_httpClientDisposed = true; //set flag before disposing is done to be able to react correctly!
if (client != null)
{
client.Dispose();
}
var handler = _httpClientHandler;
if (handler != null)
{
handler.Dispose();
}
GC.Collect();
}
public async Task<object> InitNewSharepointSession(bool useCookies = true)
{
var config = _httpClientService.CurrentClientConfig;
config.UseCookies = useCookies;
var res = await getRequestDigestAsync();
if (res.IsSuccess)
{
SharepointContextInformation = res.Response;
if (config.Headers.ContainsKey("X-RequestDigest"))
{
config.Headers.Remove("X-RequestDigest");
}
config.Headers.Add("X-RequestDigest", SharepointContextInformation.FormDigestValue);
return new DataServiceResponse<bool>(true);
}
else
{
return new DataServiceResponse<bool>(res.Error);
}
}
The ResetCookies method only disposes the old cookies list:
public void ResetCookies()
{
_cookieContainer = new CookieContainer();
}
As you can see we used some GC.Collect() calls which shows a bit our helplessness according the logout stuff.
For logout, we just dispose our httpclient.
But for some reason, if we login with another user, we sometimes get the data of the previous user which is a pretty high rated bug for us.
Everything works nice if we restart the app, but if we only dispose the current users httpClient we may run in this failure having access with the wrong credential/user context of the previous user.
Another thing I watched is the behaviour after a password change. The old password remains and is valid until the app has been restarted.
So I would be very thankful for some hints or suggestions of a sharepoint REST specialist on how to solve this issue.
I guess you are creating a Universal app for Windows 10. In that case, there is no other option than restarting the app, see this answer.
HTTP credentials are not the same as cookies, so resetting the cookies will not help.
However, if you are using System.Net.Http.HttpClient in a Windows 8/8.1 project (no Universal project), disposing the HttpClient should work.
Example with Windows 8/8.1 template. Do NOT use with Universal template.
private async void Foo()
{
// Succeeds, correct username and password.
await Foo("foo", "bar");
// Fails, wrong username and passord.
await Foo("fizz", "buzz");
}
private async Task Foo(string user, string password)
{
Uri uri = new Uri("http://heyhttp.org/?basic=1&user=foo&password=bar");
HttpClientHandler handler = new HttpClientHandler();
handler.Credentials = new System.Net.NetworkCredential(user, password);
HttpClient client = new HttpClient(handler);
Debug.WriteLine(await client.GetAsync(uri));
}
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.