Injecting DbContext into service layer - c#

How am I supposed to inject my MyDbContext into my database service layer MyService?
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));
services.AddMvc();
}
MyDbContext.cs
public partial class MyDbContext : DbContext
{
public virtual DbSet<User> User { get; set; }
public MyDbContext(DbContextOptions<MyDbContext> options)
:base(options)
{
}
}
appsettings.json
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
},
"ConnectionStrings": {
"DefaultConnection": "Server=MyServer;Database=MyDatabase;user id=MyUser;password=MyPassword;"
}
}
MyService.cs
public class MyService
{
public User GetUser(string username)
{
// Should i remove this call and get the reference from the injection somehow?
MyDbContext db_context = new MyDbContext(optionsBuilder.Options);
using (db_context)
{
var users = from u in db_context.User where u.WindowsLogin == username select u;
if (users.Count() == 1)
{
return users.First();
}
return null;
}
}
}
In my GetUser method, I know I am supposed to be using my injected MyDbContext here but I am not quite sure how to fetch it. Which piece of the puzzle am I missing?

You don't have to include the dbcontext yourself, ASP.NET core dependency injection service will do this for you.
You have just to declare your services and your database context in your startup class, and put the dbcontext you need in your service's constructor :
Startup.cs (your have to choose the service lifetime you want, here it's a scoped service, once per request):
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));
services.AddMvc();
services.AddScoped<IMyService, MyService>();
}
Your service class:
public class MyService : IMyService
{
private readonly MyDbContext _context;
public MyService(MyDbContext ctx){
_context = ctx;
}
public User GetUser(string username)
{
var users = from u in _context.User where u.WindowsLogin == username select u;
if (users.Count() == 1)
{
return users.First();
}
return null;
}
}
public interface IMyService
{
User GetUser(string username);
}
In your controller, you have to declare the services (or the database context) you need to use in the same way :
public class TestController : Controller
{
private readonly IMyService _myService;
public TestController(IMyService serv)
{
_myService = serv;
}
public IActionResult Test()
{
return _myService.MyMethod(); // No need to instanciate your service here
}
}
Note about the controller : you don't have to add them in your startup class like you do with your database context or your services. Just implement their constructor.
If you need more information about the .NET Core depencency injection, the official documentation is clear and very complete : https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection
NB:
In the startup.cs, the AddScoped line is an option. Your can choose the lifetime you want for your service.
There is the different lifetimes you can choose :
Transient
Transient lifetime services are created each time they are requested.
This lifetime works best for lightweight, stateless services.
Scoped
Scoped lifetime services are created once per request.
Singleton
Singleton lifetime services are created the first time they are
requested (or when ConfigureServices is run if you specify an instance
there) and then every subsequent request will use the same instance.
Above taken from: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection
Note: It's not the question here but your GetUser data query looks like a little strange to me. If your count()==1 goal is to check the unicity of the user, the good way is to add a unicity constraint in your database. If count()==1 goal is to check you have data to avoid a object null reference exception, you can use .FirstOrDefault(), it will manage this for you. You can simplify this method :
public User GetUser(string username) => (from u in _context.User where u.WindowsLogin == username select u).FirstOrDefault();

Related

AddSingleton doesn't work in my ASP.NET Core project

I have the problem that I want to add my service CourseService as Singleton and I want to add only once my initial data like this:
context.Courses.Add(new Course { ... });
But it turned out that every time when I upload my page with courses my initial data added again and again to my database. So I see the same courses on my page as much time as I upload this page. Can't understand where is the problem.
My interface:
public interface ICourseService
{
IEnumerable<CourseDto> GetCourses();
Task<IEnumerable<CourseDto>> GetCoursesAsync();
}
My service:
public class CourseService : BaseService<Course, CourseDto>, ICourseService
{
public CourseService(IMapper mapper, DataContext context) : base(mapper,context)
{
context.Courses.Add(new Course { ... });
context.Courses.Add(new Course { ... });
context.SaveChanges();
}
public IEnumerable<CourseDto> GetCourses() { ... }
public async Task<IEnumerable<CourseDto>> GetCoursesAsync() { ... }
}
My controller:
public class CourseController : Controller
{
private readonly CourseService _courseService;
public CourseController(CourseService courseService)
{
_courseService = courseService;
}
[Route("courses")]
public async Task<IActionResult> GetCourses()
{
var courses = await _courseService.GetAllAsync();
return View("CourseList", courses);
}
}
And I added my service as Singleton in the method ConfigureServices:
services.AddSingleton<ICourseService, CourseService>();
services.AddEntityFrameworkSqlite().AddDbContext<DataContext>();
AddDbContext by default adds context with Scoped lifetime, so it can't be resolved in singleton services. Possible workarounds:
register ICourseService as scoped
register context as scoped or transient (would not recommend)
inject IServiceScopeFactory and use it to create scope and resolve context from the scope (on each method call), like in this answer
Personally I would go with the first approach.
Read more:
Service lifetimes
Data seeding in EF Core

