简体   繁体   中英

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.

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM