Inject IWebHostingEnvironment into overridden method - c#

Using ASP.NET Core 5.0, I'm attempting to access IWebHostEnvironment in an EF Core 5.0 model class. How can I get access to the check if IsDevelopment is true from the environment?
I'm calling the below class from my Controllers like so:
private MyContext db = new();
Do I really need to also spin up IWebHostEnvironment in each controller that calls this EF class to target the correct constructor?
public partial class MyClass : DbContext
{
private readonly IWebHostEnvironment env;
public MyContext()
{
}
public MyContext(DbContextOptions<MyContext> options)
: base(options)
{
}
public MyContext(IWebHostEnvironment _env)
{
this.env = _env;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if(this.env.IsDevelopment()) { ... } // env is null
}
}
I've seen a few threats about DI, but to be honest, they have convoluted solutions that I can't quite grok.
EDIT with new code:
public IConfiguration Configuration { get; }
public IWebHostEnvironment WebHostEnvironment { get; }
public Startup(IConfiguration configuration, IWebHostEnvironment webHostEnvironment)
{
WebHostEnvironment = webHostEnvironment;
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
DbContextOptionsBuilder<MyContext> builder = new DbContextOptionsBuilder<MyContext>();
builder.UseSqlServer(#"Server=localhost;[...]");
services.AddScoped<MyContext>(_ => new MyContext(builder.Options, WebHostEnvironment));
services.AddDbContext<MyContext>();
EF context:
public partial class MyContext : DbContext
{
private readonly IWebHostEnvironment Env;
public MyContext()
{
}
public MyContext(DbContextOptions<MyContext> options, IWebHostEnvironment env)
: base(options)
{
Env = env;
}
}
Controller:
public class MyController : Controller
{
private MyContext db;
public ActionResult Index(MyContext db)
{
this.db = db; //throws exception below
return View();
}
}
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.
Microsoft.EntityFrameworkCore.Internal.DbContextServices.Initialize(IServiceProvider scopedProvider, IDbContextOptions contextOptions, DbContext context)

Your DbContext
public class MyContext : DbContext
{
private readonly IWebHostEnvironment WebHostEnv;
public MyContext(
DbContextOptions<MyContext> options,
IWebHostEnvironment webHostEnv) : base(options)
{
WebHostEnv = webHostEnv;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if(this.WebHostEnv.IsDevelopment()) { ... } // env is null
}
}
In StartUp add
public IConfiguration Configuration { get; }
public IWebHostEnvironment WebHostEnvironment { get; }
public StartUp(IConfiguration configuration, IWebHostEnvironment webHostEnvironment)
{
Configuration = configuration;
WebHostEnvironment = webHostEnvironment;
}
public void ConfigureServices(IServiceCollection services)
{
// ... some services
DbContextOptionsBuilder<MyContext> optsBuilder = new DbContextOptionsBuilder<MyContext>();
optsBuilder.UseSqlServer(Configuration.GetConnectionString("connstringname"));
services.AddScoped<MyContext>(_ =>
new MyContext(optsBuilder.Options, WebHostEnvironment));
}
Then your controller
public class MyController
{
// MyContext already loaded with IWebHostEnvironment
private readonly MyContext MyLoadedContext;
public MyController(MyContext myLoadedContext)
{
MyLoadedContext = myLoadedContext;
}
}

Related

Unable to resolve a type in Integration Testing using WebApplicationFactory

This is the problem I have: InvalidOperationException: Unable to resolve service for type 'WebApi.Test.Repository.TestRepository' while attempting to activate 'WebApi.Test.Services.TestService'.
Of course, the problem is, It seems I cant construct a TestRepository, when the service needs it.
This is the Repo:
public class TestRepository : GenericRepository<TestEntity>
{
public TestRepository(TestContext dbContext
, ILoggerFactory loggerFactory) : base(dbContext, loggerFactory)
{
}
}
The service basically has a TestRepository as a parameter in its constructor (but the issue seems to be before that).
This is my custom WebApplicationFactory
public class ApiWebApplicationFactory : WebApplicationFactory<Startup>
{
public IConfiguration Configuration { get; private set; }
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureAppConfiguration(config =>
{
Configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
config.AddConfiguration(Configuration);
});
//Add Services
builder.ConfigureTestServices(services =>
{
// Add framework services.
services.AddMvc();
//Add ApplicationDbContext using an in-memory database for testing.
services.AddDbContext<TestContext>(options =>
options.UseInMemoryDatabase("InMemoryDbForTesting"));
services.AddAutoMapper(typeof(Startup));
// Build the service provider.
var serviceProvider = services.BuildServiceProvider();
// Create a scope to obtain a reference to the database
// context (ApplicationDbContext).
using var scope = serviceProvider.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<TestContext>();
// Ensure the database is created.
db.Database.EnsureCreated();
// Seed the database with test data.
InitializeTestDatabase(db);
services.AddScoped<GenericRepository<TestEntity>, TestRepository>();
services.AddScoped<GenericService<TestEntity, TestRequest, TestResponse>, TestService>();
services.AddScoped<GenericController<TestEntity, TestRequest, TestResponse>, TestController>();
});
}
private void InitializeTestDatabase(TestContext textContext)
{
textContext.TestEntities.Add(new TestEntity { Id = 1 });
}
}
All the Generic classes are abstract classes. This is the GenericRepository:
public abstract class GenericRepository<T> : IGenericRepository<T>
where T : BaseEntity
{
protected DbContext _context;
private DbSet<T> _table;
private ILogger _logger;
public GenericRepository(DbContext context
, ILoggerFactory loggerFactory)
{
_context = context;
_table = context.Set<T>();
_logger = loggerFactory.CreateLogger<GenericRepository<T>>();
}
//CRUD Methods
}
I was thinking if it is because Im not creating the ILoggerFactory on my Custom WebAppFactory, if that is the case, how can someone create an ILoggerFactory?.
What I tried:
I tried to add the following line:
services.AddScoped<DbContext, TestContext>();
but I keep getting the same error.
This is the Service:
public abstract class GenericService<T, Request, Response>
where T : BaseEntity
where Response : class, IResponseDto
where Request : class, IRequestDto
{
protected readonly GenericRepository<T> _genericRepository;
protected readonly IMapper _mapper;
protected readonly ILogger _logger;
public GenericService(GenericRepository<T> genericRepository
, IMapper mapper
, ILoggerFactory loggerFactory)
{
_genericRepository = genericRepository;
_mapper = mapper;
_logger = loggerFactory.CreateLogger<GenericService<T, Request, Response>>();
}
//Methods
}

Getting Connection String in ASP.Net Core MVC data layer

I am using the code below to retrieve the connection string and it works fine. However, the configuration object has to be passed through the layers. Previous versions of .Net would allow me to get the connection string directly in the data layer. So can I still do that (and how do I do that) or do I need to pass the configuration object through the application as I do now?
In startup.cs
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSingleton(_ => Configuration);
...
}
MyController.cs
public class MyController : Controller
{
protected readonly IConfiguration Configuration;
public MyController(IConfiguration configuration)
{
Configuration = configuration;
}
public IActionResult ListRecords()
{
DatabaseContext ctx = new DatabaseContext(Configuration);
return View();
}
}
DatabaseContext.cs
public class DatabaseContext : DbContext
{
private readonly IConfiguration config;
public DatabaseContext(IConfiguration config)
{
this.config = config;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(config["ConnectionStrings:Dev"]);
}
}
Having to explicitly inject IConfiguration is usually seen as a code smell and indicates design issues.
Take advantage of dependency injection
public class MyController : Controller {
DatabaseContext context;
public MyController(DatabaseContext context) {
this.context = context;
}
public IActionResult ListRecords() {
//...use context here
return View();
}
}
and inject the database options instead
public class DatabaseContext : DbContext {
public DatabaseContext(DbContextOptions<DatabaseContext> options): base(options) {
//...
}
}
Then it is only a matter of configuring the context at startup
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services) {
// ...
services.AddDbContext<DatabaseContext>(options =>
options.UseSqlServer(Congiguration.GetConnectionString("Dev"));
// ...
}
Typically the pattern I've used for setting up DBContext, is to configure at startup.
So if this is startup.cs:
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
var sqlConnString = Configuration.GetConnectionString(dbConnectionStringSettingsName);
services.AddDbContext<DatabaseContext >(opt => opt.UseSqlServer(sqlConnString));
Also, if you pass your context as a service reference, you shouldn't need to give it IConfiguration.
private readonly DatabaseContext _context;
public MyController(DatabaseContext context)
{
_context = context;
}
public IActionResult ListRecords()
{
var dbresults = _context.Table.ToList();
return View(dbresults );
}
use nugget packaeges
Install-Package Microsoft.Extensions.Configuration.Abstractions
Install-Package Microsoft.Extensions.Configuration
and then Inject IConfigurationSection in the web application.
https://github.com/geeksarray/read-appsettings-json-in-net-core-class-library-using-dependency-injection

Unable to run commands. Error: No database provider has been configured for this DbContext

I am unable to run EntityFramework Core commands against the basic .NET Core 2.2 Web API I created. The API is working, but I can not 'add-migration' or 'update-database' unless I change where the connection string is retrieved. I believe the first example is best practice because the connection string is more secure, but I get an error when trying to run EF Core commands.
"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."
As far as I can tell I have correctly used AddDbContext() while passing DbContextOptions<>. The only code differences are in Startup.ConfigureServices() and MyContext.OnConfiguring(). What am I doing wrong with my preferred example?
Preferred Example (EF Commands do not work)
// MyContext.cs
public class MyContext : DbContext
{
private readonly DbContextOptions<MyContext> _options;
private readonly IConfiguration _config;
public MyContext (DbContextOptions<MyContext> options, IConfiguration config) : base(options)
{
_options = options;
_config = config;
}
public DbSet<Account> Accounts { get; set; }
public DbSet<User> User { get; set; }
}
}
// Startup.cs
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyContext>(
options => options.UseSqlServer(Configuration.GetConnectionString("MyAPI")));
services.AddScoped<IMyRepository, MyRepository>();
services.AddAutoMapper();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); }
else { app.UseHsts(); }
//app.UseHttpsRedirection();
app.UseMvc();
}
}
With the code below I am able to run 'add-migration' and 'update-database' with no errors, but I believe the retrieval of the connection string is less secure this way.
Example 2 (EF Commands Work)
// MyContext.cs
public class MyContext : DbContext
{
private readonly DbContextOptions<MyContext> _options;
private readonly IConfiguration _config;
public MyContext (DbContextOptions<MyContext> options, IConfiguration config) : base(options)
{
_options = options;
_config = config;
}
public DbSet<Account> Accounts { get; set; }
public DbSet<User> User { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(_config.GetConnectionString("MyAPI"));
}
}
}
// Startup.cs
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyContext>();
services.AddScoped<IMyRepository, MyRepository>();
services.AddAutoMapper();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); }
else { app.UseHsts(); }
//app.UseHttpsRedirection();
app.UseMvc();
}
}
You just need to specify where the DB connection is being created.
dotnet ef migrations add SomeMigration --startup-project ../path/to/Api/project
In this example, you are running the migration command and specifying the path to the project which contains the configuration/set up of the database context. If you don't specify this, and the setup is not in the DbContext class, then EF doesn't know how the database should be configured.
What fixed the issue for me was updating to .NET Core 3.1.101 and EFCore 3.1.1.

