I am creating an ASP.NET Core Web API that is intended to work as a middle ground between several third party API's and a first-party web application to sync data between the third parties and my main application.
My Web API implements a service pattern in its controller to access the data. The first third party API we are integrating with uses token-based authorization, so I have to get a token prior to sending requests for other data. In order to get this token, I have created a private method ValidateExampleToken
to see if I have already gotten a valid token, and if so, if that token is still valid based on its timestamp. If either the token is missing or too old, I get another token using the GetExampleToken
method.
Controller:
namespace MyService.API.Controllers
{
[Route("api/[controller]/[action]")]
[ApiController]
public class ExampleController : ControllerBase
{
private readonly ILogger<ExampleController> _logger;
private readonly IExampleService _exampleService;
public ExampleController(ILogger<ExampleController> logger, IExampleService exampleService)
{
_logger = logger;
_exampleService = exampleService;
}
[HttpGet(Name = "GetInsured")]
public async Task<IActionResult> GetInsured(Guid exampleId)
{
try
{
ExampleInsuredModel? insured = await _exampleService.GetInsured(exampleId);
if (insured == null)
return NoContent();
return Ok(insured);
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
}
}
Service:
namespace InsuranceService.ApplicationServices.Services.ExampleService
{
public class ExampleService : IExampleService
{
private readonly ILogger<ExampleService> _logger;
private readonly HttpClient _exampleClient;
private ExampleAuthenticationResponseModel _authRespModel;
private readonly string _exampleUserName;
private readonly string _examplePassword;
private readonly int _batchSize;
public ExampleService(ILogger<ExampleService> logger, HttpClient client, IOptions<ExampleClientSettings> configuration)
{
_logger = logger;
_exampleClient = client;
_exampleUserName = configuration.Value.UserName ?? throw new ArgumentNullException(_exampleUserName);
_examplePassword = configuration.Value.Password ?? throw new ArgumentNullException(_examplePassword);
_batchSize = configuration.Value.BatchResultAmount ?? 0;
_authRespModel = GetExampleToken().Result;
_exampleClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(_authRespModel.Token_Type, _authRespModel.Access_Token);
}
private async void ValidateExampleToken()
{
if (_authRespModel == null || _authRespModel.Expires < DateTime.Now)
{
_authRespModel = await GetExampleToken();
_exampleClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(_authRespModel.Token_Type, _authRespModel.Access_Token);
}
}
private async Task<ExampleAuthenticationResponseModel> GetExampleToken()
{
var body = $"grant_type=password&username={_exampleUserName}&password={_examplePassword}&client_id=ngAuthApp";
StringContent content = new StringContent(body);
var response = await _exampleClient.PostAsync("api/token", content);
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException("Unable to authenticate example credentials");
}
var contentString = await response.Content.ReadAsStringAsync();
if (contentString == null)
{
throw new HttpRequestException("Example authentication response is Null.");
}
// var respModel = JsonConvert.DeserializeObject<ExampleAuthenticationResponseModel>(contentString);
var respModel = JsonSerializer.Deserialize<ExampleAuthenticationResponseModel>(contentString);
if (respModel == null)
throw new Exception("Unable to deserialize example authentication response.");
return respModel;
}
public async Task<ExampleInsuredModel?> GetInsured(Guid exampleId)
{
try
{
ValidateExampleToken();
HttpResponseMessage response = await _exampleClient.GetAsync("?$count=true&$filter=id eq " + exampleId);
response.EnsureSuccessStatusCode();
string content = await response.Content.ReadAsStringAsync();
ExampleInsuredListResponseModel model = JsonSerializer.Deserialize<ExampleInsuredListResponseModel>(content) ?? new ExampleInsuredListResponseModel();
if (model.Count > 1 || model.Value.Count > 1)
{
throw new Exception($"Multiple insured accounts found with example id {exampleId}");
}
else if (model.Count == 1 && model.Value.Count == 1)
{
return model.Value[0];
}
else
{
return null;
}
}
catch
{
throw;
}
}
}
}
This has been working as I expected for making individual API calls from my web application. Now I am trying to make a series of asynchronous calls to the GetInsured
endpoint of the controller to synchronize data for several entities efficiently. The first call to the GetExampleToken
method successfully retrieves a token, but subsequent requests come back with a 500 internal server error.
I'm assuming that because those requests are running asynchronously, their API is returning a server error because its trying to generate two tokens for the same credentials at the same time. Normally if I run requests synchronously, I'm not getting this error.
is there a way to set up some middleware to assign this credential to the ExampleService
's _exampleClient
without having it run on every request and causing this conflict?