Is there any better way to add the dbcontext to a Asp.core MVC controller?
Asked Answered
M

4

6

In the new MVC Core it seems the standard way to get the context to the controller is by doing this

public class BaseController : Controller
{
    public readonly ReportDBContext _db;
    
    public BaseController(ReportDBContext db)
    {
        _db = db;
    }
}

I can then use

public class HomeController : BaseController
{   
    public HomeController(ReportDBContext db) : base(db) { }
}

To make this a little easier in all other controllers. Normally in Asp.net MVC I can get a context any time by using new ReportDBContext()

Is there a similar way of this now, or do I have to have the context in all controllers in asp.core MVC?

Mayle answered 16/6, 2020 at 14:53 Comment(1)
Seems like you don't understand the benefits of Dependency Injection. DI is incredibly important to writing software that is flexible and easy to maintain. There's really not a good argument to not use it. I suggest you watch this video to obtain an understanding of DI and the benefits.Monro
C
12

Thanks to @mm8's answer, if you decided to use dependency injection, then you can follow the below mentioned steps.

Suppose that you have defined your ReportDBContext like this:

public class ReportDBContext : DbContext
{
    public DbSet<Sample> Samples { get; set; }
    //...

    public ReportDBContext(DbContextOptions<ReportDBContext> options) : base(options) 
    { 
      //...
    }
}

So you need to config your startup.cs like this:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        //...What needed
        services.AddDbContext<ReportDBContext>(options => options.UseSqlServer("Connection string to your DB"));
        //...What needed
    }
    //...
 }

So you can easily inject your ReportDBContext to your classes like this(for example inject it to one of your controllers):

[Route("api/[controller]")]
public class ValuesController : Controller
{
    private readonly ReportDBContext _dbContext;

    public ValuesController(ReportDBContext dbContext )
    {
        _dbContext = dbContext;
    }
    //...
}

You can simply inject the ReportDBContext to your BaseController instead of injecting it to every controller in your project too.

Update 1

If you do not want to inject the ReportDBContext into each constructor, then you can design your BaseController as follow by using HttpContext.RequestServices:

public class BaseController : Controller
{
    protected ReportDBContext DbContext => (ReportDBContext)HttpContext.RequestServices.GetService(typeof(ReportDBContext));
    //...
}

[Route("api/[controller]")]
public class ValuesController : BaseController
{
    //...

    [HttpGet]
    public List<Sample> Get() => DbContext.Samples.ToList();
}

Read more here:

  1. https://learn.microsoft.com/en-us/ef/core/miscellaneous/configuring-dbcontext
  2. http://www.binaryintellect.net/articles/17ee0ba2-99bb-47f0-ab18-f4fc32f476f8.asp

You will need to install at least these two NuGet packages too:

  1. Microsoft.EntityFrameworkCore
  2. Microsoft.EntityFrameworkCore.SqlServer (If the database provider is Microsoft SQL Server)
Cordie answered 16/6, 2020 at 15:13 Comment(8)
Thanks, that's pretty much what I've got now - I just wanted to know if there was a better way of doing it. It just seems like your going to end up with 20 or so controllers each with a contructor that takes the DBContext.Mayle
@RichardHousham I have said at the end of my answer that you can inject your dbContext to your BaseController instead of injecting it to each of the controllers :-)Cordie
Yeah, but that would still require your normal controller to still have it in the constructor ie. public HomeController(ReportDBContext db) : base(db) { }Mayle
Or is there another way?Mayle
@RichardHousham I have updated the answer which now covers your requirementsCordie
Thanks would that follow this DI method or would you refrain doing this yourself?Mayle
@RichardHousham this is not what I do myself, however it follows the DICordie
would it cause any issue of request failture or slow down?Springe
D
3

Normally in Asp.net MVC I can get a context any time by using new ReportDBContext()...

You could do the same, i.e. create a new context explicitly in the controller, nowadays. There is nothing that stops for you from doing this, except for best practices.

Some of the benfits of using dependency injection is that you can define the lifetime of the dependency in the Startup class and reuse it across all your controllers, and that you can mock away dependencies in your unit tests. Please refer to the docs and this question for more information.

But you certainly don't have to use dependency injection if you don't want to or have a reason not to do so. The compiler won't force to you define a custom constructor that accepts a dependency.

Dutchman answered 16/6, 2020 at 15:6 Comment(1)
This does not always work in all scenarios and may require you to hard code the connection string to the database. It may work fine for running your application, but not when doing a migration. The issue should be fixed in .Net7.Esker
M
2

The architecture of ASP.NET Core is built on Dependency Injection. It even has a built-in dependency injection container, but you can also user others like AutoFac or NInject. If you need the DbContext in more than one action, you can use constructor injection, like in your example.

If you need it only once, you can inject it directly into the action method like so:

public IActionResult Get([FromServices]ReportDbContext db) 
{ 
   …
} 

The usage of Dependency Injection makes your code more testable. For your unit tests you could inject a InMemory DbContext.

In my opinion it is even less work than constructing the DbContext yourself. It's done by the framework.

Mineraloid answered 16/6, 2020 at 15:9 Comment(2)
That's pretty interesting, if I have 2 contexts but maybe only use 1 in a handful of places could I use this then?Mayle
@RichardHousham Yes, that should work. As long as both contexts are registeredMineraloid
M
0

Another approach is creating a "ServiceManager" class have various items in there like for instance I've got classes for database context, Active Directory and the IConfiguration for the config file.

Add in a creator like this...

public class ServiceManager
{
    public DBConn DBConn { get; set; }
    public iADLookup iADLookup { get; set; }

    public IConfiguration configuration { get; set; }

    public ServiceManager(DBConn conn, iADLookup aDLookup, IConfiguration config)
    {

        DBConn = conn;
        iADLookup = aDLookup;
        configuration = config;
    }
}

and into the program like this.

builder.Services.AddDbContext<DBConn>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DBConn")));

builder.Services.AddScoped<iADLookup, ADLookup>();

//create a service manager 
builder.Services.AddTransient<ServiceManager>();

Use in your controllers like this..

public readonly DBConn _db;
public readonly iADLookup _ad;

public readonly IConfiguration _config;


public BaseController(ServiceManager serviceManager)
{
    _db = serviceManager.DBConn;
    _ad = serviceManager.iADLookup;
    _config = serviceManager.configuration;
}

This works for me and allows you to add in more "services" easily.

Mayle answered 17/7, 2024 at 9:10 Comment(1)
I don't think this is essential to the question. It's just an arbitrary way of organising DI. Many users could post similar answers, which, in the end, only adds noise.Childbearing

© 2022 - 2025 — McMap. All rights reserved.