C# unable to resolve service for type context

I'm using API to implement some services.
I get an error :
InvalidOperationException: Unable to resolve service for type 'Zipcode_Service.Models.IZipcodeRepository' while attempting to activate 'Zipcode_Service.Controllers.CityController'.
The constructor of my cotroller or the deceleration of the _repository is causing the issue :
private IZipcodeRepository _repository;
public CityController(ILogger<CityController> logger, IZipcodeRepository repository)
{
_logger = logger;
_repository = repository;
}
My interface :
public interface IZipcodeRepository
{
IEnumerable<City> GetCitiesByCityName(string cityName);
// City GetCityNameById(int cityId);
}
My ZipcodeRepository :
public class ZipcodeRepository : IZipcodeRepository
{
private ZipcodeContext _context;
private ILogger<ZipcodeRepository> _logger;
public ZipcodeRepository(ZipcodeContext context, ILogger<ZipcodeRepository> logger)
{
_context = context;
_logger = logger;
}
public IEnumerable<City> GetCitiesByCityName(string cityName)
{
return _context.Cities.Where(t => t.Name.Contains(cityName)).ToList();
}
I saw many similar question, most of them suggested the problem was in the controller using the repository and not the interface. This is not the case.
EDIT: Some more info My startup page:
public Startup(IHostingEnvironment env)
{
_env = env;
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
_config = builder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
services.AddDbContext<ZipcodeContext>();
services.AddScoped<IZipcodeRepository, ZipcodeRepository>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
...
app.UseMvc(config =>
{
config.MapRoute(
name: "Default",
template: "{controller}/{action}/{id?}",
defaults: new { controller = "App", action = "Index" }
);
My context class :
public ZipcodeContext(IConfigurationRoot config, DbContextOptions options) : base(options)
{
_config = config;
}
public DbSet<City> Cities { get; set; }
public DbSet<Zipcode> Zipcodes { get; set; }
public DbSet<Street> Streets { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlServer(_config["ConnectionStrings:ZipcodeContextConnection"]);
}
The issue is usually associated with an error while the container is trying to resolve the object graph to satisfy dependencies.
In the comments you indicated that you registered IZipcodeRepository
services.AddScoped<IZipcodeRepository, ZipcodeRepository>();
Great. But that error can also happen while trying to initialize the implementation ZipcodeRepository or one of it's dependencies. For example if ZipcodeContext threw an exception while being initialized it would cause the same issue.
Make sure that the context is also configured properly.
For example
services.AddDbContext<ZipcodeContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("ConnectionNameHere")));

How to inject ILogger into EFCore DbContext

I have .net core app with EFCore db context:
public class MyappDbContext : DbContext
{
private ILogger<MyappDbContext> _logger;
public MyappDbContext(DbContextOptions<MyappDbContext> options)
: base(options)
{
// ???
}
}
In startup class I registered context:
services.AddDbContext<MyappDbContext>(options => options.UseSqlServer(connectionString));
How to inject into MyappDbContext, ILogger or ILoggerFactory (to create logger)?
All you need to do is add a ILoggerFactory or ILogger parameter to the context constructor:
public class MyappDbContext : DbContext
{
private readonly ILogger<MyappDbContext> _logger;
public MyappDbContext(DbContextOptions<MyappDbContext> options,
ILogger<MyappDbContext> logger)
: base(options)
{
_logger = logger;
}
}
In case if you need to instantiate the dbcontext manually:
public class Startup
{
public static readonly ILoggerFactory logFactory = LoggerFactory.Create(builder => builder.AddDebug());
....
public Startup(IWebHostEnvironment env)
{
....
}
public void ConfigureServices(IServiceCollection services)
{
....
}
}
public class MyDbContext : DbContext
{
private readonly ILoggerFactory _loggerFactory;
public MyDbContext(DbContextOptions<MyDbContext> options,
ILoggerFactory loggerFactory)
: base(options)
{
_loggerFactory = loggerFactory;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLoggerFactory(_loggerFactory);
// optionsBuilder.EnableSensitiveDataLogging();
}
}
// Somewhere else
var db = new MyDbContext(new DbContextOptions<MyDbContext>(), Startup.logFactory);
But I would recommend using DI instead:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("Default")).UseLoggerFactory(logFactory));
}

Categories

Resources