Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Injecting Dependency programmatically asp.net core

I'm just starting with Asp.net core Dependency Injection, and my concept could be inaccurate. This docs.asp.net post explains how to inject context to a controller. I have little confusion regarding injection, in testing perspective. Assume we have following scenario:

public interface ITasksRepository
{ 
   public void Create();
}

//This is fake implementation, using fake DbContext for testing purpose
public class TasksRepositoryFake : ITasksRepository
{
   public void Create()
   {
     FakeDbContext.Add(sometask);
     //logic;
   }
}

//This is actual implementation, using actual DbContext
public class TasksRepository : ITasksRepository
{
   public void Create()
   {
     DbContext.Add(someTask);
     //logic;
   }
}

Now in order to inject context in controller, we design it as:

public class TasksController : Controller
{
    public ITasksRepository TaskItems { get; set; }

    public TodoController(ITaskRepository taskItems)
    {
        TaskItems = taskItems;
    }
    //other logic
 }

What asp.net core provides as builtin feature is, we can register the dependency injection in startup class as follows:

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc();
    services.AddSingleton<ITasksRepository, TasksRepositoryFake>();
}

According to this logic, my TaskRepositoryFake will be injected to the controller. So far, everything is clear. My questions/confusions regarding this are as follows:

Questions:

  • How can I use this builtin DI feature to inject the context using some logic? May be programatically, or configuration based, or environment based? (for example, always inject fake context, when using 'test' environment? etc.)
  • Is it even possible? If we always have to change this manually in StartUp class, then how does this builtin DI feature serve us? Because we could have simply done that in controller, without this feature.
like image 875
Zeeshan Avatar asked Aug 21 '16 14:08

Zeeshan


People also ask

What is dependency injection in .NET Core with example?

Dependency injection is the design pattern that allows us to inject the dependency into the class from the outer world rather than creating with in class. This will help us to create a loosely coupled applications so that it has provided greater maintainability, testability, and also reusability.

How many types of dependency injection are there in ASP.NET Core?

NET Core provides three kinds of dependency injection, based in your lifetimes: Transient: services that will be created each time they are requested. Scoped: services that will be created once per client request (connection) Singleton: services that will be created only at the first time they are requested.

What is @inject in ASP.NET Core?

From ASP.NET Core, one way to supply the data is Custom service that is used via DI (dependency injection). However, it is not a best practice. @inject directive. The @inject directive is used to inject the dependency into a View.


2 Answers

First to answer your question: Yes, you can inject the dependency programmatically. By using factories, as is always the case with injecting dependencies based on run-time values. The AddSingleton has an overload which takes an implementationfactory and so a basic example for your use case looks like:

   public class Startup
{
    public bool IsTesting { get; }

    public Startup(IHostingEnvironment env)
    {
        IsTesting = env.EnvironmentName == "Testing";
    }

    // This method gets called by the runtime. Use this method to add services to the container.
    // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<ISomeRepository>(sp => IsTesting ? (ISomeRepository)new SomeRepository() : (ISomeRepository) new FakesomeRepository());
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, ISomeRepository someRepository)
    {
        app.UseIISPlatformHandler();

        app.Run(async (context) =>
        {
            await context.Response.WriteAsync($"Hello World from {nameof(someRepository)}!");
        });
    }

    // Entry point for the application.
    public static void Main(string[] args) => WebApplication.Run<Startup>(args);
}

The concerning line of code for your TasksRepository would look like:

services.AddSingleton<ITaskRepository>(sp => isTesting?(ITasksRepository)new TasksRepositoryFake(): (ITasksRespository)new TasksRepository() );

Even better would be to put it in a factory (again with my example):

services.AddSingleton<ISomeRepository>(sp => SomeRepositoryFactory.CreatSomeRepository(IsTesting));

I hope you see how this helps you setting it up config based, environment based, or however you want. I you are interested I wrote more about DI based on run-time values via abstract factories here.

Having said that, with unit tests I would simply inject my fakes in the classes that are under test. Unit tests are there to still prove to yourself and your co-workers that the code still does as intended. And with integration tests I would make a special StartUp class with all my fakes and give it to the test host as ASP.NET Core allows you to do. You can read more about the test host here: https://docs.asp.net/en/latest/testing/integration-testing.html

Hope this helps.

Update Added cast to interface because the ternary conditional has no way of telling. Plus added some basic samples.

like image 121
Danny van der Kraan Avatar answered Oct 26 '22 22:10

Danny van der Kraan


You can inject your dependencies configuration based, or environment based, or both.

Option 1 : Environment Based

    public IHostingEnvironment env{ get; set; }
    public Startup(IHostingEnvironment env)
    {
        this.env = env;
    } 
    public void ConfigureServices(IServiceCollection services)
    {
        if (env.IsDevelopment())
        {
            // register other fake dependencies
            services.AddSingleton<ITasksRepository, TasksRepositoryFake>();
        }
        else
        {
            // register other real dependencies
            services.AddSingleton<ITasksRepository, TasksRepository>();
        }
    }

Option 2 : Configuration Based

    public IConfigurationRoot Configuration { get; set; }
    public Startup()
    {
       var builder = new ConfigurationBuilder()
        .SetBasePath(env.ContentRootPath)
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
        .AddEnvironmentVariables();
       Configuration = builder.Build();
    } 

    public void ConfigureServices(IServiceCollection services)
    {
        var isFakeMode= Configuration["ServiceRegistrationMode"] == "Fake";
        if (isFakeMode)
        {
            // register other fake dependencies
            services.AddSingleton<ITasksRepository, TasksRepositoryFake>();
        }
        else
        {
            // register other real dependencies
            services.AddSingleton<ITasksRepository, TasksRepository>();
        }
    }

Option 3 : Environment Based + Configuration Based

    public IConfigurationRoot Configuration { get; set; }
    public IHostingEnvironment env{ get; set; }
    public Startup(IHostingEnvironment env)
    {
        this.env = env;
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
            .AddEnvironmentVariables();
        Configuration = builder.Build();
    } 
    public void ConfigureServices(IServiceCollection services)
    {
        var isFakeMode = Configuration["ServiceRegistrationMode"] == "Fake";
        if (env.IsDevelopment() && isFakeMode)
        {
            // register other fake dependencies
            services.AddSingleton<ITasksRepository, TasksRepositoryFake>();
        }
        else
        {
            // register other real dependencies
            services.AddSingleton<ITasksRepository, TasksRepository>();
        }
    }
like image 34
adem caglin Avatar answered Oct 26 '22 21:10

adem caglin