简体   繁体   English

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

[英]Implement dependency injection outside of Startup.cs

I want to implement dependency injection in ASP.NET CORE 1 .我想在ASP.NET CORE 1 中实现依赖注入 I know everything is about DI in .Net Core.我知道一切都是关于 .Net Core 中的 DI。 For example例如

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

But for Big projects which has more than 20 entities and Services, it is so difficult and unreadable writing all of these code lines inside ConfigureServices .但是对于拥有 20 多个实体和服务的大型项目,在ConfigureServices 中编写所有这些代码行是非常困难和不可读的。 I want to know Is this possible implement dependency injection outside of Startup.cs and then add it to services.我想知道这是否可能在 Startup.cs 之外实现依赖注入,然后将其添加到服务中。

Thanks for answers.感谢您的回答。

you can write extension methods of IServiceCollection to encapsulate a lot of service registrations into 1 line of code in Startup.cs可以在Startup.cs中编写IServiceCollection的扩展方法,将大量的服务注册封装成一行代码

for example here is one from my project:例如,这是我的项目中的一个:

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


    }
}

and in Startup.cs I call that method with one line of code在 Startup.cs 中,我用一行代码调用该方法

services.AddCloudscribeCore(Configuration);

There are several approaches that can be taken, but some are simply moving code between classes;有几种方法可以采用,但有些只是在类之间移动代码; I suggest you consider Assembly Scanning as I describe as the second option below:我建议您考虑Assembly Scanning因为我将其描述为下面的第二个选项:

1. 'MOVE THE PROBLEM': EXTENSION METHODS 1.“解决问题”:扩展方法

The initial option is to use extension methods for configuration of Services.最初的选择是使用extension methods来配置服务。

Here is one example that wraps multiple service reigstrations into one extension method:这是一个将多个服务注册包装到一个扩展方法中的示例:

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

This can be called within ConfigureServices :这可以在ConfigureServices调用:

services.AddCustomServices();

Note: This is useful as a 'builder pattern', for specific configurations (for example, when a service needs multiple options to be passed to it), but, does not solve the problem of having to register multiple services by hand coding;注意:这对于特定配置(例如,当一个服务需要多个选项传递给它时)作为“构建器模式”很有用,但是,并不能解决必须通过手动编码注册多个服务的问题; it is essentially no different to writing the same code but in a different class file, and it still needs manual maintenance.本质上和在不同的类文件中编写相同的代码没有什么不同,仍然需要手动维护。

2. 'SOLVE THE PROBLEM': ASSEMBLY SCANNING 2.“解决问题”:组装扫描

The 'best practice' option is Assembly Scanning which is used to automatically find and Register components based on their Implemented Interfaces ; “最佳实践”选项是程序集扫描,用于根据已Implemented Interfaces自动查找和注册组件; below is an Autofac example:下面是一个 Autofac 示例:

var assembly= Assembly.GetExecutingAssembly();

builder.RegisterAssemblyTypes(assembly)
       .Where(t => t.Name.EndsWith("Repository"))
       .AsImplementedInterfaces();

One trick to handle lifetime (or scope) of registration, is to use a marker interface (an empty interface), for example IScopedService , and use that to scan for and register services with the appropriate lifetime.处理注册生命周期(或范围)的一个技巧是使用标记接口(空接口),例如IScopedService ,并使用它来扫描和注册具有适当生命周期的服务。 This is the lowest friction approach to registering multiple services, which is automatic, and therefore 'zero maintenance'.这是注册多个服务的最低摩擦方法,它是自动的,因此是“零维护”。

Note : The built in ASP.Net Core DI implementation does not support Assembly Scanning (as pf current, 2016 release);注意:内置的 ASP.Net Core DI 实现不支持Assembly Scanning (作为当前版本,2016 版); however, the Scrutor project on Github (and Nuget) adds this functionality, which condenses Service and Type registration to:然而,Github(和 Nuget)上的Scrutor项目添加了这个功能,它将服务和类型注册浓缩为:

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

SUMMARY :总结

Assembly Scanning , in combination with Extension Methods (where applicable) will save you a considerable amount of maintenance, and is performed once at application startup, and subsequently cached. Assembly ScanningExtension Methods (如果适用)相结合将为您节省大量维护,并且在应用程序启动时执行一次,随后被缓存。 It obviates the need to hand code service registrations.它消除了手动代码服务注册的需要。

You can write an extension method for batch registration:可以写一个批量注册的扩展方法:

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

And usage:和用法:

 services.AddScopedFromAssembly(assembly);

I recently implemented the Assembly scanning approach (successfully), but in the end found the cluster_registrations_in_a_few_extension_methods approach a lot clearer to read for myself and for other programmers working on it.我最近实现了汇编扫描方法(成功),但最后发现 cluster_registrations_in_a_few_extension_methods 方法更清晰,我自己和其他程序员都可以阅读。 If you keep the clustering of registrations close to where the registered classes are defined, maintenance is always a lot less work than the maintenance involved with the registered classes themselves.如果您将注册集群保持在注册类的定义位置附近,那么维护工作总是比注册类本身所涉及的维护工作少得多。

Add DependenciesManager class to your project and implement AddApplicationRepositories method.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);
                }
            }
        }
   }

In Startup class add services.AddApplicationRepositories();Startup类中添加services.AddApplicationRepositories(); in ConfigureServices method.ConfigureServices方法中。

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

In case you need to register different services, just implement more methods in DependenciesManager class.如果您需要注册不同的服务,只需在DependenciesManager类中实现更多方法即可。 For example, if you need to register some Authorization Handler services, just implement AddAuthorizationHandlers method:例如,如果你需要注册一些 AuthorizationHandler 服务,只需实现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);
            }
        }
    }

And in Startup class add:Startup类中添加:

 services.AddAuthorizationHandlers();

Notes: the names of the services and its implementation you want to register must end with "Repository" or "Handler" according to my answer.注意:根据我的回答,您要注册的服务及其实现的名称必须以“Repository”或“Handler”结尾。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM