IdentityServer4: IDX20803: Unable to obtain configuration from 'https://<ids_server_url>/.well-known/openid-configuration' - c#

Using:
Frontend: Angular 14,
API: .NET Core 5, c#, MVC
IDS: .NET Core 5, c#, Razor as per ID standard
For my web app I have an instance of IdentityServer 4 running. This worked perfectly fine and without hick ups for about a year. Since recently when the app starts the login still works flawlessly and provides the token as per usual.
However, any API request thereafter return a 500 error, for about 1 minute or so, after which it works fine and without issue. Until the app is in 'rest' position (i.e. no active users) it starts of with the same error for the same amount of time.
I tried installing serilog to see if I can catch the error on the API side, to no avail.
There are no errors in the logged serilog file.
The only errors I can find are in the ASP.NET logs, which generally llok like the below;
fail: Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[3]
Exception occurred while processing message.
System.InvalidOperationException: IDX20803: Unable to obtain configuration from: 'https://<ids_server_url>/.well-known/openid-configuration'.
---> System.IO.IOException: IDX20804: Unable to retrieve document from: 'https://<ids_server_url>/.well-known/openid-configuration'.
---> System.Net.Http.HttpRequestException: Only one usage of each socket address (protocol/network address/port) is normally permitted. (<ids_server_url>:443)
---> System.Net.Sockets.SocketException (10048): Only one usage of each socket address (protocol/network address/port) is normally permitted.
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken)
Nor can I catch the error on the IDS side, as that also seems to be working fine.
Accessing the .well-known/openid-configuration directly (i.e. from browser) gives a direct and correct response.
Several posts on SO indicated to add the below;
IdentityModelEventSource.ShowPII = true;
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol =
SecurityProtocolType.Tls |
SecurityProtocolType.Tls11 |
SecurityProtocolType.Tls12;
// | SecurityProtocolType.Tls13;
This didn't seem to do anything at all to improve the error.
Would anybody be able to point me in the directions of any other possibilities?
Especially the fact that it is only about a minute at the startup of the app seems to be weird?
I thought it might be the startup of IDS instance, but given that the actual login window repsonds directly and without delay, it implies that the IDS instance is active and running?
Any ideas would be helpfull?
update: 19/02/2023
With the help of #Tore Nestenius I have been able to add some logging to the initial process but the behaviour remains erratic and only on the deployed instance. (Likely because of app_pool shutting down)
Last night according to logger, after 6 failed attempts there was a succesfull query of the openid-configuration
JwtBearerBackChannelListener
#### SendASync: https://<ids_server_url>/.well-known/openid-configuration
#### success: True
#### completed: True
#### loadtime: 132
#### url: https://<ids_server_url>/.well-known/openid-configuration
But...
The subsequent process fails (again)
fail: Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[3]
What's more is that the initial call that the frontend makes is to a non-authorized endpoint (i.e. a public endpoint) there should not be a need for any token verification on that call?
If I query the backend on that endpoint directly from the browser it responds immediately, hence the backend appears to be working correctly? (i.e. api & database respond as expected when queried from the browser) yet in the API ASP logs it indicates a failed jwtHandler call? Weird...

