I am fairly new to ASP.NET Core and have been using Entity Framework Core to work with SQL Server databases on my local machine. My most recent project is using .NET 7 runtime instead of 6 and the Web App Core template instead of the MVC one.
Here is the layout of my project so far.
Program.cs
using CommentsApplication.Db;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddDbContext<DBconnection>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("localhost"));
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Dbconnection.cs
This is the DbContext that connects to my database. I have a single table in the database called accounts.
using CommentsApplication.Models;
using Microsoft.EntityFrameworkCore;
namespace CommentsApplication.Db
{
public class DBconnection: DbContext
{
public DBconnection(DbContextOptions<DBconnection> options) : base(options) { }
public DbSet<Account> accounts { get; set; }
}
}
Here is what the Account class looks like
using System.ComponentModel.DataAnnotations;
namespace CommentsApplication.Models
{
public class Account
{
[Key]
public int Id { get; set; }
public string Organization { get; set; }
public string Email { get; set; }
}
}
The connection string looks like this (my computers name is ZURG)
Server=ZURG;Database=AccountsDB;Trusted_Connection=True;Encrypt=False;
When I attempt to run the application I get this error:
InvalidOperationException: Unable to resolve service for type 'System.Data.Common.DbConnection' while attempting to activate 'CommentsApplication.Pages.IndexModel'.
This is what the IndexModel looks like:
using CommentsApplication.Models;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Data.Common;
namespace CommentsApplication.Pages
{
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
private readonly DbConnection _db;
public string org { get; set; }
public string email { get; set; }
public IndexModel(ILogger<IndexModel> logger, DbConnection DB)
{
_logger = logger;
_db = DB;
}
public void OnGet()
{
}
public void OnPost(string org, string email)
{
org = org.Trim();
email = email.Trim();
Account a = new Account()
{
Organization= org,
Email = email
};
}
}
}
Every time I've used a similar setup in my other ASP.NET Core projects this has worked fine but for some reason this time it won't.
Is anyone able to point me in the right direction?
You messed up your naming... You called your DbContext "Dbconnection" note the lower case "c", but added a reference of type "DbConnection" so your viewmodel is looking for a System.Data.Common.DbConnection class.
I'd recommend naming your DbContext something like "AppDbContext" to avoid naming confusion which will make case issues like this easier to spot/avoid.
In your case,you referred the namespace:System.Data.Common,there's also a class named of DBconnection,and you didn't referred the namespace :CommentsApplication.Db,So If you don't write the full name of DBconnection class,
it would be System.Data.Common.DBconnection by default
If there're classes share the samename under different NameSpaces in your codes,try with the full name to avoid confusing,
In your case,it should be CommentsApplication.Db.DBconnection
private readonly CommentsApplication.Db.DbConnection _db
public IndexModel(ILogger<IndexModel> logger, CommentsApplication.Db.DbConnection DB)
{
_logger = logger;
_db = DB;
}
Related
I'm currently developing a web application with ASP.NET Core and handling the database with Entity Framework Core. I have two projects in my VS Solution; WebApp (the main application) and DatabaseHandler (the EF Core handler). I have installed Entity Framework Core with the Pomelo package, since I'm using a MySQL database.
I've been following the Microsoft documentation to setup EF Core, connection strings and all that, and it works fine. I'm able to make migrations, make updates and do stuff with the database. I'm however not sure if I'm doing it correctly, since the latest EF Core tutorials use dependency injection and I'm not familiar with it.
Right now I'm passing the DbContext object as an argument from WebApp to DatabaseHandler, since I want all database-related stuff to only exist in DatabaseHandler. This works, but is it possible to call functions from another project and also share the DbContext object without passing it as an argument? I'm probably not explaining it well, I hope my code explains it better.
WebApp/Startup.cs:
This is where I load the connection string from appsettings.json.
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextPool<DataContext>(
options => options.UseMySql(Configuration.GetConnectionString("DefaultConnection")
));
services.AddRouting(options => options.LowercaseUrls = true);
services.AddControllersWithViews();
}
WebApp/HomeController.cs:
This is where I call the GetAllChallenges() function from the DatabaseHandler project, and I also pass the DataContext object as an argument. This is what I'm trying to avoid!
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly DataContext db;
public HomeController(ILogger<HomeController> logger, DataContext _db)
{
_logger = logger;
db = _db;
}
public IActionResult Challenges()
{
List<Challenge> ChallengesList = DatabaseHandler.HandleChallenges.GetAllChallenges(db);
return View(ChallengesList);
}
}
DatabaseHandler/DataContext.cs:
This is where I initialize the entity classes and so on.
public class DataContext : DbContext
{
public DataContext(DbContextOptions<DataContext> options) : base(options) { }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { }
// Tables
public DbSet<User> Users { get; set; }
public DbSet<Challenge> Challenges { get; set; }
// Data seeding
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Seed();
}
}
DatabaseHandler/HandleChallenges.cs:
This is where I have all my database functions. The results are returned back to the controller within the WebApp project.
public class HandleChallenges
{
public static List<Challenge> GetAllChallenges(DataContext db)
{
var Data = db.Challenges;
List<Challenge> ChallengesList = Data.ToList();
return ChallengesList;
}
}
I have looked into dependency injection, but I'm not sure how I can use this between two projects. Is there a less complicated way of achieving this, perhaps without using DI at all? I'm satisfied as long as I don't need to pass the DataContext object as an argument every time I need to call a function from DatabaseHandler.
Can someone help me understand? Thanks a lot in advance!
You could use Options pattern, which I have already used many times. Its working very well despite of database you use. Thanks to dependency injection you are able to access if from multiple projects. Reading documentation about Option pattern (https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-3.1) is useful but I will also provide you with my own example :
First you create model to store you connection string, dbName etc. Remember to add it in a library outside your main project(eg. Web Api) :
public class NameOfYourProject_ApiDbSettings : IIMTTApiDbSettings
{
public NameOfYourProject_ApiDbSettings()
{
}
public string CollectionName { get; set; }
public string ConnectionString { get; set; }
public string DatabaseName { get; set; }
}
public interface I_NameOfYourProject_ApiDbSettings
{
string CollectionName { get; set; }
string ConnectionString { get; set; }
string DatabaseName { get; set; }
}
Secondly you make it available for all you projects :
services.Configure<NameOfYourProjectApiDbSettings>(options =>
{
options.ConnectionString
= Configuration.GetSection("NameOfYourProjectDbSettings:ConnectionString").Value;
options.DatabaseName
= Configuration.GetSection("NameOfYourProjectDbSettings:DatabaseName").Value;
});
Then you can use it in multiple projects. (Rememebr to add referance to you model -> point 1. I keep the model always with repository) I will give you my example where I use MongoDb :
private readonly IMongoDatabase _database = null;
public SomeObjectContext(IOptions<IMyProjectDbSettings> settings)
{
var client = new MongoClient(settings.Value.ConnectionString);
if (client != null)
_database = client.GetDatabase(settings.Value.DatabaseName);
}
public IMongoCollection<MyModel> MyModels
{
get
{
return _database.GetCollection<MyModel>("MyModels");
}
}
You need to extract an interface from the class (note the method is no longer static) and add a constructor for the context:
public interface IHandleChallenges
{
List<Challenge> GetAllChallenges();
}
public class HandleChallenges : IHandleChallenges
{
public HandleChallenges(DataContext context)
{
db = context;
}
private DataContext db;
public List<Challenge> GetAllChallenges()
{
var Data = db.Challenges;
List<Challenge> ChallengesList = Data.ToList();
return ChallengesList;
}
}
Then register it as a service:
services.AddScoped<IHandleChallenges, HandleChallenges>();
Your controller now receives this class in it's constructor instead of the context:
private IHandleChallenges _challengeHandler;
public HomeController(ILogger<HomeController> logger, IHandleChallenges challengeHandler)
{
_logger = logger;
_challengeHandler = challengeHandler;
}
And calls it from the action:
public IActionResult Challenges()
{
List<Challenge> ChallengesList = _challengeHandler.GetAllChallenges();
return View(ChallengesList);
}
I'm trying to get more clarification on an already asked question. Because I'm a new user, I can't comment yet (You must have 50 reputation to comment).
In regards to Global Variables in ASP.Net Core 2
#nurdyguy comments that "The dependency injection built in to the framework will populate the options variable".
var repo = new DB();
More code to demonstrate the problem:
public class Secrets
{
public string ConnectionString { get; set; }
}
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
.
.
.
System.Action<Secrets> opts = (opt =>
{
opt.ConnectionString = Configuration["ConnectionString"];
});
services.Configure(opts);
services.AddSingleton(resolver => resolver.GetRequiredService<IOptions<Secrets>>().Value);
}
}
public sealed class DB
{
private string _connectionString;
public DB(Secrets secrets)
{
_connectionString = secrets.ConnectionString;
}
}
public class testModel : PageModel
{
public void OnGet()
{
DB db = new DB();
}
}
When I attempt to instantiate the class, I receive a the following compile time error:
Error CS7036
There is no argument given that corresponds to the required formal parameter 'secrets' of 'DB(Secrets)'
I've followed the code example correctly I'm sure. Is their another way to instantiate the class?
The main problem is you are trying to create an instance of PropertySalesRepository using new that calls the constructor that requires a prop of the type MDUOptions.
When creating an instance with new you do not get the class with injected properties.
To get the instance of the class with injected properties the easiest way is to inject it into your controller.
I have setup .net core project and db context also. But i cant start using dbContext yet due this error-
"there is no argument given that corresponds to the required formal
parameter 'options'"
Controller:
public IActionResult Index()
{
using (var db = new BlexzWebDb())
{
}
return View();
}
Dbcontext Code:
public class BlexzWebDb : DbContext
{
public BlexzWebDb(DbContextOptions<BlexzWebDb> options)
: base(options)
{ }
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<AssignedRole> AssignedRoles { get; set; }
}
error picture attached. How can this issue be fixed?
Instantiate new object of DbContext from ConnectionString
var connectionstring = "Connection string";
var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
optionsBuilder.UseSqlServer(connectionstring);
ApplicationDbContext dbContext = new ApplicationDbContext(optionsBuilder.Options);
// Or you can also instantiate inside using
using(ApplicationDbContext dbContext = new ApplicationDbContext(optionsBuilder.Options))
{
//...do stuff
}
Note
At the time of writing the use of EF Core with the Dependency injection framework wasn't as known as it is now. This answers gives answer to the question from a DI perspective, which at the time, helped out OP.
The other answer provides you a conventional way to instantiate the DbContext using the new operator.
TL;DR, 3 options:
Option 1
Register the DbContext during application configuration:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextPool<BlexzWebDb>(options =>
options.UseSqlServer(Configuration.GetConnectionString("BlexzWebConnection")));
}
and use the DI framework to retrieve it:
public class SomeController : Controller
{
private readonly BlexzWebDb _db;
//the framework handles this
public SomeController(BlexzWebDb db)
{
_db = db;
}
}
Option 2
If you are looking for a design-time IdentityDbContext using IOptions<OperationalStoreOptions>, see: Add migration for ApiAuthorizationDbContext from another project - EF Core
Option 3
Or use the new operator and provide the details, see #Qamar Zaman's answer for details.
The long answer, and why DI is a treat
In EF Core it's common to pass some DbContextOptions to the constructor.
So in general, a constructor looks like this:
public BlexzWebDb(DbContextOptions<BlexzWebDb> options) : base(options)
As you can see there, there is no valid overload in the form of a parameter-less constructor:
Thus, this does not work:
using (var db = new BlexzWebDb())
Obviously, you can pass in an Option object in the constructor but there is an alternative. So,
Instead
.Net Core has IoC implemented in it's roots. Okay, this means; you don't create a context, you ask the framework to give you one, based on some rules you defined before.
Example: somewhere you will register your dbcontext, (Startup.cs):
//typical configuration part of .net core
public void ConfigureServices(IServiceCollection services)
{
//some mvc
services.AddMvc();
//hey, options!
services.AddDbContextPool<BlexzWebDb>(options =>
options.UseSqlServer(Configuration.GetConnectionString("BlexzWebConnection")));
//...etc
Now the registering part is done, you can retrieve your context from the framework. E.g.: inversion of control through a constructor in your controller:
public class SomeController : Controller
{
private readonly BlexzWebDb _db;
//the framework handles this
public SomeController(BlexzWebDb db)
{
_db = db;
}
//etc.
why?
So, why not just provide the arguments and new it?
There is nothing wrong with the use of new - there are a lot of scenario's in which it works best.
But, Inversion Of Control is considered to be a good practice. When doing asp dotnet core you're likely to use it quite often because most libraries provide extension methods to use it. If you are not familiar with it, and your research allow it; you should definitely give it a try.
Therefore, instead of providing "just a way to instantiate" the object, I'll try to get you onto this track - inline with the framework. It will save you some hassle afterwards. Besides, otherwise "use an activator's CreateInstance" would just be as valid as an answer ;-)
Some links:
MSDN Fundamentals
MSDN Dependency Injection
Wikipedia Inversion Of Control
As addition of #Stefan's answer there is another way to achieve this. You can set db connection string in OnConfiguring method of DbContext class without adding DbContext service in startup.cs.
Setting.cs
public static class Setting
{
public static string ConnectionString { get; set; }
}
Startup.cs
Setting.ConnectionString = Configuration.GetSection("ConnectionStrings:BlexzDbConnection").Value;
BlexzWebDb.cs
public class BlexzWebDb : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer(Setting.ConnectionString);
}
}
}
HomeController.cs
public class HomeController : Controller
{
private readonly BlexzWebDb db;
public HomeController()
{
this.db = new BlexzWebDb();
}
//etc.
Code sample for EF Core 3.1:
public class Test
{
private readonly IServiceProvider _serviceProvider;
public Test(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task<RequestResult> Handle(...)
{
await using var context = CreateContext();
...
}
private DocumentContext CreateContext()
{
var options = _serviceProvider.GetService<IOptions<DocumentContextOptions>>();
return new DocumentContext(options);
}
}
I am struggling with some concepts in ASP.NET 5 (vNext).
One of those is the Dependency Injection approach used for configuration. It seems like I have to pass a parameter all the way through the stack. I'm probably misunderstanding something or doing it wrong.
Imagine I have a config property named "contactEmailAddress". I'll use that config property to send an email when a new order is placed. With that scenario in mind, my ASP.NET 5 stack will look like this:
Startup.cs
public class Startup
{
public IConfiguration Configuration { get; set; }
public Startup(IHostingEnvironment environment)
{
var configuration = new Configuration().AddJsonFile("config.json");
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.Configure<AppSettings>(Configuration.GetSubKey("AppSettings"));
services.AddMvc();
}
public void Configure(IApplicationBuilder app)
{
app.UseErrorPage();
app.UseMvc(routes =>
{
routes.MapRoute("default",
"{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index" });
}
);
app.UseWelcomePage();
}
AppSettings.cs
public class AppSettings
{
public string ContactEmailAddress { get; set; }
}
config.json
{
"AppSettings": {
"ContactEmailAddress":"support#mycompany.com"
}
}
OrderController.cs
[Route("orders")]
public class OrdersController : Controller
{
private IOptions<AppSettings> AppSettings { get; set; }
public OrdersController(IOptions<AppSettings> appSettings)
{
AppSettings = appSettings;
}
[HttpGet("new-order")]
public IActionResult OrderCreate()
{
var viewModel = new OrderViewModel();
return View(viewModel);
}
[HttpPost("new-order")]
public IActionResult OrderCreate(OrderViewModel viewModel)
{
return new HttpStatusCodeResult(200);
}
}
Order.cs
public class Order()
{
public void Save(IOptions<AppSettings> appSettings)
{
// Send email to address in appSettings
}
public static List<Order> FindAll(IOptions<AppSettings> appSettings)
{
// Send report email to address in appSettings
return new List<Order>();
}
}
As the example above shows, I'm passing AppSettings through the entire stack. This does not feel correct. To further my worries, this approach will not work if I'm attempt to use a third-party library that needs to access configuration settings. How can a third-party library access configuration settings? Am I misunderstanding something? Is there a better way to do this?
You are entangling 2 different run time resource provider, AppSettings and Dependency Injection.
AppSettings, provides run-time access to Application specific values like UICulture strings, Contact Email, etc.
DI Containers are factories that Manage access to Services and their lifetime scopes. For example, If a MVC Controller needed access to your EmailService, you would configure
public void ConfigureServices(IServiceCollection services)
{
// Add all dependencies needed by Mvc.
services.AddMvc();
// Add EmailService to the collection. When an instance is needed,
// the framework injects this instance to the objects that needs it
services.AddSingleton<IEmailService, EmailService>();
}
Then, if our Home Controller needs access to your EmailService, we add a dependency on it's Interface by adding it as a parameter to the Controller constructor
public class HomeController : Controller
{
private readonly IEmailService _emailService;
private readonly string _emailContact;
/// The framework will inject an instance of an IEmailService implementation.
public HomeController(IEmailService emailService)
{
_emailService = emailService;
_emailContact = System.Configuration.ConfigurationManager.
AppSettings.Get("ContactEmail");
}
[HttpPost]
public void EmailSupport([FromBody] string message)
{
if (!ModelState.IsValid)
{
Context.Response.StatusCode = 400;
}
else
{
_emailService.Send(_emailContact, message);
The purpose of Dependancy Injection is to manage access and lifetimes of services.
In the previous example, in our Application Startup, we configured the DI Factory to associate application requests for IEmailService with EmailService. So when our Controllers are instantiate by the MVC Framework, the framework notices that our Home Controller expects IEmailService, the framework checks our Application Services Collection. It finds mapping instructions and Inject a Singleton EmailService (a descendant of the occupying Interface) into our Home Controller.
Super Polymorphic Factorific - alodocious!
Why is this important?
If your contact email changes, you change the AppSetting value and are done. All requests for "ContactEmail" from ConfigurationManager are Globally changed. Strings are easy. No need for Injection when we can just hash.
If your Repository, Email Service, Logging Service, etc changes, you want a Global way to change all references to this service. Service reference aren't as easily transferred as immutable string literals. Service instantiation should be handled by a factory to configure the Service's settings and dependencies.
So, in a year you develop a RobustMailService:
Class RobustMailService : IEmailService
{
....
}
As long as your new RobustMailService inherits and implements the IEmailService Interface, you can substitute all references to your mail service Globally by changing :
public void ConfigureServices(IServiceCollection services)
{
// Add all dependencies needed by Mvc.
services.AddMvc();
// Add RobustMailService to the collection. When an instance is needed,
// the framework injects this instance to the objects that needs it
services.AddSingleton<IEmailService, RobustMailService>();
}
This can be achieved using IOptions assessor service as it seems you were trying.
We can begin by creating a class with all of the variables that your controller needs from configuration.
public class VariablesNeeded
{
public string Foo1{ get; set; }
public int Foo2{ get; set; }
}
public class OtherVariablesNeeded
{
public string Foo1{ get; set; }
public int Foo2{ get; set; }
}
We now need to tell the middleware that the controller needs this class in the constructor of the controller using dependency injection, we do this using IOptions accessor service.
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
public class MyController: Controller{
private readonly VariablesNeeded _variablesNeeded;
public MyController(IOptions<VariablesNeeded> variablesNeeded) {
_variablesNeeded= variablesNeeded.Value;
}
public ActionResult TestVariables() {
return Content(_variablesNeeded.Foo1 + _variablesNeeded.Foo2);
}
}
To get the variables from your configuration files, we create a constructor for the startup class, and a configuration property.
public IConfigurationRoot Configuration { get; }
public Startup(IHostingEnvironment env)
{
/* This is the fairly standard procedure now for configuration builders which will pull from appsettings (potentially with an environmental suffix), and environment variables. */
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();
}
Now we need to make sure the pipeline actually supplies the controller with this service.
In your ConfigureServices method in your Startup class, you want to use the Options middleware, and inject an object of type VariablesNeeded in to the pipeline.
public void ConfigureServices(IServiceCollection services)
{
// Tells the pipeline we want to use IOption Assessor Services
services.AddOptions();
// Injects the object VariablesNeeded in to the pipeline with our desired variables
services.Configure<VariablesNeeded>(x =>
{
x.Foo1 = Configuration["KeyInAppSettings"]
x.Foo2 = Convert.ToInt32(Configuration["KeyParentName:KeyInAppSettings"])
});
//You may want another set of options for another controller, or perhaps to pass both to our "MyController" if so, you just add it to the pipeline
services.Configure<OtherVariablesNeeded>(x =>
{
x.Foo1 = "Other Test String",
x.Foo2 = 2
});
//The rest of your configure services...
}
For more information see the chapter on Using Options and configuration objects in the ASPCore Docs
I starting to learn changes in ASP.NET 5(vNext)
and cannot find how to get IServiceProvider, for example in "Model"'s method
public class Entity
{
public void DoSomething()
{
var dbContext = ServiceContainer.GetService<DataContext>(); //Where is ServiceContainer or something like that ?
}
}
I know, we configuring services at startup, but where all service collection staying or IServiceProvider?
You have to bring in Microsoft.Extensions.DependencyInjection namespace to gain access to the generic
GetService<T>();
extension method that should be used on
IServiceProvider
Also note that you can directly inject services into controllers in ASP.NET 5. See below example.
public interface ISomeService
{
string ServiceValue { get; set; }
}
public class ServiceImplementation : ISomeService
{
public ServiceImplementation()
{
ServiceValue = "Injected from Startup";
}
public string ServiceValue { get; set; }
}
Startup.cs
public void ConfigureService(IServiceCollection services)
{
...
services.AddSingleton<ISomeService, ServiceImplementation>();
}
HomeController
using Microsoft.Extensions.DependencyInjection;
...
public IServiceProvider Provider { get; set; }
public ISomeService InjectedService { get; set; }
public HomeController(IServiceProvider provider, ISomeService injectedService)
{
Provider = provider;
InjectedService = Provider.GetService<ISomeService>();
}
Either approach can be used to get access to the service. Additional service extensions for Startup.cs
AddInstance<IService>(new Service())
A single instance is given all the time. You are responsible for initial object creation.
AddSingleton<IService, Service>()
A single instance is created and it acts like a singleton.
AddTransient<IService, Service>()
A new instance is created every time it is injected.
AddScoped<IService, Service>()
A single instance is created inside of the current HTTP Request scope. It is equivalent to Singleton in the current scope context.
Updated 18 October 2018
See: aspnet GitHub - ServiceCollectionServiceExtensions.cs
I don't think it is a good idea for an entity (or a model) to have access to any service.
Controllers, on the other hand, do have access to any registered service in their constructors, and you don't have to worry about it.
public class NotifyController : Controller
{
private static IEmailSender emailSender = null;
protected static ISessionService session = null;
protected static IMyContext dbContext = null;
protected static IHostingEnvironment hostingEnvironment = null;
public NotifyController(
IEmailSender mailSenderService,
IMyContext context,
IHostingEnvironment env,
ISessionService sessionContext)
{
emailSender = mailSenderService;
dbContext = context;
hostingEnvironment = env;
session = sessionContext;
}
}
use GetRequiredService instead of GetService, like the example on ASP.NET Core tutorials ( https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/working-with-sql )
documentation on the method:
https://learn.microsoft.com/en-us/aspnet/core/api/microsoft.extensions.dependencyinjection.serviceproviderserviceextensions#Microsoft_Extensions_DependencyInjection_ServiceProviderServiceExtensions_GetRequiredService__1_System_IServiceProvider_
using Microsoft.Extensions.DependencyInjection;
using (var context = new ApplicationDbContext(serviceProvicer.GetRequiredService<DbContextOptions<ApplicationDbContext>>()))
Do not use GetService()
The difference between GetService and GetRequiredService is related with exception.
GetService() returns null if a service does not exist.
GetRequiredService() will throw exception.
public static class ServiceProviderServiceExtensions
{
public static T GetService<T>(this IServiceProvider provider)
{
return (T)provider.GetService(typeof(T));
}
public static T GetRequiredService<T>(this IServiceProvider provider)
{
return (T)provider.GetRequiredService(typeof(T));
}
}
Generally you want to have the DI do its thing and inject that for you:
public class Entity
{
private readonly IDataContext dbContext;
// The DI will auto inject this for you
public class Entity(IDataContext dbContext)
{
this.dbContext = dbContext;
}
public void DoSomething()
{
// dbContext is already populated for you
var something = dbContext.Somethings.First();
}
}
However, Entity would have to be automatically instantiated for you... like a Controller or a ViewComponent. If you need to manually instantiate this from a place where this dbContext is not available to you, then you can do this:
using Microsoft.Extensions.PlatformAbstractions;
public class Entity
{
private readonly IDataContext dbContext;
public class Entity()
{
this.dbContext = (IDataContext)CallContextServiceLocator.Locator.ServiceProvider
.GetService(typeof(IDataContext));
}
public void DoSomething()
{
var something = dbContext.Somethings.First();
}
}
But just to emphasize, this is considered an anti-pattern and should be avoided unless absolutely necessary. And... at the risk of making some pattern people really upset... if all else fails, you can add a static IContainer in a helper class or something and assign it in your StartUp class in the ConfigureServices method: MyHelper.DIContainer = builder.Build(); And this is a really ugly way to do it, but sometimes you just need to get it working.
I think the OP is getting confused. Entities should be as “thin” as possible. They should try not to contain logic, and or external references other than navigation properties. Look up some common patterns like repository pattern which helps to abstract your logic away from the entities themselves
Instead of getting your service inline, try injecting it into the constructor.
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient(typeof(DataContext));
}
}
public class Entity
{
private DataContext _context;
public Entity(DataContext context)
{
_context = context;
}
public void DoSomething()
{
// use _context here
}
}
I also suggest reading up on what AddTransient means, as it will have a significant impact on how your application shares instances of DbContext. This is a pattern called Dependency Injection. It takes a while to get used to, but you will never want to go back once you do.