5

I am developing asp.net core 3.1 GraphQL based APIs. I used the below reference article to setup automatic DI configuration in the API layer and have used the nuget package : NetCore.AutoRegisterDi

https://www.thereformedprogrammer.net/asp-net-core-fast-and-automatic-dependency-injection-setup/

Here goes the code details:

Code:

Startup.cs:

public virtual void ConfigureServices(IServiceCollection services) => services
                      .AddGraphQLResolvers()
                      .AddProjectRepositories();

ProjectServiceCollectionExtensions.cs

public static class ProjectServiceCollectionExtensions
{
    public static IServiceCollection AddProjectRepositories(this IServiceCollection services) => 
        services.RegisterAssemblyPublicNonGenericClasses(Assembly.GetAssembly(typeof(CommonService)))
                .Where(c => c.Name.EndsWith("Persistence"))
                .AsPublicImplementedInterfaces(ServiceLifetime.Scoped);

    public static IServiceCollection AddGraphQLResolvers(this IServiceCollection services) =>
       services
           .AddScoped<ICountriesResolver, CountriesResolver>()
           .AddScoped<ICountryGroupsResolver, CountryGroupsResolver>()
           .AddScoped<IDisclaimerResolver, DisclaimerResolver>();
}

Here in the above CommonService is part of the Service Layer that ends with Persistence.

CountriesResolver.cs

public class CountriesResolver : Resolver, ICountriesResolver
{
    private readonly ICountryService _countryService;
    private readonly IHttpContextAccessor _accessor;
    private readonly IDataLoaderContextAccessor _dataLoaderContextAccessor;
    public CountriesResolver(ICountryService countryService, IHttpContextAccessor accessor, IDataLoaderContextAccessor dataLoaderContextAccessor)
    {
        _countryService = countryService ?? throw new ArgumentNullException(nameof(countryService));
        _accessor = accessor;
        _dataLoaderContextAccessor = dataLoaderContextAccessor;
    }

    public void Resolve(GraphQLQuery graphQLQuery)
    {
        var language = _accessor.HttpContext.Items["language"] as LanguageDTO;
        graphQLQuery.FieldAsync<ResponseGraphType<CountryResultType>>("countriesresponse", arguments: new QueryArguments(new QueryArgument<IdGraphType>{Name = "pageNo", Description = "page number"}, new QueryArgument<IdGraphType>{Name = "pageSize", Description = "page size"}), resolve: async context =>
        {
            var pageNo = context.GetArgument<int>("pageNo") == 0 ? 1 : context.GetArgument<int>("pageNo");
            var pageSize = context.GetArgument<int>("pageSize") == 0 ? 100 : context.GetArgument<int>("pageSize");
            if (language != null)
            {
                var loader = _dataLoaderContextAccessor.Context.GetOrAddLoader("GetAllCountries", () => _countryService.GetAllCountriesAsync(language, pageNo, pageSize));
                var list = await context.TryAsyncResolve(async c => await loader.LoadAsync());
                return Response(list);
            }

            return null;
        }

        , description: "All Countries data");
    }
}

ICommonService.cs

using Author.Query.Persistence.DTO;
using System.Threading.Tasks;

namespace Author.Query.Persistence.Interfaces
{
    public interface ICommonService
    {
        LanguageDTO GetLanguageFromLocale(string locale);
        Task<LanguageDTO> GetLanguageFromLocaleAsync(string locale);
    }
}

CommonService.cs

namespace Author.Query.Persistence
{
    public class CommonService : ICommonService
    {
        private readonly AppDbContext _dbContext;
        private readonly IOptions<AppSettings> _appSettings;
        private readonly IMapper _mapper;
        private readonly ICacheService<Languages, LanguageDTO> _cacheService;
        public CommonService(AppDbContext dbContext, IOptions<AppSettings> appSettings, IMapper mapper, ICacheService<Languages, LanguageDTO> cacheService)
        {
            _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
            _appSettings = appSettings;
            _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
            _cacheService = cacheService ?? throw new ArgumentNullException(nameof(cacheService));
        }

