在 Startup.cs 之外实施依赖注入
Implement dependency injection outside of Startup.cs
我想在 ASP.NET 核心 1 中实施 依赖注入 。我知道 .Net Core 中的一切都是关于 DI 的。例如
public void ConfigureServices(IServiceCollection services)
{
// Add application services.
services.AddTransient<IDateTime, SystemDateTime>();
}
但是对于拥有超过 20 个实体和服务的大项目,在 ConfigureServices 中编写所有这些代码行是非常困难和不可读的。我想知道这是否可能在 Startup.cs 之外实施依赖注入,然后将其添加到服务中。
感谢您的回答。
可以在Startup.cs
中编写IServiceCollection的扩展方法将大量服务注册封装到1行代码中
例如,这是我项目中的一个:
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);
可以采用多种方法,但有些只是在 class 之间移动代码;我建议您考虑 Assembly Scanning
,正如我在下面描述的第二个选项:
1. 'MOVE THE PROBLEM':扩展方法
初始选项是使用 extension methods
来配置服务。
这是一个将多个服务注册包装到一个扩展方法中的示例:
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();
注意: 这对于特定配置(例如,当服务需要向其传递多个选项时)作为 'builder pattern' 很有用,但是,没有解决必须手动编码注册多个服务的问题;这与编写相同的代码但在不同的 class 文件中本质上没有区别,并且仍然需要手动维护。
2。 'SOLVE THE PROBLEM':装配扫描
'best practice'选项是Assembly Scanning,用于根据Implemented Interfaces
自动查找和注册组件;下面是一个 Autofac 示例:
var assembly= Assembly.GetExecutingAssembly();
builder.RegisterAssemblyTypes(assembly)
.Where(t => t.Name.EndsWith("Repository"))
.AsImplementedInterfaces();
处理注册生命周期(或范围)的一个技巧是使用标记接口(空接口),例如 IScopedService
,并使用它来扫描和注册具有适当生命周期的服务.这是注册多项服务的最低摩擦方法,它是自动的,因此 'zero maintenance'.
注意:内置ASP.Net核心DI实现不支持Assembly Scanning
(如pf current,2016版本);然而,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());
摘要:
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);
我最近(成功地)实现了程序集扫描方法,但最终发现 cluster_registrations_in_a_few_extension_methods 方法对于我自己和其他使用它的程序员来说更清晰。
如果您将注册集群保持在注册 类 的定义位置附近,维护工作总是比注册 类 本身涉及的维护工作少得多。
将 DependenciesManager
class 添加到您的项目并实施 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
class中添加services.AddApplicationRepositories();
在ConfigureServices
方法中。
public void ConfigureServices(IServiceCollection services)
{
services.AddApplicationRepositories();
}
如果您需要注册不同的服务,只需实现DependenciesManager
class中的更多方法即可。比如需要注册一些Authorization Handler服务,实现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
class 中添加:
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.
我想在 ASP.NET 核心 1 中实施 依赖注入 。我知道 .Net Core 中的一切都是关于 DI 的。例如
public void ConfigureServices(IServiceCollection services)
{
// Add application services.
services.AddTransient<IDateTime, SystemDateTime>();
}
但是对于拥有超过 20 个实体和服务的大项目,在 ConfigureServices 中编写所有这些代码行是非常困难和不可读的。我想知道这是否可能在 Startup.cs 之外实施依赖注入,然后将其添加到服务中。
感谢您的回答。
可以在Startup.cs
中编写IServiceCollection的扩展方法将大量服务注册封装到1行代码中例如,这是我项目中的一个:
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);
可以采用多种方法,但有些只是在 class 之间移动代码;我建议您考虑 Assembly Scanning
,正如我在下面描述的第二个选项:
1. 'MOVE THE PROBLEM':扩展方法
初始选项是使用 extension methods
来配置服务。
这是一个将多个服务注册包装到一个扩展方法中的示例:
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();
注意: 这对于特定配置(例如,当服务需要向其传递多个选项时)作为 'builder pattern' 很有用,但是,没有解决必须手动编码注册多个服务的问题;这与编写相同的代码但在不同的 class 文件中本质上没有区别,并且仍然需要手动维护。
2。 'SOLVE THE PROBLEM':装配扫描
'best practice'选项是Assembly Scanning,用于根据Implemented Interfaces
自动查找和注册组件;下面是一个 Autofac 示例:
var assembly= Assembly.GetExecutingAssembly();
builder.RegisterAssemblyTypes(assembly)
.Where(t => t.Name.EndsWith("Repository"))
.AsImplementedInterfaces();
处理注册生命周期(或范围)的一个技巧是使用标记接口(空接口),例如 IScopedService
,并使用它来扫描和注册具有适当生命周期的服务.这是注册多项服务的最低摩擦方法,它是自动的,因此 'zero maintenance'.
注意:内置ASP.Net核心DI实现不支持Assembly Scanning
(如pf current,2016版本);然而,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());
摘要:
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);
我最近(成功地)实现了程序集扫描方法,但最终发现 cluster_registrations_in_a_few_extension_methods 方法对于我自己和其他使用它的程序员来说更清晰。 如果您将注册集群保持在注册 类 的定义位置附近,维护工作总是比注册 类 本身涉及的维护工作少得多。
将 DependenciesManager
class 添加到您的项目并实施 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
class中添加services.AddApplicationRepositories();
在ConfigureServices
方法中。
public void ConfigureServices(IServiceCollection services)
{
services.AddApplicationRepositories();
}
如果您需要注册不同的服务,只需实现DependenciesManager
class中的更多方法即可。比如需要注册一些Authorization Handler服务,实现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
class 中添加:
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.