有些服务无法使用NetCore.AutoRegisterDi库进行构建

5
我正在开发基于asp.net core 3.1的GraphQL API。我使用下面的参考文章来设置API层中的自动DI配置,并使用了Nuget包:NetCore.AutoRegisterDi。

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

以下是代码的详细信息:

代码:

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>();
}

在上面的代码中,CommonService 是服务层的一部分,最终与持久化结合。

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;
        }
    }
}

错误:
    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'.

有人能帮我知道如何解决这个问题吗?


1
异常显示找不到ICountryService服务。Startup.cs没有显示任何注册ICountryService的内容。 - Panagiotis Kanavos
顺便提一下,你应该只发布相关信息。那张图片没有任何帮助 - 它只是把代码推到了第二屏幕。GraphQLQueryAppSchema也没用,错误甚至都没有提到它们。最好的情况是,它们应该是最后的代码片段。 - Panagiotis Kanavos
是的,您说得对。方法AddProjectRepositories包含注册服务的逻辑,并且在Startup.cs文件的ConfigureServices方法中引用。同时,CommonService位于以Persistence结尾的程序集中,我正在尝试使用NuGet包NetCore.AutoRegisterDi进行注册。 - santosh kumar patro
把那段代码贴上来。移除图片、GraphQLQuery和AppSchema,然后添加那些类。由于某种原因,那个类没有被找到和注册。这很可能是反射代码的问题。如果不知道这些类型的样子,就无法修复它。此外,有多少类型实现了ICommonService?CountriesResolver只需要一个实例。如果有5个类实现了该服务,则DI容器将随机选择一个。 - Panagiotis Kanavos
非常感谢@PanagiotisKanavos的回复。我已经更新了代码。 - santosh kumar patro
显示剩余3条评论
3个回答

2
错误提示显示:
无法解析类型“Author.Query.Persistence.Interfaces.ICountryService”,因此尝试激活“Author.Query.New.API.GraphQL.Resolvers.CountriesResolver”失败。
“ICountryService”从未注册。 “AddGraphQLResolvers”仅注册“ICountriesResolver”、“ICountryGroupsResolver”和“IDisclaimerResolver”。
方法“AddProjectRepositories”仅注册名称以“Persistence”结尾的类,这些类出现在与“CommonService”相同的命名空间中。它从不注册“CommonService”本身。
必须注册服务,例如:
services.AddScoped<ICommonService, CommonService>();

感谢@PanagiotisKanavos的回复。我的项目中服务层有很多接口和它们的具体实现类。我已经为所有服务完成了服务注册过程,但通过Nuget包:NetCore.AutoRegisterDi(https://www.thereformedprogrammer.net/asp-net-core-fast-and-automatic-dependency-injection-setup/)这个设置会简化很多。我不必为服务类做注册处理。使用Nuget包提供的方法,它将扫描程序集并完全进行注册设置。 - santosh kumar patro
@santoshkumarpatro,错误提示说您需要注册服务。您发布的代码没有注册该服务。这是一个事实,而不是讨论的问题。您的代码注册以“Persistence”结尾的类,而不是“CommonService”类。 - Panagiotis Kanavos
1
@santoshkumarpatro 为什么不直接添加 services.AddScoped<ICommonService, CommonService>(); 然后看看会发生什么? - Panagiotis Kanavos
AddProjectRepositories方法还注册了CommonService,因为它也以Persistence结尾。我所要做的就是注册所有命名空间以Persistence结尾的类。这将是一次性的设置,我不需要担心将来添加的其他类的注册问题。 - santosh kumar patro

1
你的方法AddProjectRepositories()没有注册CountryService,因为RegisterAssemblyPublicNonGenericClasses()中的Where过滤器查看类(Type),而不是命名空间。因此,也许你可以更改过滤器:
services.RegisterAssemblyPublicNonGenericClasses(Assembly.GetAssembly(typeof(CommonService)))
                .Where(c => c.Name.EndsWith("Service")) // <-- Change "Persistence" to "Service"
                .AsPublicImplementedInterfaces(ServiceLifetime.Scoped);

那应该在包含CommonService的程序集中注册所有以"Service"结尾的类。

1
对我来说这只是一个简单的错误。我在另一个命名空间中有一个同名的类,引用了错误的类/忘记删除重复的类并将正确的类添加到我的启动项中。
我的应用程序中有一个名为UserService的服务,一个在myapp.Utils中,另一个在myapp.Services中。我本意是删除myapp.Utils并仅使用myapp.Services,但我却引用了myapp.Utils。当我的控制器被设置为使用myapp.Services时,我错误地注入了myapp.Utils中的服务。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接