        //public Languages GetLanguageFromLocale(string locale)
        public LanguageDTO GetLanguageFromLocale(string locale)
        {
            return GetLanguagesByFilter(GetFilterValues(locale, true).ToArray());
        }
    }
}

ICountryService.cs

namespace Author.Query.Persistence.Interfaces
{
    public interface ICountryService
    {
        Task<CountryResult> GetAllCountriesAsync(LanguageDTO language, int pageNo, int pageSize);
        Task<CountryDTO> GetCountryAsync(LanguageDTO language, int countryId);
    }
}

CountryService.cs

namespace Author.Query.Persistence
{
    public class CountryService : ICountryService
    {
        private readonly AppDbContext _dbContext;
        private readonly IOptions<AppSettings> _appSettings;
        private readonly ICacheService<Images, ImageDTO> _cacheService;
        public CountryService(TaxathandDbContext dbContext, IOptions<AppSettings> appSettings, ICacheService<Images, ImageDTO> cacheService)
        {
            _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
            _cacheService = cacheService ?? throw new ArgumentNullException(nameof(cacheService));
            _appSettings = appSettings;
        }

        public async Task<CountryResult> GetAllCountriesAsync(LanguageDTO language, int pageNo, int pageSize)
        {
            var localeLangId = language.LanguageId;
            var dftLanguageId = int.Parse(_appSettings.Value.DefaultLanguageId);
            // By default pick the localLanguage value
            var countries = await GetAllCountriesDataAsync(localeLangId, pageNo, pageSize);
            // If localLanguage data is not available then pull the data based on default language
            if (countries.Countries.Count == 0)
            {
                countries = await GetAllCountriesDataAsync(dftLanguageId, pageNo, pageSize);
            }

            return countries;
        }

        public async Task<CountryDTO> GetCountryAsync(LanguageDTO language, int countryId)
        {
            var localeLangId = language.LanguageId;
            var dftLanguageId = int.Parse(_appSettings.Value.DefaultLanguageId);
            //var country = new CountryDTO();
            // By default pick the localLanguage value
            var country = await GetCountryDetailsAsync(countryId, localeLangId);
            // If localLanguage data is not available then pull the data based on default language
            if (country == null)
            {
                country = await GetCountryDetailsAsync(countryId, dftLanguageId);
            }

            return country;
        }

        private async Task<CountryDTO> GetCountryDetailsAsync(int countryId, int languageId)
        {
            var images = await _cacheService.GetAllAsync("imagesCacheKey");
            var country = await _dbContext.Countries.AsNoTracking().FirstOrDefaultAsync(c => c.CountryId.Equals(countryId) && c.IsPublished.Equals(true) && c.LanguageId.Equals(languageId));
            if (country == null)
            {
                return null;
            }

            var countryDTO = new CountryDTO{Uuid = country.CountryId, PNGImagePath = images.FirstOrDefault(im => im.ImageId.Equals(country.PNGImageId)).FilePath, SVGImagePath = images.FirstOrDefault(im => im.ImageId.Equals(country.SVGImageId)).FilePath, DisplayName = country.DisplayName, DisplayNameShort = country.DisplayName, Name = Helper.ReplaceChars(country.DisplayName), Path = Helper.ReplaceChars(country.DisplayName), CompleteResponse = true};
            return countryDTO;
        }

        private async Task<CountryResult> GetAllCountriesDataAsync(int languageId, int pageNo, int pageSize)
        {
            var countryList = new CountryResult();
            var images = await _cacheService.GetAllAsync("imagesCacheKey");
            var countries = await _dbContext.Countries.Where(cc => cc.IsPublished.Equals(true) && cc.LanguageId.Equals(languageId)).Select(c => new
            {
            c.CountryId, c.DisplayName, c.PNGImageId, c.SVGImageId
            }

            ).OrderByDescending(c => c.CountryId).Skip((pageNo - 1) * pageSize).Take(pageSize).AsNoTracking().ToListAsync();
            if (countries.Count == 0)
            {
                return null;
            }

            countryList.Countries.AddRange(countries.Select(co => new CountryDTO{Uuid = co.CountryId, PNGImagePath = images.FirstOrDefault(im => im.ImageId.Equals(co.PNGImageId)).FilePath, SVGImagePath = images.FirstOrDefault(im => im.ImageId.Equals(co.SVGImageId)).FilePath, DisplayName = co.DisplayName, DisplayNameShort = co.DisplayName, Name = Helper.ReplaceChars(co.DisplayName), Path = Helper.ReplaceChars(co.DisplayName), CompleteResponse = true}));
            return countryList;
        }
    }
}

Error:

    System.AggregateException
      HResult=0x80131500
      Message=Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Author.Query.New.API.GraphQL.Resolvers.ICountriesResolver Lifetime: Scoped ImplementationType: Author.Query.New.API.GraphQL.Resolvers.CountriesResolver': 
Unable to resolve service for type 'Author.Query.Persistence.Interfaces.ICountryService' while attempting to activate 'Author.Query.New.API.GraphQL.Resolvers.CountriesResolver'.) (Error while validating the service descriptor 'ServiceType: 
Author.Query.New.API.GraphQL.Resolvers.ICountryGroupsResolver Lifetime: Scoped ImplementationType: Author.Query.New.API.GraphQL.Resolvers.CountryGroupsResolver': Unable to resolve service for type 'Author.Query.Persistence.Interfaces.ICountryGroupService' while attempting to activate 'Author.Query.New.API.GraphQL.Resolvers.CountryGroupsResolver'.) (Error while validating the service descriptor 'ServiceType: Author.Query.New.API.GraphQL.Resolvers.IDisclaimerResolver Lifetime: Scoped ImplementationType: Author.Query.New.API.GraphQL.Resolvers.DisclaimerResolver': Unable to resolve service for type 'Author.Query.Persistence.Interfaces.IDisclaimerService' while attempting to activate 'Author.Query.New.API.GraphQL.Resolvers.DisclaimerResolver'.)
      Source=Microsoft.Extensions.DependencyInjection
      StackTrace:
       at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(IEnumerable`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 Author.Query.New.API.Program.Main(String[] args) in /src/QueryStack/Author.Query.New.API/Program.cs:line 15

    Inner Exception 1:
    InvalidOperationException: Error while validating the service descriptor 'ServiceType: Author.Query.New.API.GraphQL.Resolvers.ICountriesResolver Lifetime: Scoped ImplementationType: 
Author.Query.New.API.GraphQL.Resolvers.CountriesResolver': 
Unable to resolve service for type 'Author.Query.Persistence.Interfaces.ICountryService' while attempting to activate 
'Author.Query.New.API.GraphQL.Resolvers.CountriesResolver'.

    Inner Exception 2:
    InvalidOperationException: Unable to resolve service for type 
'Author.Query.Persistence.Interfaces.ICountryService' while attempting to activate 
'Author.Query.New.API.GraphQL.Resolvers.CountriesResolver'.

Can anyone help me to know how to fix this issue?

Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
santosh kumar patro
  • 7,231
  • 22
  • 71
  • 143
  • 1
    The exception says that no `ICountryService` service was found. The Startup.cs doesn't show anything registering `ICountryService` – Panagiotis Kanavos Jan 30 '20 at 10:31
  • BTW you should post the relevant information only. That image doesn't help at all - it just pushes the code to the second screen. `GraphQLQuery` and `AppSchema` aren't useful either, the error doesn't even mention them. At best, they should be the last code snippets. – Panagiotis Kanavos Jan 30 '20 at 10:33
  • Thanks @PanagiotisKanavos for your response. The only intention was to give a clear picture of the entire components with code and diagram.Here ICountryService is part of the assembly which contains the CommonService and that is reason I have added CommonService in the method: AddProjectRepositories of ProjectServiceCollectionExtensions.cs – santosh kumar patro Jan 30 '20 at 10:41
  • Is `ICountryService` registered anywhere? That's what the error complains about. The question doesn't contain any class that implements that interface either. – Panagiotis Kanavos Jan 30 '20 at 10:47
  • Is `AddProjectRepositories` to register it? For that to work, the interface would have to be implemented by a class ending in `Persistence` in the same assembly with `CommonService`. – Panagiotis Kanavos Jan 30 '20 at 10:49
  • Yes you are correct in making the statement that the method: AddProjectRepositories contains the logic to register the services and this is referenced in the ConfigureServices method of Startup.cs. And also the CommonService is present in the assembly that is ending with Persistence which I am trying to register using the nuget package: NetCore.AutoRegisterDi – santosh kumar patro Jan 30 '20 at 10:57
  • Post that code then. Remove the picture, GraphQLQuery and AppSchema and add those classes. For some reason, that class *isn't* found and registered. This is most likely a problem with the Reflection code. It's impossible to fix it without knowing that those types look like. Furthermore, how many types implement ICommonService? `CountriesResolver ` asks for a single instance. If there are 5 classes that implement the service, the DI container will have to pick one at random – Panagiotis Kanavos Jan 30 '20 at 11:01
  • Thanks a lot @PanagiotisKanavos for your response. I have updated the code – santosh kumar patro Jan 30 '20 at 12:05

3 Answers3

2

The error says that :

Unable to resolve service for type 'Author.Query.Persistence.Interfaces.ICountryService' while attempting to activate 'Author.Query.New.API.GraphQL.Resolvers.CountriesResolver'

ICountryService is never registered. AddGraphQLResolvers registers only ICountriesResolver, ICountryGroupsResolver and IDisclaimerResolver.

The method AddProjectRepositories only registers classes whose name ends in Persistence that appear in the same namespace as CommonService. It never registers CommonService itself.

The service must be registered, eg with :

services.AddScoped<ICommonService, CommonService>();
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
  • Thanks @PanagiotisKanavos for your response. The service layer in my project has many interfaces and its concrete implementation classes. I already did the service registration process for all the services but with the nuget package: NetCore.AutoRegisterDi (https://www.thereformedprogrammer.net/asp-net-core-fast-and-automatic-dependency-injection-setup/) this setup will be very much simplified. I don't have to do the registration process for the service classes. With the methods available as part of the nuget package it will scan the assembly and do the registration setup completely. – santosh kumar patro Jan 31 '20 at 16:26
  • @santoshkumarpatro the error says you do have to register the service. The code you posted *doesn't* register that service. That's a *fact*, not a matter of discussion. Your code registers classes that end in `Persistence`, not the `CommonService` class – Panagiotis Kanavos Jan 31 '20 at 16:33
  • 1
    @santoshkumarpatro why don't you just add `services.AddScoped();` and check what happens? – Panagiotis Kanavos Jan 31 '20 at 16:34
  • The AddProjectRepositories method also registers CommonService since it also ends with Persistence. All I am trying to do is register all the classes that has namespace ends with Persistence. It will be one time setup and I no need to worry about registering the additional classes that are going to added in the future. – santosh kumar patro Feb 19 '20 at 13:20
1

Your method AddProjectRepositories() does not register CountryService, because the Where filter in RegisterAssemblyPublicNonGenericClasses() looks at classes (Type), not at namespaces.

So perhaps you can change your filter:

services.RegisterAssemblyPublicNonGenericClasses(Assembly.GetAssembly(typeof(CommonService)))
                .Where(c => c.Name.EndsWith("Service")) // <-- Change "Persistence" to "Service"
                .AsPublicImplementedInterfaces(ServiceLifetime.Scoped);

That should register all classes that end with "Service" in the Assembly that contains CommonService.

veuncent
  • 1,599
  • 1
  • 20
  • 17
1

For me it was a simple mistake. I had a class with the same name in another namespace and referenced the wrong class/forgot to delete the duplicate class and add the correct one to my startup.

I had a service called UserService in myapp.Utils and another in myapp.Services. I was referencing myapp.Utils when I meant to delete that one and only use myapp.Services. I was incorrectly injecting the one in myapp.Utils when my controllers were set up to use the one in myapp.Services.

mbuchok
  • 391
  • 6
  • 18