Skip to main content
3 of 10
update wording, break up long paragraph into two

IHostedService - Calling 3rd party web api

In my current solution, a user triggers my web API from a web application of mine first, then my web API calls a 3rd party web service, and the results/codes are displayed on the web application. This current design (web application <-> web API <-> 3rd party web service) forces the user to stay in front of the screen and keep constantly sending requests to the API until successfully fulfilling 20K codes. This 3rd party web API returns a maximum of 10 results/codes inside of a single request. There are times it is needed to get 20.000 results/codes from this service.

I implemented an IHostedService that calls this 3rd party web API in the background. I need your feedback in terms of speed, error handling and etc. How can I improve my solution? Do you have any comments?

Here is the IHostedService:

public class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger;
    private readonly IHttpClientFactory _httpClientFactory;
    private readonly IConfiguration _configuration;

    public Worker(ILogger<Worker> logger, IHttpClientFactory httpClientFactory, IConfiguration configuration)
    {
        _logger = logger;
        _httpClientFactory = httpClientFactory;
        _configuration = configuration;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        //while (!stoppingToken.IsCancellationRequested)
        //{
        //    await GetData();
        //    _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
        //    /*await Task.Delay(1000, stoppingToken);*/
        //}

        //Timer with 3 mins
        var timer = new PeriodicTimer(TimeSpan.FromMinutes(3));
        while (await timer.WaitForNextTickAsync(stoppingToken))
        {
            await GetData();
            _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);

        }
    }

    private async Task MakeRequestsToRemoteService(string productCode, long id)
    {
        if (id <= 0) throw new ArgumentOutOfRangeException(nameof(id));
        try
        {
            var httpClient = _httpClientFactory.CreateClient("RazerClient");

            //Token
            
            httpClient.DefaultRequestHeaders.Accept.Add(
                new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));

            var tokenContent = new FormUrlEncodedContent(new[]
            {
                new KeyValuePair<string, string>("username", "Test"),
                new KeyValuePair<string, string>("password", "123456")
                
            });

            using var tokenResponse = await httpClient.PostAsync(_configuration["Token:Production"], tokenContent);

            if ((int)tokenResponse.StatusCode == 200)
            {
                var token = await tokenResponse.Content.ReadAsStringAsync();

                //Call Razer Multi Requests
                
                httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer",
                    token);
                httpClient.DefaultRequestHeaders.Accept.Add(
                    new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));

                //without Parallel.ForEach
                //var content = new FormUrlEncodedContent(new[]
                //{
                //    new KeyValuePair<string, string>("productCode", productCode),
                //    new KeyValuePair<string, string>("quantity", "100")

                //});

                ////Parallel.ForEachAsync
                //using var response = await httpClient.PostAsync(_configuration["Razer:Production"], content);

                //if ((int)response.StatusCode == 200)
                //{
                //    var coupon = await response.Content.ReadFromJsonAsync<Root>();

                //    _logger.LogInformation("Order {referenceId} {pin}, {Serial} ", coupon.ReferenceId,
                //        coupon.Coupons[0].Pin, coupon.Coupons[0].Serial);

                //    await UpdateData(id);
                //}
                //else
                //{
                //    _logger.LogError("Purchase ServiceError: {statusCode}",
                //        (int)response.StatusCode);
                //}
                var quantities = new List<int> { 100, 100, 100, 100, 100 };
                var cts = new CancellationTokenSource();
                ParallelOptions parallelOptions = new()
                {
                    MaxDegreeOfParallelism = 2,
                    CancellationToken = cts.Token
                };
                try
                {
                    await Parallel.ForEachAsync(quantities, parallelOptions, async (quantity, ct) =>
                    {
                        var content = new FormUrlEncodedContent(new[]
                        {
                            new KeyValuePair<string, string>("productCode", productCode),
                            new KeyValuePair<string, string>("quantity", quantity.ToString())

                        });

                        using var response = await httpClient.PostAsync(_configuration["Razer:Production"], content, ct);

                        if ((int)response.StatusCode == 200)
                        {
                            var coupon = await response.Content.ReadFromJsonAsync<Root>(cancellationToken: ct);

                            _logger.LogInformation("REFERENCE ID: {referenceId}", coupon.ReferenceId);

                            await UpdateData(id);
                        }
                        else
                        {
                            _logger.LogError("Purchase ServiceError: {statusCode}",
                                (int)response.StatusCode);
                        }
                    });
                }
                catch (OperationCanceledException ex)
                {
                    _logger.LogError("Operation canceled: {Message}",
                        ex.Message);
                }

                
            }
            else
            {
                _logger.LogError("Token ServiceError: {statusCode}",
                    (int)tokenResponse.StatusCode);
            }
        }
        catch (Exception e)
        {
            _logger.LogError("Error: {Error} ", e.Message);
            throw;
        }
    }
    private async Task GetData()
    {
        var sw = Stopwatch.StartNew();
        var connString = _configuration["ConnectionStrings:Default"];
        await using var sqlConnection = new SqlConnection(connString);
        sqlConnection.Open();

        await using var command = new SqlCommand { Connection = sqlConnection };
        const string sql = @"Select TOP 1 Id, BulkId, Amount, ProductCode from BulkPurchases where status = 0";
        command.CommandText = sql;


        try
        {
            await using var reader = await command.ExecuteReaderAsync();
            while (reader.Read())
            {
                _logger.LogInformation(
                    "Order {Id}, {BulkId}, {Amount}, {ProductCode}",
                    reader.GetInt32(0), reader.GetInt32(1), reader.GetInt32(2), reader.GetString(3));

                await MakeRequestsToRemoteService(reader.GetString(3).Trim(), reader.GetInt32(0));
            }
        }
        catch (SqlException exception)
        {
            _logger.LogError("Error: {Error} ", exception.Message);
            throw; // Throw exception if this exception is unexpected
        }
        sw.Stop();

        _logger.LogInformation($"******** ELAPSED TIME: {sw.Elapsed.TotalSeconds} seconds ********");
    }

    private async Task UpdateData(long id)
    {
        var connString = _configuration["ConnectionStrings:Default"];
        await using var sqlConnection = new SqlConnection(connString);
        sqlConnection.Open();

        await using var command = new SqlCommand { Connection = sqlConnection };
        const string sql = @"Update BulkPurchases set status = 1 where Id = @id";
        command.CommandText = sql;

        command.Parameters.Add(new SqlParameter("id", id));

        try
        {
            await command.ExecuteNonQueryAsync();
        }
        catch (SqlException exception)
        {
            _logger.LogError("Error: {Error} ", exception.Message);
            throw; // Throw exception if this exception is unexpected
        }
    }
}
raysefo
  • 267
  • 2
  • 12