Could it be a timing issue that, when you deploy your application that the client starts to request the discovery document before IdentityServer is up and running?
In AddOpenIDConnect and JwtBearer, you can define your own BackchannelHttpHandler, like this:
.AddJwtBearer(opt =>
{
opt.BackchannelHttpHandler = new JwtBearerBackChannelListener();
opt.BackchannelTimeout = TimeSpan.FromSeconds(60); //default 60s
...
}
This handler is used when it needs to load and reload the discovery document.
A sample handler can look like this:
public class JwtBearerBackChannelListener : DelegatingHandler
{
public JwtBearerBackChannelListener() : base(new HttpClientHandler())
{
Console.WriteLine();
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
Console.WriteLine("JwtBearerBackChannelListener");
Console.WriteLine("#### SendASync: " + request.RequestUri);
var sw = new Stopwatch();
sw.Start();
var result = base.SendAsync(request, cancellationToken);
result.ContinueWith(t =>
{
sw.Stop();
Console.WriteLine("#### success: " + result.IsFaulted);
Console.WriteLine("#### loadtime: " + sw.ElapsedMilliseconds.ToString());
Console.WriteLine("#### url: " + request.RequestUri);
Serilog.Log.Logger.ForContext("SourceContext", "JwtBearerBackChannelListener")
.ForContext("url", request.RequestUri)
.ForContext("loadtime", sw.ElapsedMilliseconds.ToString() + " ms")
.ForContext("success", result.IsCompletedSuccessfully)
.Information("Loading IdentityServer configuration");
});
return result;
}
}
This allows you to add more extensive logging and also even custom retry logic.
It is important that IdentityServer is up-and-running before the client/api starts.
One approach to solve this is to add a middleware that blocks incoming requests from being processed until IdentityServer is online, like this:
Sample code for a waiting middleware
namespace PaymentAPI.Middleware
{
/// <summary>
/// Extension method to register the middleware
/// </summary>
public static class WaitForIdentityServerMiddlewareExtensions
{
public static IApplicationBuilder UseWaitForIdentityServer(this IApplicationBuilder builder, WaitForIdentityServerOptions options)
{
return builder.UseMiddleware<WaitForIdentityServerMiddleware>(options);
}
}
public class WaitForIdentityServerOptions
{
public string Authority { get; set; }
}
/// <summary>
/// ASP.NET Core middleware that will wait for IdentityServer to respond
///
/// It will return a 503 SERVICE UNAVAILABLE if IdentityServer is not responding
///
/// This middleware is only in use until the first successfull response from IdentityServer.
/// After that this module will not do anything.
///
/// It will add the following response headers to the resonse when we return a 503 error:
///
/// - x-reason: Waiting for IdentityServer
/// - Cache-Control: no-store,no-cache,max-age=0
/// - Retry-After: 5
///
/// The authority URL will be taken from the
///
/// Written by Tore Nestenius to be used in the IdentityServer in production training class.
/// https://www.tn-data.se
///
/// </summary>
public class WaitForIdentityServerMiddleware
{
/// <summary>
/// number of seconds between each attempt to contact IdentityServer
/// </summary>
private int secondsBetweenRetries = 2;
/// <summary>
/// How many seconds should we wait before we give up waiting?
/// </summary>
private int httpRequestTimeout = 3;
/// <summary>
/// True when we have been able to reach IdentityServer
/// </summary>
private bool _identityServerReady = false;
private readonly RequestDelegate _next;
private readonly string _discoveryUrl;
private readonly SemaphoreSlim _refreshLock;
private DateTimeOffset _syncAfter = DateTimeOffset.MinValue;
private readonly DateTime _startTime;
public WaitForIdentityServerMiddleware(RequestDelegate next, IConfiguration configuration, WaitForIdentityServerOptions options)
{
_next = next;
_startTime = DateTime.UtcNow;
_discoveryUrl = buildDiscoveryUrl(options.Authority);
_refreshLock = new SemaphoreSlim(1);
}
public async Task InvokeAsync(HttpContext context)
{
//Has IdentityServer has succesfully responsed yet?
if (_identityServerReady == false)
{
//Fail fast if we should wait a bit or if there is already a request is in progress
if (_syncAfter > DateTimeOffset.UtcNow ||
_refreshLock.CurrentCount == 0)
{
//We are waiting to not overload IdentitytServer with to many requests
//Just terminate the request with a 503 Service Unavailable response
CreateServiceUnavailableResponse(context);
return;
}
//Make sure we only do one request at the time
await _refreshLock.WaitAsync().ConfigureAwait(false);
try
{
//Still not answering?
if (_identityServerReady == false)
{
_identityServerReady = await TryToReachIdentityServer(context);
}
}
catch (Exception exc)
{
Log.Logger.ForContext("SourceContext", "WaitForIdentityServerMiddleware")
.ForContext("DiscoveryUrl", _discoveryUrl)
.ForContext("Exception", exc.Message)
.ForContext("Path", context.Request.Path)
.Fatal("Exception while trying to reach IdentityServer");
}
finally
{
_refreshLock.Release();
_syncAfter = DateTimeOffset.UtcNow.AddSeconds(secondsBetweenRetries);
}
}
if (_identityServerReady)
{
// Call the next delegate/middleware in the pipeline
await _next(context);
}
else
{
//As we did not succeeed, let's terminate return a 503 SERVICE UNAVAILABLE error back to the client
CreateServiceUnavailableResponse(context);
return;
}
}
/// <summary>
/// Create a service unavailable 503 error response
/// </summary>
/// <param name="context"></param>
private void CreateServiceUnavailableResponse(HttpContext context)
{
context.Response.Headers.Add("x-reason", "Waiting for IdentityServer");
context.Response.Headers.Add("Retry-After", "5"); //Add a retry again header, with 5 seconds
context.Response.Headers.Add("Cache-Control", "no-store,no-cache,max-age=0"); //Don't cache this response
context.Response.StatusCode = (int)HttpStatusCode.ServiceUnavailable; //503 status code
}
/// <summary>
/// Try to reach the IdentityServer discovery endpoint
/// </summary>
/// <returns>True if successfull</returns>
private async Task<bool> TryToReachIdentityServer(HttpContext context)
{
var client = new HttpClient();
client.Timeout = TimeSpan.FromSeconds(httpRequestTimeout);
var response = await client.GetAsync(_discoveryUrl);
//Should we log?
if (response.IsSuccessStatusCode == false)
{
var secondsSinceStart = (int)DateTime.UtcNow.Subtract(_startTime).TotalSeconds;
Log.Logger.ForContext("SourceContext", "WaitForIdentityServerMiddleware")
.ForContext("DiscoveryUrl", _discoveryUrl)
.ForContext("Path", context.Request.Path)
.ForContext("Tried for over", secondsSinceStart.ToString() + " seconds")
.Information("Failed to reach IdentityServer at startup");
}
return response.IsSuccessStatusCode;
}
/// <summary>
/// Construct the discovery endpoint URL
/// </summary>
/// <param name="authority"></param>
/// <returns></returns>
private string buildDiscoveryUrl(string authority)
{
string Url = authority;
if (!Url.EndsWith("/", StringComparison.Ordinal))
{
Url = Url + "/";
}
Url = Url + ".well-known/openid-configuration";
return Url;
}
}
}
Then to use the handler:
//Make sure its placed before app.UseAuthentication();
//Wait for IdentityServer to startup
app.UseWaitForIdentityServer(new WaitForIdentityServerOptions()
{ Authority = _configuration["openid:authority"] });

Related

Precondition Failed Error while trying to send Message to Teams-Chat | Graph API | C#

everytime I try to send a Teams-Message I recieve an error.
I couldn`t figure out how to fix it and hope someone here has a clue for me.
Error Message:
Microsoft.Graph.ServiceException: "Code: PreconditionFailed
Message: Requested API is not supported in application-only context
Inner error:
AdditionalData:
date: 2022-10-20T12:07:44
request-id: 88e01bd9-370c-4739-b0bd-0244892475e2
client-request-id: 88e01bd9-370c-4739-b0bd-0244892475e2
ClientRequestId: 88e01bd9-370c-4739-b0bd-0244892475e2
"
Permissions:
Delegated Permissions:
Chat.ReadBasic, Chat.Read, Chat.ReadWrite, Chat.Create, ChatMember.Read, ChatMember.ReadWrite, ChatMessage.Send, Chat.ReadWrite, Channel.Delete.All, Group.ReadWrite.All, Directory.ReadWrite.All
Application Permissions:
ChatMember.Read.All, ChatMember.ReadWrite.All, Chat.ReadBasic.All, Chat.Read.All, Chat.Create, Chat.ReadWrite.All, Channel.Delete.Group, Channel.Delete.All, Group.ReadWrite.All, Directory.ReadWrite.All
Code I am using:
/* ------------------------------------------------------------- */
/// <summary>
///
/// </summary>
/// <param name="userId"></param>
/// <param name="chatID"></param>
/// <param name="messageText"></param>
/// <param name="scopes"></param>
/// <returns></returns>
public ChatMessage SendMessageToChat(string userId, string chatID, string messageText, string[] scopes = null)
{
return SendMessageToChatAsync(userId, chatID, messageText, scopes).GetAwaiter().GetResult();
}
/* ------------------------------------------------------------- */
private async Task<ChatMessage> SendMessageToChatAsync(string userId, string chatID, string messageText, string[] scopes = null)
{
GraphServiceClient graphClient = this.GetAuthenticatedGraphClient(scopes);
var chatMessage = new ChatMessage
{
Body = new ItemBody
{
Content = messageText
}
};
return await graphClient.Users[userId].Chats[chatID].Messages
.Request()
.AddAsync(chatMessage);
}
}
}
what you write is await graphClient.Users[userId].Chats[chatID].Messages, so it's obvious that you want to send chat message to a teams chat but not a teams channel.
Let's see the permissions for this graph api. It does not support application permissions. Combining with your error message, I deduce you used client credential flow and set the scope as https://graph.microsoft.com/.default. So you need to use delegate api permission. That means you need to have a module to let users sign in with their user#xxx.onmicrosoft.com account then calling graph api. If what you had is a web application, you need to integrate AAD like this sample demonstrating. If you owned a daemon app, then your requirement can't be realized.

How to deal with 100 seconds timeouts while using Poly retry policy

I'm using retry policy in .net core application and am getting timeouts after exceeding 100 seconds period.
Might I use Poly in some incorrect way or it's by design and only timeout period increase might help?
Here is the way I use Poly:
Startup:
// populate timeouts array from appsettings
var resilencyOptions = services.BuildServiceProvider().GetRequiredService<IOptions<ResiliencyOptions>>().Value;
var attempts = resilencyOptions.TimeOutsInSeconds.Count;
TimeSpan[] timeouts = new TimeSpan[attempts];
int i = 0;
foreach (var timeout in resilencyOptions.TimeOutsInSeconds)
{
timeouts[i++] = TimeSpan.FromSeconds(timeout);
}
// register
services.AddTransient<LoggingDelegatingHandler>();
services.AddHttpClient<IMyClient, MyClient>()
.AddHttpMessageHandler<LoggingDelegatingHandler>()
.AddPolicyHandler(ResiliencyPolicy.GetRetryPolicy(attempts, timeouts))
.AddPolicyHandler(ResiliencyPolicy.GetCircuitBreakerPolicy());
Library:
/// <summary>
/// Resiliency policy.
/// </summary>
public class ResiliencyPolicy
{
/// <summary>
/// Get a retry policy.
/// </summary>
/// <param name="numberofAttempts"> Количество попыток.</param>
/// <param name="timeOfAttempts"> Массив с таймаутами между попытками, если передается неполный или пустой, попытки делаются в секундах 2^.</param>
/// <returns></returns>
public static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy(int numberofAttempts = 5, TimeSpan[] timeOfAttempts = null)
{
// In case timeOfAttempts is null or its elements count doesnt correspond to number of attempts provided,
// we will wait for:
// 2 ^ 1 = 2 seconds then
// 2 ^ 2 = 4 seconds then
// 2 ^ 3 = 8 seconds then
// 2 ^ 4 = 16 seconds then
// 2 ^ 5 = 32 seconds
return HttpPolicyExtensions
.HandleTransientHttpError()
.WaitAndRetryAsync(
retryCount: numberofAttempts,
sleepDurationProvider: retryAttempt => ((timeOfAttempts == null) || (timeOfAttempts.Length != numberofAttempts)) ?
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)) :
timeOfAttempts[retryAttempt],
onRetry: (exception, retryCount, context) =>
{
Logging.Global.LogError($"Retry {retryCount} of {context.PolicyKey} at {context.OperationKey}, due to: {exception}.");
});
}
/// <summary>
/// Get circuit breaker policy.
/// </summary>
/// <param name="numberofAttempts">количество попыток</param>
/// <param name="durationOfBreaksInSeconds">количество секунд (таймаут) между попытками</param>
/// <returns></returns>
public static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy(int numberofAttempts = 5, int durationOfBreaksInSeconds = 30)
{
return HttpPolicyExtensions
.HandleTransientHttpError()
.CircuitBreakerAsync(
handledEventsAllowedBeforeBreaking: numberofAttempts,
durationOfBreak: TimeSpan.FromSeconds(durationOfBreaksInSeconds)
);
}
}
Calling from custom http client:
public class MyClient : IMyClient
{
private readonly HttpClient _httpClient;
private readonly ILogger<MyClient> _logger;
public MyClient(HttpClient httpClient, ILogger<MyClient> logger)
{
_httpClient = httpClient;
_logger = logger;
}
public async Task<bool> Notify(string url, Guid id, string orderId, int state, int category, DateTime date, CancellationToken cancellationToken)
{
// prepare request
var request = new
{
Id = id,
OrderId = orderId,
State = state,
Category = category,
Date = date
};
var data = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json");
// send request
_logger.LogInformation("sending request to {url}", url);
var response = await _httpClient.PostAsync(url, data, cancellationToken);
// process response
if (response.IsSuccessStatusCode)
return true;
var content = await response.Content.ReadAsStringAsync(cancellationToken);
response.Content?.Dispose();
throw new HttpRequestException($"{response.ReasonPhrase}. {content.Replace("\"", "").TrimEnd()}", null, response.StatusCode);
}
}
Controller simulating endpoint availability:
[ApiController]
[Route("[controller]")]
public class RabbitController : ControllerBase
{
private static int _numAttempts;
public RabbitController(IBus client)
{
_client = client;
}
[HttpPost("ProcessTestREST")]
public IActionResult ProcessTestREST(Object data)
{
_numAttempts++;
if (_numAttempts%4==3)
{
return Ok();
}
else
{
return StatusCode((int)HttpStatusCode.InternalServerError, "Something went wrong");
}
}
}
I am getting this error:
"The request was canceled due to the configured HttpClient.Timeout of 100 seconds elapsing."
The important thing to note here, and it's definitely not intuitive, is that the HttpClient.Timeout applies to the ENTIRE collection of calls, which includes all retries and waits: https://github.com/App-vNext/Polly/wiki/Polly-and-HttpClientFactory#use-case-applying-timeouts
The default for HttpClient is 100 seconds, so if your retries and waits exceed that then Polly will throw the TimeoutException.
There's a couple ways to address this:
Set HttpClient.Timeout to the max length of time you'd expect it to take for all your retries.
Put the timeout policy BEFORE the retry policy, which makes it act like a global timeout policy.
In my case I did #1, because I want my timeout policy to apply independently to each request, so I kept my timeout policy AFTER my retry policy. The docs further explain how this works.
Check https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-6.0#dynamically-select-policies
var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
builder.Services.AddHttpClient("PollyDynamic")
.AddPolicyHandler(httpRequestMessage =>
httpRequestMessage.Method == HttpMethod.Get ? timeoutPolicy : longTimeoutPolicy);
The timeout policy should be set during the AddHttpClient phase to override the 100 seconds default value as defined in the official documentation.
Your timeout for polly related requests, should cover the biggest value of your retry policy.
Beware to use custom clients in case you want to ignore the retries, so that the timeout is the default one.
You need to make sure that the timeout for the HttpClient is greater than any of the timeouts for your Polly policies. You need to use the AddHttpClient overload, changing the default timeout for the client from 100 seconds.
var notFoundTimeout = TimeSpan.FromMinutes(5);
var transientTimeout = TimeSpan.FromSeconds(5);
var clientTimeout = notFoundTimeout.Add(new TimeSpan(0, 1, 0));
var notFoundRetryPolicy = Policy.Handle<HttpRequestException>() // 404 not found errors
.OrResult<HttpResponseMessage>(response => response.StatusCode == System.Net.HttpStatusCode.NotFound)
.WaitAndRetryAsync(3, (int tryIndex) => notFoundTimeout);
services.AddHttpClient(CLIENT_NAME, config => config.Timeout = clientTimeout)
.AddPolicyHandler(notFoundRetryPolicy)
.AddTransientHttpErrorPolicy(
builder => builder.WaitAndRetryAsync(3, (int tryIndex) => transientTimeout));
I might be late to the game but allow me to put my 2 cents.
All the other answers are focusing on the 100 seconds default value of HttpClient's Timeout property and try to solve that issue. The real problem is how the AddPolicyHandler works under the hood.
I have detailed here how the PolicyHttpMessageHandler ruins the party. In case of typed HttpClient the solution is to move the policy inside the typed client to avoid the usage of AddPolicyHandler.
You have already separated the policies into a dedicated class ResiliencyPolicy. (BTW you can declare the class as static). I would recommend to expose a combined policy instead of exposing two policies.
public static IAsyncPolicy<HttpResponseMessage> GetCombinedPolicy(int attempts = 5, TimeSpan[] timeouts = null)
=> Policy.WrapAsync<HttpResponseMessage>(GetRetryPolicy(attempts, timeouts), GetCircuitBreakerPolicy())
You may try this during construct your HttpClient:
HttpClient client = new();
client.Timeout = TimeSpan.FromMinutes(5); // or your desire

ERROR: PayPal: Prop is required: payment

I am running into an issue that I do not completely understand. Following this demo from the PayPal developer site: https://developer.paypal.com/demo/checkout/#/pattern/server
I am running into the error in the title of this post.
Here are some code samples
Client side:
payment: function () {
// Make a call to the merchant server to set up the payment
return paypal.request.post('My/api/call').then(function (res) {
return res.token;
});
},
Server side (my/api/call)
var createdPayment = payment.Create(apiContext);
return createdPayment;
I am using the PayPal-NET-SDK to create these objects and return them to which PayPal seems to be OK with until the response is returned. The demo code from PayPal, I think, implies that a payment object is returned. Which is what I am returning from the server (PayPal gives it an ID, a token, etc from the api call), granted the property name of the token is different. Does anyone have any insight into what may be going on?
Thanks
EDIT: Asa per request here is the payment.Create method
/// <summary>
/// Creates and processes a payment. In the JSON request body, include a `payment` object with the intent, payer, and transactions. For PayPal payments, include redirect URLs in the `payment` object.
/// </summary>
/// <param name="apiContext">APIContext used for the API call.</param>
/// <returns>Payment</returns>
public Payment Create(APIContext apiContext)
{
return Payment.Create(apiContext, this);
}
/// <summary>
/// Creates (and processes) a new Payment Resource.
/// </summary>
/// <param name="apiContext">APIContext used for the API call.</param>
/// <param name="payment">Payment object to be used in creating the PayPal resource.</param>
/// <returns>Payment</returns>
public static Payment Create(APIContext apiContext, Payment payment)
{
// Validate the arguments to be used in the request
ArgumentValidator.ValidateAndSetupAPIContext(apiContext);
// Configure and send the request
var resourcePath = "v1/payments/payment";
var resource = PayPalResource.ConfigureAndExecute<Payment>(apiContext, HttpMethod.POST, resourcePath, payment.ConvertToJson());
resource.token = resource.GetTokenFromApprovalUrl();
return resource;
}
You need to return either the EC-XXXXXXX token or the PAY-XXXXXX id, as a string, not the entire payment object.

How do I check to see if an remote API is available when using ASP.Net Core and HttpClient

I am working on an ASP.Net Core API that calls a 3rd party API using an HttpClient GetAsync method. If the 3rd party API is off line, the call times out but in the response that is returned, I do not see any info related to not being able to connect. The response object's StatusCode and ResponsePhrase properties say "Not Found", which is a bit misleading. Is there a way for me to know if the 3rd party API is actually running?
For example, listed below is my code for making a cal to the 3rd party API;
public async Task<SAPIAddAlertSubscriberResponse> AddAlertSubscriberAsync(SAPIAddAlertSubscriberRequest a_request, CancellationToken a_cancellationToken)
{
try
{
SAPIAddAlertSubscriberResponse sapiResponse = new SAPIAddAlertSubscriberResponse();
using (HttpClient client = new HttpClient())
{
var policy = SetRetryPolicy();
HttpResponseMessage response = null;
var uri = $#"{m_appSettings.Value.SAPIUrl88}SAPIAddAlertSubscriber";
string jsonInString = JsonConvert.SerializeObject(a_request);
SetHttpClientHeader(client);
await policy.ExecuteAsync(async token =>
{
response = await client.PostAsync(uri, new StringContent(jsonInString, Encoding.UTF8, "application/json"));
}, a_cancellationToken);
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsStringAsync();
result = TrimWBResponse(result, "SAPIAddAlertSubscriber");
sapiResponse = JsonConvert.DeserializeObject<SAPIAddAlertSubscriberResponse>(result);
}
else
{
sapiResponse.queue.Add(SetSapiError(response, uri, $"add an AlertSubscriber for CarrierLoginId {a_request.CarrierLoginPrimaryID}"));
}
}
return sapiResponse;
}
catch (Exception ex)
{
m_logger.LogError(1, ex, "An exception has occurred in the AddAlertSubscriberAsync method call.");
return null;
}
}
When I make the call at a time that the 3rd party API is down, the call to the 3rd party API holds until the timeout is reached and then returns response.IsSuccessStatusCode = false. But when I examine the values in the response object's StatusCode and ResponsePhrase properties, they both just say "Not Found".
Is there a way for me to return more information that could indicate that the reason was that the API was not available?
EDIT: Added code for SetRetryPolicy
/// <summary>
/// Creates a retry policy for a Polly instance.
/// </summary>
/// <returns>The Polly.Retry.RetryPolicy object</returns>
/// <remarks>
/// Supports implementation of a Polly (https://github.com/App-vNext/Polly) transient exception handler to apply exponential backoff solution for timeout errors.
/// If a timeout transient error occurs, the call will retry after an exponentially increasing time period (starting at 1/10 second) , up to the
/// RetryAttempts value in the settings database
/// <remarks>
private Polly.Retry.RetryPolicy SetRetryPolicy()
{
int retries = 0;
var policy = Policy.Handle<Exception>()
.WaitAndRetryAsync((int)m_appSettings.Value.SAPIRetryAttempts, attempts => TimeSpan.FromSeconds(0.1 * Math.Pow(2, attempts)),
(exception, calculateWaitDuration) =>
{
m_logger.LogError(1, exception, "A transient exception has occurred{NewLine} ... automatically delaying for [{WaitDuration}] ms. This is retry [{Retries}].", Environment.NewLine, calculateWaitDuration, retries);
retries++;
});
return policy;
}
Thanks in advance for any help you can provide.

WebClient Request Extremely slow when running from Windows Service

I'm trying to make a HTTP POST request to a WebAPI Controller, I am calling it from my service. Here is the class that I am injecting into my business layer in order to make the calls:
/// <summary>
/// Makes calls to WebAPI
/// </summary>
public class WebApiProxy : IWebApiProxy
{
/// <summary>
/// Sends HTTP POST requests to WebAPI along with post values
/// </summary>
/// <param name="apiControllerName">The name of the controller to call</param>
/// <param name="postValues">The values to post to the controller</param>
public void SendApiRequest(string apiControllerName, NameValueCollection postValues)
{
using (var client = new WebClient())
{
try
{
//
// Breakpoint below this line, it's where it hangs
//
byte[] responsebytes = client.UploadValues(string.Format("http://localhost:8080/Api/{0}", apiControllerName), "POST", postValues);
string responsebody = Encoding.UTF8.GetString(responsebytes);
}
catch (Exception)
{
// TODO: Handle failed requests
}
}
}
}
I realised that it is running extremely slowly, probably around 30 seconds for every request.
As an experiment I tried doing the request from the console in Chrome using the following code and it runs lightening fast - in the blink of an eye:
$.post("http://localhost:8080/Api/FinishedTakersCount", { "QuizOwnerUserId": 1, UrlId: "0mFjBH" }, null);
I also created a quick console app to test the functionality and this also runs in the blink of an eye:
static void Main(string[] args)
{
while(true)
{
Console.WriteLine("Press Enter to make web request, or type exit...");
var typed = Console.ReadLine();
if (typed == "exit")
{
break;
}
using (var client = new WebClient())
{
try
{
var apiControllerName = "LiveTakersCount";
var postValues = new NameValueCollection();
postValues.Add("QuizOwnerUserId", "1");
postValues.Add("UrlId", "0mFjBH");
//TODO: Remove port from localhost
byte[] responsebytes = client.UploadValues(string.Format("http://localhost:8080/Api/{0}", apiControllerName), "POST", postValues);
string responsebody = Encoding.UTF8.GetString(responsebytes);
Console.WriteLine("HTTP Request completed.");
}
catch (Exception ex)
{
// TODO: Handle failed requests
Console.WriteLine("An Exception occurred with the web request: {0}", ex.Message);
}
}
}
}
You can see the values that I am posting in the above code.
I can't for the life of me understand why it would be taking so long, just because I am running the request within a Window Service.
It is the same whether it makes the call when installed on my local machine or if I debug it within Visual Studio - stepping over client.UploadValues(...) takes 30 seconds or so and the result is fine.
Why is it running slow in my Windows Service (installed or debugging in VS), but lightening fast every time when doing an AJAX request or request from a console application?

Categories