DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
  1. DZone
  2. Coding
  3. Frameworks
  4. Registering a Type as an Interface and as Self With ASP.NET Core Dependency Injection

Registering a Type as an Interface and as Self With ASP.NET Core Dependency Injection

In this post, we go over how to use the built-in dependency injection that comes with the ASP.NET Core web development framework. Read on to get started!

Maarten Balliauw user avatar by
Maarten Balliauw
·
Oct. 28, 18 · Tutorial
Like (2)
Save
Tweet
Share
17.45K Views

Join the DZone community and get the full member experience.

Join For Free

While I am a big fan of using Autofac to serve as the framework for making Inversion of Control (IoC) and Dependency Injection (DI) work in an application, it is quite convenient in simple projects to use the built-in dependency injection in ASP.NET Core.

While it's simple to replace the default one with Autofac, the default one is often sufficient. Unless it's not!

Consider the following component registration:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // ...

        services.AddTransient<ICustomerService, DefaultCustomerService>();

        // ...
    }
}

With the default Microsoft.Extensions.DependencyInjection package in ASP.NET Core, we can now consume an ICustomerService in, for example, our controllers:

public class SupportController
{
    // DefaultCustomerService will be injected here:
    public SupportController(ICustomerService customerService)
    {
        // ...
    }
}

The above code will work fine, and where we expect a ICustomerService, we'll receive a DefaultCustomerService because that is what we registered.

Now, what will happen if at some point we want to be more specific? Let's say we have another controller where we really want to get a concrete DefaultCustomerService injected?

public class AnotherController
{
    public AnotherController(DefaultCustomerService customerService)
    {
        // ...
    }
}

This will blow up with a nice exception!

An unhandled exception occurred while processing the request. InvalidOperationException: Unable to resolve service for type 'DefaultCustomerService' while attempting to activate 'AnotherController'. Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, bool isDefaultParameterRequired)

The reason for the InvalidOperationException we get is that the service collection only contains a registration for ICustomerService, and not for DefaultCustomerService.

How to Register a Type as an Interface and as Self

So how can we solve this and register a type as an interface and as self? Googling/Binging/DuckDuckGoing reveals many developers who ran into this issue, and recommend registering our service twice, in any of the following forms:

// Using a factory to fetch the previously registered type
services.AddTransient<ICustomerService, DefaultCustomerService>();
services.AddTransient<DefaultCustomerService>(
    provider => provider.GetService<ICustomerService>());

// Using a concrete instance during registration,
// with the downside that this will always be registered as a singleton
// (and that we lose dependency injection in DefaultCustomerService itself)
var customerService = new DefaultCustomerService();
services.AddTransient<ICustomerService>(customerService);
services.AddTransient<DefaultCustomerService>(customerService);

All of the above will work, but it looks... ugly. Especially when a few instances need to be registered like this. Wouldn't it be nice if ASP.NET Core's built-in service collection supported registering types as a specific interface, and AsSelf(), much like Autofac supports? Something like this:

services.AddTransient<ICustomerService, DefaultCustomerService>().AsSelf();

The idea behind the ASP.NET Core dependency injection was that it should be sufficient for most scenarios, and that for more fine-grained control of dependencies we can always plug in another framework. But this is one of those cases where we'd need an overhaul of service registrations... Why not "make it work"?

Building an AsSelf() Extension Method

We can easily write an AsSelf() extension method on IServiceCollection, so that we can register a type "as self" as well. Our goal will be to write an extension method that pretty much resembles this solution from above:

// Using a factory to fetch the previously registered type
services.AddTransient<ICustomerService, DefaultCustomerService>();
services.AddTransient<DefaultCustomerService>(
    provider => provider.GetService<ICustomerService>());

Disclaimer: this is not a perfect solution in all cases and can easily go wrong with more registrations. Plug another framework when needed!

Boiler-Plate Extension Method

With that out of the way, let's start. We'll extend IServiceCollection, and since we want the code to flow a bit nicer where AsSelf() will follow the previous registration, we are interested in that one:

public static class ServiceCollectionHostedServiceExtensions
{
/// <summary>
/// Register the last registration as its own type.
/// </summary>
/// <returns>The original <see cref="T:Microsoft.Extensions.DependencyInjection.IServiceCollection" />.</returns>
public static IServiceCollection AsSelf(this IServiceCollection services)
{
var lastRegistration = services.LastOrDefault();
if (lastRegistration != null)
{
// TODO
}

return services;
}
}

Hardening

Let's also add some hardening: if the previous registration is already registered and the service type and implementation type are the same, we have no more work to do:

var implementationType = GetImplementationType(lastRegistration);

// When the last registration service type was already registered
// as its implementation type, bail out.
if (lastRegistration.ServiceType == implementationType)
{
    return services;
}

Note that we use a GetImplementationType() helper method which is in the full extension method code below — it helps us find the implementation type regardless of whether the previous registration was a concrete instance registration, a type registration, or a factory registration.

Registering a Single Instance "as Self"

Next up, let's cover the easiest case first: instance registration. Concrete instances are always registered as a singleton in ASP.NET Core's dependency injection framework, so we can mimic this behavior:

if (lastRegistration.ImplementationInstance != null)
{
// Register "self" registration as the same instance
services.Add(new ServiceDescriptor(
implementationType, 
lastRegistration.ImplementationInstance));
}

That's it, really. We repeat the last registration, just with the first argument passed to ServiceDescriptor as the implementation type instead of the service type. Our instance is now registered twice: once as the original registration, and once as its own type.

Side-Step to Registering a Type or a Type Factory "as Self"

Next up: type registration and factory registration. This is a little bit more complex because there is an edge case we must cover... let's side-step for a bit. The code we GoogleBingDucked previously has an issue waiting to happen. Look at this registration:

services.AddTransient<ICustomerService, DefaultCustomerService>();
services.AddTransient<DefaultCustomerService>(
    provider => provider.GetService<ICustomerService>());

Now what happens when we have multiple ICustomerService registered?

services.AddTransient<ICustomerService, OtherCustomerService>();
services.AddTransient<ICustomerService, DefaultCustomerService>();
services.AddTransient<DefaultCustomerService>(
    provider => provider.GetService<ICustomerService>());

Exactly: our concrete type registration may return OtherCustomerService in this case (I haven't actually tried to run this, but it looks suspicious, right?)

So we want to rewrite our registration for this case, and come up with this instead:

services.AddTransient<ICustomerService, OtherCustomerService>();
services.AddTransient<DefaultCustomerService>();
services.AddTransient<ICustomerService>(
    provider => provider.GetService<DefaultCustomerService>());

This would leave our original intent intact (multiple ICustomerService, but at least DefaultCustomerService would resolve the correct type.

Registering a Type or a Type Factory "as Self"

Back to our extension method! We want to start by removing the previous service registration. That's easy, right?

// Remove last registration
services.Remove(lastRegistration);

Next, we want to register our implementation type first, either using a factory method or as a type registration:

// Register "self" registration first
if (lastRegistration.ImplementationFactory != null)
{
        // Factory-based
        services.Add(new ServiceDescriptor(
            implementationType,
            lastRegistration.ImplementationFactory,
            lastRegistration.Lifetime));
    }
    else
    {
        // Type-based
        services.Add(new ServiceDescriptor(
            implementationType,
            implementationType, 
            lastRegistration.Lifetime));
    }

Since we removed the original registration, let's re-add it with a small modification: instead of being a full copy of the original registration, we will use a factory method to resolve the implementation type we just registered:

// Re-register last registration, proxying our specific registration
    services.Add(new ServiceDescriptor(
        lastRegistration.ServiceType,
        provider => provider.GetService(implementationType), 
        lastRegistration.Lifetime));

This method now helps us to register a type with the service collection twice, once as a service type and once as its own type, as intended:

services.AddTransient<ICustomerService, DefaultCustomerService>().AsSelf();

Completed Extension Method

Here's the complete extension method:

public static class ServiceCollectionHostedServiceExtensions
{
/// <summary>
/// Register the last registration as its own type.
/// </summary>
/// <returns>The original <see cref="T:Microsoft.Extensions.DependencyInjection.IServiceCollection" />.</returns>
public static IServiceCollection AsSelf(this IServiceCollection services)
{
var lastRegistration = services.LastOrDefault();
if (lastRegistration != null)
{
    var implementationType = GetImplementationType(lastRegistration);

            // When the last registration service type was already registered
            // as its implementation type, bail out.
            if (lastRegistration.ServiceType == implementationType)
            {
                return services;
            }

if (lastRegistration.ImplementationInstance != null)
{
// Register "self" registration as the same instance
services.Add(new ServiceDescriptor(
implementationType, 
lastRegistration.ImplementationInstance));
}
else 
{
// Remove last registration
services.Remove(lastRegistration);

// Register "self" registration first
if (lastRegistration.ImplementationFactory != null)
{
// Factory-based
services.Add(new ServiceDescriptor(
lastRegistration.ImplementationType,
lastRegistration.ImplementationFactory,
lastRegistration.Lifetime));
}
else
{
// Type-based
services.Add(new ServiceDescriptor(
lastRegistration.ImplementationType,
lastRegistration.ImplementationType, 
lastRegistration.Lifetime));
}

// Re-register last registration, proxying our specific registration
services.Add(new ServiceDescriptor(
lastRegistration.ServiceType,
provider => provider.GetService(implementationType), 
lastRegistration.Lifetime));
}
}

return services;
}

private static Type GetImplementationType(ServiceDescriptor descriptor)
{
if (descriptor.ImplementationType != null)
{
return descriptor.ImplementationType;
}

if (descriptor.ImplementationInstance != null)
{
return descriptor.ImplementationInstance.GetType();
}

if (descriptor.ImplementationFactory != null)
{
return descriptor.ImplementationFactory.GetType().GenericTypeArguments[1];
}

return null;
}
}

Again, this is not a perfect solution in all cases and can easily go wrong with more registrations. Plug another framework when needed! But for the simple cases where double registration is needed, this solution will work.

Enjoy!

ASP.NET Dependency injection ASP.NET Core Self (programming language) Interface (computing) Extension method

Published at DZone with permission of Maarten Balliauw, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • How to Submit a Post to DZone
  • What Was the Question Again, ChatGPT?
  • A Brief Overview of the Spring Cloud Framework
  • Microservices Discovery With Eureka

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: