Implement dependency injection outside of Startup.cs

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.


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

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.


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