Consider a scenario where you want to get a shopping cart object and have implemented IShoppingCart Interface. Now, you have multiple options to get the shopping cart, like from Database, API, or Cache. Here, we need to implement all three as concrete implementations for an interface, IShoppingCart. Now, the question is how we can decide which instance is to be instantiated, as generally, we see one implementation for one interface and add it into service collection for dependency injection. So, let’s see the implementation step by step.
Note: If you are new to DI, check out Dependency Injection In .NET Core.
Open Visual Studio and create a new project.
Select the API as the template and click OK.
namespace MultipleImplementation
{
public interface IShoppingCart
{
object GetCart();
}
}
Implement the GetCart method of the IShoppingCart interface in ShoppingCartCache.
namespace MultipleImplementation
{
public class ShoppingCartCache : IShoppingCart
{
public object GetCart()
{
return "Cart loaded from cache.";
}
}
}
Implement the same interface in ShoppingCartDB.
namespace MultipleImplementation
{
public class ShoppingCartDB : IShoppingCart
{
public object GetCart()
{
return "Cart loaded from DB";
}
}
}
At last, implement the same interface in ShoppingCartAPI.
namespace MultipleImplementation
{
public class ShoppingCartAPI : IShoppingCart
{
public object GetCart()
{
return "Cart loaded through API.";
}
}
}
Now, we need a repository that will internally decide which implementation should be instantiated and called to get the GetCart method from it. So, create IShoppingCartRepository having the GetCart method.
namespace MultipleImplementation
{
public interface IShoppingCartRepository
{
object GetCart();
}
}
Create an enum to select the concrete implementation type, i.e, Cache, DB, or API.
namespace MultipleImplementation
{
public class Constants
{
}
public enum CartSource
{
Cache = 1,
DB = 2,
API = 3
}
}
In the implementation of IShoppingCartRepository, we use constructor injection, and we take Func delegate as a parameter. Func delegate expects a string as a parameter and IShoppingCart as a return value. So in the GetCart method, you can see that we are using an enum to pass the parameter value, which indicates the type of implementation we want to instantiate.
using System;
namespace MultipleImplementation
{
public class ShoppingCartRepository : IShoppingCartRepository
{
private readonly Func<string, IShoppingCart> shoppingCart;
public ShoppingCartRepository(Func<string, IShoppingCart> shoppingCart)
{
this.shoppingCart = shoppingCart;
}
public object GetCart()
{
return shoppingCart(CartSource.DB.ToString()).GetCart();
}
}
}
In the same way, we need to add Func in the startup class. First, we add the concrete implementation in service collection, and based on the parameter value, we used to get that concrete class on each requestup.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
namespace MultipleImplementation
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IShoppingCartRepository, ShoppingCartRepository>();
services.AddSingleton<ShoppingCartCache>();
services.AddSingleton<ShoppingCartDB>();
services.AddSingleton<ShoppingCartAPI>();
services.AddTransient<Func<string, IShoppingCart>>(serviceProvider => key =>
{
switch (key)
{
case "API":
return serviceProvider.GetService<ShoppingCartAPI>();
case "DB":
return serviceProvider.GetService<ShoppingCartDB>();
default:
return serviceProvider.GetService<ShoppingCartCache>();
}
});
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
}
}
}
You can download the sample code from the top of this article.

Byron ByronPosted Nov 2, 2021, 4:45 PM
Excelent article.
Rippah RippahPosted Jul 22, 2021, 12:52 PM
How do I run this ? How to choose which implementation i want to use ?
Hamid KhanPosted Jun 7, 2021, 7:09 PM
Very nice explained
Rohit RaoPosted Sep 13, 2020, 9:56 AM
Good Article. Why you keep those three (Cache, DB, API) as Singleton? If it is a singleton then you will have the single shared object among all your users. It could be scope or transient.
Adip PrithviPosted Jun 18, 2020, 1:44 PM
Very nice Article. Thanks for posting it. This is an awesome example covering the use of single interface multiple implementations via Func. For me the in the lambda for the provider we should create a new Func<string, IShoppingCart>; and then comes the key as the input. In summary, the code should look like : services.AddTransient<Func<string, IShoppingCart>>(serviceProvider => new Func<string,IShoppingCart>(key => { switch (key) { case "API": return serviceProvider.GetService<ShoppingCartAPI>(); case "DB": return serviceProvider.GetService<ShoppingCartDB>(); default: return serviceProvider.GetService<ShoppingCartCache>(); } }));
lijo johnPosted Apr 27, 2020, 12:33 AM
Case "API": return serviceProvider.GetService<ShoppingCartAPI>(); The above code presentation layer has a direct reference to the service implementation layer. That means it is not loosely coupled. Other than the scope of an object, how can it become loosely coupled or why we referer implementation layer directly in startup class?
Walter CardosoPosted Jan 1, 2020, 6:44 PM
Akshal Thanks! Your article is amazing, help me a lot!