Home > Software engineering >  ASP.NET Core Web API -Error while validating the service descriptor 'ServiceType: IAccountBalan
ASP.NET Core Web API -Error while validating the service descriptor 'ServiceType: IAccountBalan

Time:06-18

In my ASP.NET Core-6 Web API application, I want to get data from third party API and save into the database using HangFire.

I have these code.

In appsettings.json I have:

  "Endpoints": {
    "accountBalanceUrl": "https://api.thirdpartycompany.com:2233/UserAccount/api/AccountDetail"
  }

Then JSON Result is shown below:

{
  "AccountNumber": "string",
  "AccountName": "string",
  "CurrentBalance": 0,
  "AvailableBalance": 0,
  "Currency": "string"
}

So far, I have done this:

BalanceEnquiryResponseDto:

public class BalanceEnquiryResponseDto
{
    public List<BalanceListDto> AccountBalances
    {
        get;
        set;
    }
}

BalanceListDto:

public class BalanceListDto
{
    public string AccountNumber
    {
        get;
        set;
    }

    public string AccountName
    {
        get;
        set;
    }

    public decimal CurrentBalance
    {
        get;
        set;
    }

    public decimal AvailableBalance
    {
        get;
        set;
    }

    public string Currency
    {
        get;
        set;
    }
}

BaseResponse:

public class BaseResponse
{
    public bool Success { get; set; } = true;
    public string Message { get; set; }
}

Interface:

public interface IAccountBalanceService
{
    Task<BaseResponse> CreateAccountBalanceAsync();
}

Implementation:

public class AccountBalanceService : IAccountBalanceService
{
    private readonly IMapper _mapper;
    private readonly IUnitOfWork _unitOfWork;
    private readonly ILogger _logger;
    private readonly IConfiguration _config;
    private readonly HttpClient _myClient;
    public AccountBalanceService(
        IUnitOfWork unitOfWork,
        ILogger logger,
        IMapper mapper,
        HttpClient myClient,
        IConfiguration config
        )
    {
        _mapper = mapper;
        _unitOfWork = unitOfWork;
        _logger = logger;
        _myClient = myClient;
        _config = config;
    }
    public async Task<BaseResponse> CreateAccountBalanceAsync()
    {
        var balanceResponse = new BaseResponse();
        try
        {
            string accountBalanceUrl = _config.GetSection("Endpoints").GetValue<string>("accountBalanceUrl");

            HttpResponseMessage response = _myClient.GetAsync(bankBranchUrl).Result;

            var stringResult = response.Content.ReadAsStringAsync().Result;
            BankBranchResponse list = JsonConvert.DeserializeObject<BalanceEnquiryResponseDto>(stringResult);
            foreach (var singleBalance in list.AccountBalances)
            {
                CustomerBalance res = new CustomerBalance();

                //set all fields here
                res.AccountNumber = singleBalance.AccountNumber;
                res.AccountName = singleBalance.AccountName;
                res.CurrentBalance = singleBalance.CurrentBalance;
                res.AvailableBalance = singleBalance.AvailableBalance;
                res.Currency = singleBalance.Currency;
                await _unitOfWork.CustomerBalances.InsertAsync(res);
            }
            await _unitOfWork.Save();
        }
        catch (Exception ex)
        {
            _logger.Error("An Error occured "   ex.ToString());
        }
        return balanceResponse;
    }
}

This is the original url for the third party API:

https://api.thirdpartycompany.com:2233/UserAccount/api/AccountDetail

For the CronJob and Schedule, I am using HangFire.

HangFireServiceExtension:

public static class HangFireServiceExtension
{
    public static void AddHangFireConfigurations(this IServiceCollection services, IConfiguration config)
    {
        string connStr;
        connStr = config.GetConnectionString("DefaultConnection");
        services.AddHangfire(configuration => configuration
                .SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
                .UseSimpleAssemblyNameTypeSerializer()
                .UseRecommendedSerializerSettings()
                .UseSqlServerStorage(connStr, new SqlServerStorageOptions
                {
                    CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
                    SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
                    QueuePollInterval = TimeSpan.Zero,
                    UseRecommendedIsolationLevel = true,
                    DisableGlobalLocks = true
                }));
        // Add the processing server as IHostedService
        services.AddHangfireServer();
    }
}

Program.cs:

var builder = WebApplication.CreateBuilder(args);
ConfigurationManager configuration = builder.Configuration;
var environment = builder.Environment;

builder.Services.AddHangFireConfigurations(configuration);

builder.Services.AddScope<IAccountBalanceService, AccountBalanceService>();

app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.UseHangfireDashboard();

RecurringJob.AddOrUpdate<IAccountBalanceService>("Post_All_Balances", service => 
service.CreateAccountBalanceAsync(),
        Cron.Daily(2, 0), TimeZoneInfo.Local);


app.MapControllers();
app.Run();

I didn't create any Controller for it, since I am running it as a backgroung cron job.

It is schedule to save the data daily by 2:am.

I was able to build it successfully. But when I tried to run it in Visual Studio using IIS Express, I got this error:

System.AggregateException
  HResult=0x80131500
  Message=Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: IAccountBalanceService Lifetime: Scoped ImplementationType: AccountBalanceService': Unable to resolve service for type 'System.Net.Http.HttpClient' while attempting to activate 'AccountBalanceService'.)
  Source=Microsoft.Extensions.DependencyInjection
  StackTrace:
   at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(ICollection`1 serviceDescriptors, ServiceProviderOptions options)
   at Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(IServiceCollection services, ServiceProviderOptions options)
   at Microsoft.Extensions.DependencyInjection.DefaultServiceProviderFactory.CreateServiceProvider(IServiceCollection containerBuilder)
   at Microsoft.Extensions.Hosting.Internal.ServiceFactoryAdapter`1.CreateServiceProvider(Object containerBuilder)
   at Microsoft.Extensions.Hosting.HostBuilder.CreateServiceProvider()
   at Microsoft.Extensions.Hosting.HostBuilder.Build()
   at Microsoft.AspNetCore.Builder.WebApplicationBuilder.Build()
   at Program.<<Main>$>d__0.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Program.<Main>(String[] args)

  This exception was originally thrown at this call stack:
    [External Code]

Inner Exception 1:
InvalidOperationException: Error while validating the service descriptor 'ServiceType: IAccountBalanceService Lifetime: Scoped ImplementationType: AccountBalanceService': Unable to resolve service for type 'System.Net.Http.HttpClient' while attempting to activate 'AccountBalanceService'.

Inner Exception 2:
InvalidOperationException: Unable to resolve service for type 'System.Net.Http.HttpClient' while attempting to activate 'AccountBalanceService'.

How do I resolve this?

Thanks

CodePudding user response:

You need to register IHttpClientFactory/HttpClient in the DI to use it. For example via typed client:

builder.Services.AddHttpClient<IAccountBalanceService, AccountBalanceService>(); // instead of builder.Services.AddScope<IAccountBalanceService, AccountBalanceService>();

Note that:

The typed client is registered as transient with DI container

If you are required to have the IAccountBalanceService to be scoped you can keep current registration and resolve IHttpClientFactory:

builder.Services.AddHttpClient(); 
builder.Services.AddScope<IAccountBalanceService, AccountBalanceService>();

and in the service ctor:

public AccountBalanceService(
    IUnitOfWork unitOfWork,
    ILogger logger,
    IMapper mapper,
    IHttpClientFactory factory,
    IConfiguration config
    )
{
    _mapper = mapper;
    _unitOfWork = unitOfWork;
    _logger = logger;
    _myClient = factory.CreateClient();
    _config = config;
}
  • Related