Skip to content

Understanding Dependency Injection

Oleg Karasik edited this page Dec 4, 2019 · 13 revisions

Hierarchical Dependencies Registration

All service components are organized in the hierarchy. Each hierarchy level has access to all own registered dependencies as well as to all dependencies registered on the higher level.

  • host - all dependencies registered on HostBuilder.
    • service - all dependencies registered inside DefineStatefulService(...) or DefineStatelessService(...) method and above.
      • event source - all dependencies registered inside SetupEventSource(...) method and above
      • delegate - all dependencies registered inside DefineDelegate(...) method and above
      • listener - all dependencies registered inside DefileAspNetCoreListener(...), DefineRemotingListener(...) or DefineGenericListener(...) method and above.

This structure allows registration of shared dependencies on the higher level and their propagation to lower levels. This is called Hierarchical Dependencies Registration.

Imagine the following example:

You want to create a stateful service with two endpoints (ASP.NET Core and Remoting) and configure a background job. All these components should have access to same singleton instance of the ISingletonService and have a separate instances of IScopedService and ITransientService.

Here is how this can be implemented it without Hierarchical Dependencies Registration:

var singleton = new SingletonService();
new HostBuilder()
  .ConfigureServices( 
    services =>
    {
      services.AddScoped<IScopedService, ScopedService>();
      services.AddTransient<ITransientService, TransientService>();
      services.AddSingleton(singleton);
    })
  .DefineStatefulServiceHost(
    serviceBuilder =>
    {
      serviceBuilder
        .DefineDelegate(
          delegateBuilder => 
          {
            delegateBuilder.ConfigureDependencies(
              dependencies =>
              {
                dependencies.AddScoped<IScopedService, ScopedService>();
                dependencies.AddTransient<ITransientService, TransientService>();
                dependencies.AddSingleton(singleton);
              });
          })
        .DefineAspNetCoreListener(
          listenerBuilder =>
          {
            listenerBuilder.ConfigureWebHost(
              webHostBuilder =>
              {
                webHostBuilder.ConfigureServices(
                  services => 
                  {
                    services.AddScoped<IScopedService, ScopedService>();
                    services.AddTransient<ITransientService, TransientService>();
                    services.AddSingleton(singleton);
                  })
              });
          })
        .DefineRemotingListener(
          listenerBuilder =>
          {
            listenerBuilder.ConfigureDependencies(
              dependencies =>
              {
                dependencies.AddScoped<IScopedService, ScopedService>();
                dependencies.AddTransient<ITransientService, TransientService>();
                dependencies.AddSingleton(singleton);
              });
          });
    })
  .Build();

While this implementation has a lot of duplications it still works. But what if implementation of ISingletonService has dependencies? Imaging the implementation of ISingletonService depends on the IConfiguration or IHostedEnvironment services configured by HostBuilder. In this case we can't instantiate ISingletonService as before.

Let's see how this can be implement with Hierarchical Dependencies Registration:

new HostBuilder()
  .ConfigureServices( 
    services =>
    {
      services.AddScoped<IScopedService, ScopedService>();
      services.AddTransient<ITransientService, TransientService>();
      services.AddSingleton<ISingletonService, SingletonService>();
    })
  .DefineStatefulServiceHost(
    serviceBuilder =>
    {
      serviceBuilder
        .DefineDelegate(delegateBuilder => { ... })
        .DefineAspNetCoreListener(listenerBuilder => { ... })
        .DefineRemotingListener(listenerBuilder => { ... });
    })
  .Build();

That's it.

Here are important internal details about Hierarchical Dependencies Registration:

  1. All components have own dependency injection container. The singleton services are proxied while scoped and transient services are re-registered.
  2. Dependency registration isn't proxied or re-registered if same dependency is registered on the lower level:
    new HostBuilder()
      .ConfigureServices( 
        services =>
        {
          services.AddTransient<ITransientService, RootTransientService>();
        })
      .DefineStatefulServiceHost(
        serviceBuilder =>
        {
          serviceBuilder
            .ConfigureDependencies(
              dependencies =>
              {
                // All lower levels will now have  LocalTransientService 
                // implementation instead of RootTransientService.
                services.AddTransient<ITransientService, LocalTransientService>();
              });
        })
      .Build();
  3. The dependencies of the following types aren't proxied or re-registered:
    • IHostedService

Built-in dependencies

There are multiple dependencies registered automatically by the infrastructure on all hierarchy levels.

Host

Service

These dependencies are accessible in both stateful and stateless services:

These dependencies are specific to stateful service:

These dependencies are specific to stateless service:

Event Source

There is no special built-in dependencies registered for event source.

Delegate

These dependencies are accessible in both stateful and stateless services:

  • IServiceEventSource (and all specialized interfaces)

    public interface IServiceEventSource
    {
      void WriteEvent<T>(
        ref T eventData)
        where T : ServiceEventSourceData;
    }

These dependencies are specific to stateful service:

  • IStatefulServiceDelegateInvocationContext

    public interface IStatefulServiceDelegateInvocationContext
    {
      StatefulServiceLifecycleEvent Event { get; }
    }

These dependencies are specific to stateless service:

  • IStatelessServiceDelegateInvocationContext

    public interface IStatelessServiceDelegateInvocationContext
    {
      StatelessServiceLifecycleEvent Event { get; }
    }

Listener

These dependencies are accessible in all listeners:

  • IServiceEventSource (and all specialized interfaces)

    public interface IServiceEventSource
    {
      void WriteEvent<T>(
        ref T eventData)
        where T : ServiceEventSourceData;
    }
  • IServiceHostListenerInformation

    public interface IServiceHostListenerInformation
    {
      string EndpointName { get; }
    }

These services are specific to ASP.NET Core listeners:

  • IServiceHostAspNetCoreListenerInformation

    public interface IServiceHostAspNetCoreListenerInformation : IServiceHostListenerInformation
    {
      // The URL suffix set by Service Fabric middleware 
      // when setting UseUniqueServiceUrlIntegration() is on.
      string UrlSuffix { get; }
    }

These services are specific to Remoting listeners:

  • IServiceHostRemotingListenerInformation

    public interface IServiceHostRemotingListenerInformation : IServiceHostListenerInformation
    {
    }

These services are specific to Generic listeners:

  • IServiceHostGenericListenerInformation

    public interface IServiceHostGenericListenerInformation : IServiceHostListenerInformation
    {
    }