22
votes

I want to implement dependency injection in ASP.NET CORE 1. I know everything is about DI in .Net Core. 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. I want to know Is this possible implement dependency injection outside of Startup.cs and then add it to services.

Thanks for answers.

5
you can write extension methods of IServiceCollection that add lots of things with one line of code per extension method in StartupJoe Audette
I know extension method. But for example can you write answer?Elvin Mammadov
What about applying the "extract method" refactoring on a group of registrations? Later on these methods can be moved to other classes as well.Steven
@Steven , can you write example code as answer?Elvin Mammadov

5 Answers

24
votes

you can write extension methods of IServiceCollection to encapsulate a lot of service registrations into 1 line of code in Startup.cs

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

services.AddCloudscribeCore(Configuration);
12
votes

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:

1. 'MOVE THE PROBLEM': EXTENSION METHODS

The initial option is to use extension methods for configuration of Services.

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:

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

The 'best practice' option is Assembly Scanning which is used to automatically find and Register components based on their Implemented Interfaces; below is an Autofac example:

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. 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); however, the Scrutor project on Github (and Nuget) adds this functionality, which condenses Service and Type registration to:

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. It obviates the need to hand code service registrations.

4
votes

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);
0
votes

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

0
votes

Add DependenciesManager class to your project and implement AddApplicationRepositories method.

 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(); in ConfigureServices method.

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

In case you need to register different services, just implement more methods in DependenciesManager class. For example, if you need to register some Authorization Handler services, just implement AddAuthorizationHandlers method:

 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:

 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.