Is there a way to modify an IOptionsSnapshot<T> programmatically instead of a change in the source file?

I have a transient class registered in ASP.NET Core's DI.
An IOptions<T> is injected into its constructor. For every request and when needed, during runtime and based on a condition, I want to have another instance of IOptions injected.
Since IOptionsSnapshot<T> gets updated for every request when the source file gets updated, is there a way to mimic this behavior but instead of a change in the file, I want to programmatically make a change in IOptions, and before constructor injection, during runtime when a request comes in?
And use IOptionsSnapshot<T> instead of IOptions<T> for that.
Update:
Condition example to be run before injection happens somewhere in the app like maybe a controller or action custom attribute? In the attribute check a value and so:
if (some condition)
Options.cnnectionstring = "string1";
else
Options.cnnectionstring = "string2";
Injected into a class like this:
public class Books
{
private readonly string connectionString;
public Books(IOptions<DBOptions> options)
{
this.connectionString = options.Value.connectionString;
}
public void SomeMethod()
{
.... //uses connectionString
}
}
Registered like this:
services.Configure<DBOptions>(options =>
{
options.connectionString = "some connection string";
});
IOption (like IConfiguration) is registered as singleton, but the request is scoped. Then it isn't possible to use request's information to modify the configuration.
You can use a intermediate scoped service, that retrieve the request's information and generate the desired connection string, like :
public class BooksConnectionString
{
public IConfiguration _configuration;
public IHttpContextAccessor _httpContextAccessor;
public BooksConnectionString(IConfiguration configuration, IHttpContextAccessor httpContextAccessor)
{
_configuration = configuration;
_httpContextAccessor = httpContextAccessor;
}
public string ConnectionString
{
get
{
var library = _httpContextAccessor.HttpContext.Request.Query["library"].First();
return _configuration.GetConnectionString(library);
}
}
}
Register the service as scoped :
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
...
services.AddHttpContextAccessor();
services.AddScoped<BooksConnectionString>();
}
...
}
Then you can inject like :
public class Books
{
private readonly string connectionString;
public Books(BooksConnectionString options)
{
this.connectionString = BooksConnectionString.ConnectionString;
}
}
It's suppose you can modify the class where the connection string is injected. If you can't modify this class, then you can register IOption as scoped :
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
...
services.AddHttpContextAccessor();
services.AddScoped<IOptions<DBOptions>>(p =>
{
var configuration = p.GetService<IConfiguration>();
var httpContextAccessor = p.GetService<IHttpContextAccessor>();
var library = httpContextAccessor.HttpContext.Request.Query["library"].First();
var dbOptions = configuration.GetSection("Databases").GetSection(library).Get<DBOptions>();
return Options.Create(dbOptions);
});
}
...
}
Warning, IOption are expected as singleton. Register IOption as scoped would break this expectation. To be used as a last resort.

Unable to resolve service for type while attempting to activate

In my ASP.NET Core application, I get the following error:
InvalidOperationException: Unable to resolve service for type 'Cities.Models.IRepository' while attempting to activate 'Cities.Controllers.HomeController'.
I the HomeController I am trying to pass the Cities getter to the view like so:
public class HomeController : Controller
{
private readonly IRepository repository;
public HomeController(IRepository repo) => repository = repo;
public IActionResult Index() => View(repository.Cities);
}
I have one file Repository.cs that contains an interface and its implementation like so:
public interface IRepository
{
IEnumerable<City> Cities { get; }
void AddCity(City newCity);
}
public class MemoryRepository : IRepository
{
private readonly List<City> cities = new List<City>();
public IEnumerable<City> Cities => cities;
public void AddCity(City newCity) => cities.Add(newCity);
}
My Startup class contains the default-generated code from the template. I have made any changes:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
}
}
For the Dependency Injection framework to resolve IRepository, it must first be registered with the container. For example, in ConfigureServices, add the following:
services.AddScoped<IRepository, MemoryRepository>();
For .NET 6+, which uses the new hosting model by default, add the following in Program.cs instead:
builder.Services.AddScoped<IRepository, MemoryRepository>();
AddScoped is just one example of a service lifetime:
For web applications, a scoped lifetime indicates that services are created once per client request (connection).
See the docs for more information on Dependency Injection in ASP.NET Core.
We are getting this error in Entity frame work core database first approach. I followed below steps and error got resolved
Step 1: Check Your context class constructor should be like this
public partial class ZPHSContext : DbContext
{
public ZPHSContext(DbContextOptions<ZPHSContext> dbContextOptions)
: base(dbContextOptions)
{
}
}
Step 2: In Startup file
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDbContext<ZPHSContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("BloggingDatabase")));
}
Step 3: Connection string in appsettings
"ConnectionStrings": {
"BloggingDatabase": "Server=****;Database=ZPHSS;Trusted_Connection=True;"
}
Step 4: Remove default code in OnConfiguring method in context class
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
}
Other answers are CORRECT, however I was spinning up a new asp.net core 2.1.x project and got this error.
Ended up being a typo by ME.
So in my Controller instead of Correctly using the Interface like this
public HomeController(IApplicationRepository applicationRepository)
{
_applicationRepository = applicationRepository;
}
My typo had me using ApplicationRepository instead of its interface IApplicationRepository
Notice below, and so with NO ERRORS spotting the missing "I" was fun :/
public HomeController(IApplicationRepository applicationRepository)
{
_applicationRepository = applicationRepository;
}
Thus the controller was not resolving the DI...
A method like this needs to be added to your Startup:
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
//...
// Add application services.
services.AddTransient<IRepository, MemoryRepository>();
//..
}
Services should be registered before used.
UPDATE:
If you do not want to use DI on your application, just create and instance of MemoryRepository on the constructor of HomeController, like this:
public class HomeController : Controller
{
private IRepository repository;
public HomeController()
{
repository = new MemoryRepository();
}
public IActionResult Index()
{
return View(repository.Cities);
}
}
You have to add your implementation to DI (Dependeny Injection) section. For .Net Core Mvc, it would be like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseInMemoryDatabase()
);
services.AddScoped<IRepository, MemoRepostory>();
}
This may not be helpful for your code sample but in my case the same error was a result of a circular dependency.
you have to register your repository like this
services.AddSingleton<IRepository, MemoryRepository>();
In my case, I was trying to access context through constructor. like here;
private readonly Context _context;
public ImageController(Context context)
{
_context = context;
}
But When I tried to access the context just by creating an instance of class, it worked like here;
Context c = new Context();
For me I am using visual studio 2022 and .NET 6
the solution was add the following line in the Program.cs file :
builder.Services.AddSingleton<IHISInterface<UserDetails>, UserDetailsRepository>();
There is one more possibility that, You might have sent wrong variable in the place while writing this HTTPPOST last part code
mine is
var categoryMap = _mapper.Map(categoryCreate);
if(!_categoryRepository.CreateCategory(categoryMap))
{
ModelState.AddModelError("", "Something went wrong while saving");
return StatusCode(500, ModelState);
}
return Ok("Successfully created");
in the if condition I passed the category as parameter instead of categoryMap
so please cross check

Using ApplicationDbContext with DI from appsettings.json

I am trying to abstract any connection information away from my ApplicationDbContext class so that I can take advantage of different databases for development, staging, production. I start by registering a service from Startup.cs
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
My ApplicationDbContext class:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
}
When running this application I get the following error:
InvalidOperationException: Could not create an instance of type 'SquadApps.Data.ApplicationDbContext'. Model bound complex types must not be abstract or value types and must have a parameterless constructor.
So naturally I tried adding a parameterless constructor
public ApplicationDbContext() { }
Now getting another error:
InvalidOperationException: No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions object in its constructor and passes it to the base constructor for DbContext.
If I go back to having a connection string stored in the ApplicationDbContext class like so:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("........");
}
Then everything works fine but obviously this is not ideal and probably a bad practice. I think there is something i'm missing about the DI process and any suggestions or advice would be appreciated.
The solution turned out to be how I was trying to call the DI. I had incorrectly assumed DI would be able to be called per each IActionResult inside my controllers but in fact it must occur within the constructor of the controller. This makes the DI available to all IActionResult methods within the controller.
Example of the working DI call:
public class HomeController : Controller
{
private readonly ApplicationDbContext _ctx;
private readonly CompanySettings _companySettings;
public HomeController(ApplicationDbContext ctx, IOptions<CompanySettings> settings)
{
_ctx = ctx;
_companySettings = settings.Value;
}
public IActionResult Index()
{
var model = new HomeViewModel();
// _ctx and _companySettings can be used here
return View(model);
}
}

