在Startup.cs之外实现依赖注入

本文关键字:实现 依赖 注入 Startup cs | 更新日期: 2023-09-27 18:15:34

我想在ASP中实现依赖注入。Net core 1。我知道。net Core中的一切都是关于DI的。例如

   public void ConfigureServices(IServiceCollection services)
   {
      // Add application services.
     services.AddTransient<IDateTime, SystemDateTime>();
   }

但是对于有超过20个实体和服务的大项目,在ConfigureServices中编写所有这些代码行是非常困难和不可读的。我想知道这是否可能在Startup.cs之外实现依赖注入,然后将其添加到服务中。

在Startup.cs之外实现依赖注入

你可以编写IServiceCollection的扩展方法,将大量的服务注册封装到Startup.cs

的一行代码中。

举个例子,这是我项目中的一个:

using cloudscribe.Core.Models;
using cloudscribe.Core.Models.Setup;
using cloudscribe.Core.Web;
using cloudscribe.Core.Web.Components;
using cloudscribe.Core.Web.Components.Editor;
using cloudscribe.Core.Web.Components.Messaging;
using cloudscribe.Core.Web.Navigation;
using cloudscribe.Web.Common.Razor;
using cloudscribe.Web.Navigation;
using cloudscribe.Web.Navigation.Caching;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Options;
using System.Reflection;
using Microsoft.AspNetCore.Authorization;
namespace Microsoft.Extensions.DependencyInjection
{
    public static class StartupExtensions
    {
        public static IServiceCollection AddCloudscribeCore(this IServiceCollection services, IConfigurationRoot configuration)
        {
            services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
            services.Configure<MultiTenantOptions>(configuration.GetSection("MultiTenantOptions"));
            services.Configure<SiteConfigOptions>(configuration.GetSection("SiteConfigOptions"));
            services.Configure<UIOptions>(configuration.GetSection("UIOptions"));
            services.Configure<CkeditorOptions>(configuration.GetSection("CkeditorOptions"));
            services.Configure<CachingSiteResolverOptions>(configuration.GetSection("CachingSiteResolverOptions"));
            services.AddMultitenancy<SiteContext, CachingSiteResolver>();
            services.AddScoped<CacheHelper, CacheHelper>();
            services.AddScoped<SiteManager, SiteManager>();
            services.AddScoped<GeoDataManager, GeoDataManager>();
            services.AddScoped<SystemInfoManager, SystemInfoManager>();
            services.AddScoped<IpAddressTracker, IpAddressTracker>();
            services.AddScoped<SiteDataProtector>();
            services.AddCloudscribeCommmon();
            services.AddScoped<ITimeZoneIdResolver, RequestTimeZoneIdResolver>();
            services.AddCloudscribePagination();
            services.AddScoped<IVersionProviderFactory, VersionProviderFactory>();
            services.AddScoped<IVersionProvider, CloudscribeCoreVersionProvider>();
            services.AddTransient<ISiteMessageEmailSender, SiteEmailMessageSender>();
            services.AddTransient<ISmsSender, SiteSmsSender>();
            services.AddSingleton<IThemeListBuilder, SiteThemeListBuilder>();
            services.TryAddScoped<ViewRenderer, ViewRenderer>();
            services.AddSingleton<IOptions<NavigationOptions>, SiteNavigationOptionsResolver>();
            services.AddScoped<ITreeCacheKeyResolver, SiteNavigationCacheKeyResolver>();
            services.AddScoped<INodeUrlPrefixProvider, FolderTenantNodeUrlPrefixProvider>();
            services.AddCloudscribeNavigation(configuration);
            services.AddCloudscribeIdentity();
            return services;
        }

    }
}

在startup。cs中我用一行代码调用这个方法

services.AddCloudscribeCore(Configuration);

可以采用几种方法,但有些方法只是在类之间移动代码;我建议您考虑Assembly Scanning,我将其描述为下面的第二个选项:

1。'移动问题':扩展方法

初始选项是使用extension methods配置Services。

下面是一个将多个服务注册封装到一个扩展方法中的示例:

    public static IServiceCollection AddCustomServices(this IServiceCollection services)
    {
        services.AddScoped<IBrowserConfigService, BrowserConfigService>();
        services.AddScoped<IManifestService, ManifestService>();
        services.AddScoped<IRobotsService, RobotsService>();
        services.AddScoped<ISitemapService, SitemapService>();
        services.AddScoped<ISitemapPingerService, SitemapPingerService>();
        // Add your own custom services here e.g.
        // Singleton - Only one instance is ever created and returned.
        services.AddSingleton<IExampleService, ExampleService>();
        // Scoped - A new instance is created and returned for each request/response cycle.
        services.AddScoped<IExampleService, ExampleService>();
        // Transient - A new instance is created and returned each time.
        services.AddTransient<IExampleService, ExampleService>();
        return services;
    }

可以在ConfigureServices中调用:

services.AddCustomServices();

注意:对于特定的配置(例如,当一个服务需要多个选项传递给它时),这是一个有用的"构建器模式",但是,不能解决必须通过手工编码注册多个服务的问题;从本质上讲,这与在不同的类文件中编写相同的代码没有什么不同,并且仍然需要人工维护。

2。"解决问题":汇编扫描

"最佳实践"选项是汇编扫描,用于根据其Implemented Interfaces自动查找和注册组件;下面是一个autoface的例子:

var assembly= Assembly.GetExecutingAssembly();
builder.RegisterAssemblyTypes(assembly)
       .Where(t => t.Name.EndsWith("Repository"))
       .AsImplementedInterfaces();
处理注册的生存期(或作用域)的一个技巧是使用一个标记接口(一个空接口),例如IScopedService,并使用它来扫描和注册具有适当生存期的服务。这是注册多个服务的摩擦最小的方法,它是自动的,因此是"零维护"。

注意:内置的ASP。Net Core DI实现不支持Assembly Scanning(截至目前,2016版本);但是,Github(和Nuget)上的Scrutor项目添加了这个功能,它将Service和Type注册压缩为:

var collection = new ServiceCollection();
collection.Scan(scan => scan
    .FromAssemblyOf<ITransientService>()
        .AddClasses(classes => classes.AssignableTo<ITransientService>())
            .AsImplementedInterfaces()
            .WithTransientLifetime()
        .AddClasses(classes => classes.AssignableTo<IScopedService>())
            .As<IScopedService>()
            .WithScopedLifetime());

:

Assembly Scanning,结合Extension Methods(如果适用)将为您节省大量的维护,并且在应用程序启动时执行一次,随后进行缓存。它避免了手工编码服务注册的需要。

你可以写一个批量注册的扩展方法:

    public static void AddScopedFromAssembly(this IServiceCollection services, Assembly assembly)
    {
        var allServices = assembly.GetTypes().Where(p =>
            p.GetTypeInfo().IsClass &&
            !p.GetTypeInfo().IsAbstract);
        foreach (var type in allServices)
        {
            var allInterfaces = type.GetInterfaces();
            var mainInterfaces = allInterfaces.Except
                    (allInterfaces.SelectMany(t => t.GetInterfaces()));
            foreach (var itype in mainInterfaces)
            {
                services.AddScoped(itype, type); // if you want you can pass lifetime as a parameter
            }
        }
    }

和用法:

 services.AddScopedFromAssembly(assembly);

DependenciesManager类添加到您的项目中并实现AddApplicationRepositories方法

 public static class DependenciesManager
 {
        public static void AddApplicationRepositories(this IServiceCollection service)
        {
            var assembly = Assembly.GetExecutingAssembly();
            var services = assembly.GetTypes().Where(type =>
            type.GetTypeInfo().IsClass && type.Name.EndsWith("Repository") &&
            !type.GetTypeInfo().IsAbstract);
      
            foreach (var serviceType in services)
            {
                var allInterfaces = serviceType.GetInterfaces();
                var mainInterfaces = allInterfaces.Except
                (allInterfaces.SelectMany(t => t.GetInterfaces()));
                foreach (var iServiceType in mainInterfaces)
                {
                    service.AddScoped(iServiceType, serviceType);
                }
            }
        }
   }

Startup类中添加services.AddApplicationRepositories();ConfigureServices方法中

public void ConfigureServices(IServiceCollection services)
{
     services.AddApplicationRepositories();
}

如果您需要注册不同的服务,只需在DependenciesManager类中实现更多的方法。例如,如果您需要注册一些授权处理程序服务,只需实现AddAuthorizationHandlers方法:

 public static void AddAuthorizationHandlers(this IServiceCollection service)
  {
        var assembly = Assembly.GetExecutingAssembly();
        var services = assembly.GetTypes().Where(type =>
            type.GetTypeInfo().IsClass && type.Name.EndsWith("Handler") &&
            !type.GetTypeInfo().IsAbstract);
        foreach (var serviceType in services)
        {
            var allInterfaces = serviceType.GetInterfaces();
            var mainInterfaces = allInterfaces.Except
                (allInterfaces.SelectMany(t => t.GetInterfaces()));
            foreach (var iServiceType in mainInterfaces)
            {
                service.AddScoped(iServiceType, serviceType);
            }
        }
    }

Startup类中添加:

 services.AddAuthorizationHandlers();

注意:要注册的服务及其实现的名称必须以"Repository"结尾。或";Handler"根据我的答案。

目前的答案是:

  1. IServiceCollection创建封装多个相关服务注册的扩展方法
  2. 编写一个注册服务的程序集扫描程序。

我想补充第三个想法:

3。不要过多地注册为服务——使用IGet实例化"高层次"。类。您需要注册"较低级别"。像IConnectionFactoryIEmailSender这样的东西,但"更高级别"。

那些只在一两个页面上使用的东西可能不应该添加到你的服务中。

这可能是人们拥有的一些Repository类只在一两个页面上使用的情况。这些"handlers"

也可以通过IGet实例化。

我最近实现了汇编扫描方法(成功地),但最后发现cluster_registrations_in_a_few_extension_methods方法对于我自己和其他使用它的程序员来说要清晰得多。如果您将注册集群保持在已注册类的定义位置附近,那么维护工作总是比维护已注册类本身要少得多。