Access DBContext using EF7 and VNext

In my MVC 6 project I have my ApplicationDBContext class
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
protected override void OnModelCreating(ModelBuilder builder)
{
}
}
This is added to my services in the Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
//Other configurations removed for brevity
}
Now when I create a new Controller, it asks me if I want to use the Entity Framework, and I can choose my data context. When that controller is created the context is passed in the constructor using what I assume is dependency injection.
public class CompanyController : Controller
{
private ApplicationDbContext _context;
public CompanyController(ApplicationDbContext context)
{
_context = context;
}
}
Now, I don't want to do all database interactions in the controllers, but rather in my other classes. What I can't figure out, is how to get the ApplicationDbContext from my other classes. Passing it from the controller obviously won't work because classes could be called from other places than the controller.
If I just try new ApplicationDbContext(); I get the following error:
No database providers are configured. Configure a database provider by overriding OnConfiguring in your DbContext class or in the AddDbContext method when setting up services.
I feel like this should be something simple, but I am completely lost here.
ASP.NET Core is based on dependency injection, since your context has been added in your dependendy container, it's automatically injected by the framework when your controller is instanciated.
Edit based on comments :
You can setup your classes to support DI, let's suppose you have two class. One that depend on your context, and then second that depend both on your context and your first class :
public class MyClass
{
private ApplicationDbContext _context;
public MyClass(ApplicationDbContext context)
{
_context = context;
}
}
public class AnotherClass
{
private ApplicationDbContext _context;
private MyClass _myClass;
public AnotherClass(ApplicationDbContext context, MyClass myClass)
{
_context = context;
_myClass = myClass;
}
}
Add your classes as a transient dependency in the sevice collections at startup, and let the service provider resolve their dependencies for you :
public void ConfigureServices(IServiceCollection services)
{
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
services.AddTransient<MyClass>();
services.AddTransient<AnotherClass>();
//Other configurations removed for brevity
}
Change your controller to accept MyClass as injected dependency :
public class CompanyController : Controller
{
private ApplicationDbContext _context;
private MyClass _myClass;
public CompanyController(ApplicationDbContext context, MyClass myClass)
{
_context = context;
_myClass = myClass;
}
}
You can also have another controller that take AnotherClass as injected dependecy :
public class AnotherController : Controller
{
private AnotherClass _anotherClass;
public AnotherController(AnotherClass anotherClass)
{
_anotherClass = anotherClass;
// _anotherClass will have both ApplicationDbContext and MyClass injected by the service provider
}
}
You should read the docs of dependency injection of ASP.NET Core, it could help to understand basics of DI. Another article from K. Scott Allen that explain some bad practice when you deal with DI.
You can create a service class that receives the DbContext in the same way as the controller.
public class SomeService
{
private ApplicationDbContext MyDbContext { get; set; }
public SomeService(ApplicationDbContext dbContext)
{
MyDbContext = dbContext;
}
public void MethodName()
{
// You can now do MyDbContext.SomeDomainModel
}
}
Then register the service in Startup.cs, in your ConfigureServices method.
public void ConfigureServices(IServiceCollection services) {
// <snipped>
services.AddTransient<SomeService>();
}
And now, in your CompanyController, you can add another parameter in the constructor for the SomeService, just as you have for the ApplicationDbContext.
public class CompanyController : Controller
{
private ApplicationDbContext _context;
private SomeService _someService;
public CompanyController(ApplicationDbContext context, SomeService someService)
{
_context = context;
_someService = someService;
}
}
All that said, I don't think there's anything wrong with doing your logic in your controller actions to build your ViewModel, accessing the DbContext. The DbContext is what's separating your business logic (in the controller) from the DAL. Some may disagree with me, but you don't need to add additional services to further separate them. Most of the code in your action methods are unique to that action and not going to be reused by other actions. IMO, those are the pieces of code to put into services. Things like sending emails and such.

Categories

Resources