How to Inject Generic Repository(Or Business Logic Layer) Into ViewModel - c#

For simplicity sake, I'd like to Inject a generic repository into a view model. I'm add the the ViewModel and Repository to the services collection and the ViewModel depends on the Repository. From what I've read, the ViewModel's constructor parameters should be resolved automatically, since it's using IConfiguration. I've tried explicitly instantiating the ViewModel in the Services, but still get the same runtime error and seems to defeat the purpose as I'm creating another instance of the repository.
Repository/IRepository look like this
public class Repository<TPoco, TDatabaseConnection> : IRepository<TPoco, TDatabaseConnection>
where TPoco : class
where TDatabaseConnection : IDbConnection, new()
{
public Repository(IConfiguration configuration, string connectionName = "DefaultConnection" )//SqlConnectionConfiguration configuration) //(string connectionString)
{
_connectionString = configuration.GetConnectionString(connectionName);
_configuration = configuration;
}
...
View Model
public class PersonViewModel
{
private IRepository<Person, IDbConnection> _PersonRepository;
public List<Person> Persons { get; set; }
public PersonViewModel(IRepository<Person, IDbConnection> personRepository)
{
_PersonRepository=personRepository;
}
...
In Startup.cs file I add the services like so:
services.AddScoped<IRepository<Person, SQLiteConnection>, Repository<Person, SQLiteConnection>>();
services.AddScoped<PersonViewModel>(); //runtime errors here
I'm getting two runtime errors (System.AggregateException)
Inner Exception 1:
InvalidOperationException: Error while validating the service descriptor 'ServiceType: BlazorInjection.ViewModels.PersonViewModel Lifetime: Scoped ImplementationType: BlazorInjection.ViewModels.PersonViewModel': Unable to resolve service for type 'DapperRepository.IRepository`2[BlazorInjection.Models.Person,System.Data.IDbConnection]' while attempting to activate 'BlazorInjection.ViewModels.PersonViewModel'.
Inner Exception 2:
InvalidOperationException: Unable to resolve service for type 'DapperRepository.IRepository`2[BlazorInjection.Models.Person,System.Data.IDbConnection]' while attempting to activate 'BlazorInjection.ViewModels.PersonViewModel'.
Am I missing a concept? How do I correct this?

change class structure to:
public class PersonViewModel
{
private IRepository<Person> _PersonRepository;
public List<Person> Persons { get; set; }
public PersonViewModel(IRepository<Person> personRepository)
{
_PersonRepository = personRepository;
}
}
public interface IDbConnection
{
}
public class Person { }
public interface IRepository<TPoco> { }
public class SQLiteConnection : IDbConnection
{
private string _connectionString;
private IConfiguration _configuration;
public SQLiteConnection(IConfiguration configuration, string connectionStringName)
{
_connectionString = configuration.GetConnectionString(connectionStringName);
_configuration = configuration;
}
}
public class Repository<TPoco> : IRepository<TPoco>
where TPoco : class
{
public IDbConnection Connection { get; }
public Repository(IDbConnection connection)
{
Connection = connection;
}
}
And than in Startup.cs simply change to
services.AddScoped<IDbConnection>(sp => ActivatorUtilities.CreateInstance<SQLiteConnection>(sp, "defaultConnectionStringName"));
services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
services.AddScoped<PersonViewModel>();
Notice generic repository registration which makes it easy to extend your data layer. ActivatorUtilities allows you to combine current scope container services with custom parameters. This way (one generic, injected connection) is also in my opinion better from the design perspective, as your repository clients do not need to know the underlying database implementation.

Related

Access DBContext in ASP.NET Core 6 from data access layer class

I have an ASP.NET Core 6 MVC app that I created from a VS 2022 template. I'm writing a custom data access layer and business logic layer.
I know I can pass the _context from the controllers down through the BLL in the DAL, however I would prefer to have direct access from the DAL. I don't see any reason the BLL or the web code need to have anything to do with data access.
I've tried several examples of injection, but I can't seem to get any of them to work. Does anyone have a good solution?
Edit:
In Project.cs I have added DbContext:
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
I would like to get access to DbContext in my custom data access layer without having to pass DbContext all the way down from a Controller into my DAL contructor like so:
namespace MyProject.Code
public class CustomDataAccessLayer
{
public string[] GetCustomers()
{
DbConnection conn = DbContext.GetConnection();
//Whatever query logic I want to do here
Is there any way to access DbConext directly without having to pass it in from within a controller action?
Here you can read about how DI works exactly.
I'll provide some example of how you're supposed to do that.
first you need to have DataContext Class that extends DbContext Class that will be like this
using Microsoft.EntityFrameworkCore;
namespace MyProject.Context
{
public class DataContext : DbContext
{
public DataContext(DbContextOptions options) : base(options)
{
}
public DbSet<MyEntity> Entities { get; set; }
}
}
In the Program.cs you need to add theDataContextlike this
builder.Services.AddDbContext<DataContext>(options =>
{
options.UseSqlServer(builder.Configuration
.GetConnectionString("DefaultConnection")); // for example if you're holding the connection string in the appsettings.json
});
and then in the Logic class that you want to use it in make a data member in the class of type DataContext
private readonly DataContext _dataContext;
and your constructor must take DataContext as parameter and Initialize your constructor will look like this
public ClassName(DataContext dataContext)
{
_dataContext = dataContext;
}
you don't need to add dbcontext to a controller, nobody does it if there is a data access layer, DI will automatically inject the context
public class CustomDataAccessLayer
{
private readonly ApplicationDbContext _context;
public CustomDataAccessLayer(ApplicationDbContext context)
{
_context = context;
}
public string[] GetCustomers()
{
//your code
}
}
with json :
var constr = builder.Configuration["ConnectionStrings:Default"];
builder.Services.AddDbContext<AppDbContext>(opt => {
opt.UseSqlServer(constr);
});
Json :
"ConnectionStrings": {
"Default": "Server=Victus;Database=TeamDb;Trusted_Connection=true;"
}
AppDbContext.cs :
public class AppDbContext:DbContext {
public AppDbContext(DbContextOptions <AppDbContext> options) : base(options) { }
public DbSet<team> teams { get; set; }
}
Home Controller :
private AppDbContext _appDbContext;
public HomeController(AppDbContext appDbContext) {
_appDbContext = appDbContext;
}
public IActionResult Index() {
return View(_appDbContext.teams);
}

Derived Class How to inject ?

ASP.Net Core Web API
Does the parent class have no empty constructor
derived class Autofac injection ?
If the injection class is added after the parameter, it cannot be used
public class A
{
public A(string e1,string e2){}
}
public class B:A
{
private readonly IProductService _productService;
public B(IProductService productService):base(string e1,string e2)
{
_productService = productService
}
public void test()
{
_productService.AddProduct("");
}
}
AutoFac has no problem configuring
_productService exception occurred
You should try it like this:
public B(IProductService productService, string e1,string e2):base(e1,e2)
{
_productService = productService
}
And then configure Autofac like this for this class registration:
builder.Register(c => new B(c.Resolve<IProductService>(), "e1_val","e2_val"));
If the B class will implement an interface at some point you can use it like this also:
builder.RegisterType<B>().As<IB>()
.WithParameter("e1", "e1value")
.WithParameter("e2", "e2value");
Keep in mind that you have a lot of flexibility with Autofac, please check their documentation at: Autofac Parameters Register for even more information.

How can I configure Unity-Container to register a class by providing that class a string value?

I have an ASP.NET MVC 5 based app. I am trying to use Unity.Mvc container to help me with dependency injection.
I have the following DbContext class
public class MainContext : DbContext
{
public DbSet<User> Users { get; set; }
// .....
public MainContext(string connectionName)
: base(connectionName)
{
Database.SetInitializer<MainContext>(null);
}
}
The production value I want to use when constructing the MainContext class is DefaultConnection. I want to register the MainContext class into the container and tell Unity to provide the DefaultConnection string to the constructor when using resolving it.
I tried the following
container.RegisterType<MainContext>("DefaultConnection", new PerRequestLifetimeManager(), new InjectionConstructor(new ResolvedParameter<string>("DefaultConnection")));
Also tried this
container.RegisterType<MainContext>("DefaultConnection", new PerRequestLifetimeManager(), new InjectionConstructor("DefaultConnection"));
But I keep getting the following error
Resolution of the dependency failed, type = 'System.Web.Mvc.IController', name = 'MyProject.Controllers.HomeController'.
Exception occurred while: while resolving.
Exception is: InvalidOperationException - The type String cannot be constructed. You must configure the container to supply this value.
-----------------------------------------------
At the time of the exception, the container was:
Resolving MyProject.Controllers.HomeController, MyProject.Controllers.HomeController (mapped from System.Web.Mvc.IController, MyProject.Controllers.HomeController)
Resolving parameter 'unitOfWork' of constructor MyProject.Controllers.HomeController(MyProject.Repositories.Contracts.IUnitOfWork unitOfWork)
Resolving MyProject.Repositories.UnitOfWork,(none) (mapped from MyProject.Repositories.Contracts.UnitsOfWork.IUnitOfWork, (none))
Resolving parameter 'context' of constructor MyProject.Repositories.UnitOfWork(MyProject.Contexts.MainContext context)
Resolving MyProject.Contexts.MainContext,(none)
Resolving parameter 'connectionName' of constructor MyProject.Contexts.MainContext(System.String connectionName)
Resolving System.String,(none)
A workaround would be to change my MainContext class to this
public class MainContext : DbContext
{
public DbSet<User> Users { get; set; }
// .....
public MainContext(string connectionName)
: base(connectionName)
{
Database.SetInitializer<MainContext>(null);
}
public MainContext(string connectionName)
: this("DefaultConnection")
{
}
}
Then register my class like so
container.RegisterType<MainContext>(new PerRequestLifetimeManager(), new InjectionConstructor());
But, I don't want to couple my implementation with a specific connection-name. I want the IoC to provide the connection name to MainContext. In another words, I want to be able to swap out connection name without changing the MainContext class.
How can I correctly tell Unity-Container to register the MainContext class and use a the DefaultConnection when it is constructed?
This does not directly answer my question but gave me a flexible workaround.
I created the following interface
public interface IDatabaseConnection
{
string NameOrConnectionString { get; }
}
Then I created the following implementations of this class
public class DefaultDatabaseConnection: IDatabaseConnection
{
public string Name { get; private set; }
public DefaultDatabaseConnection()
{
NameOrConnectionString = "DefaultConnection";
}
public DefaultDatabaseConnection(string connectionName)
{
NameOrConnectionString = connectionName;
}
}
Then I changed my MainContext class to this
public class MainContext : DbContext
{
public DbSet<User> Users { get; set; }
// .....
public MainContext(IDatabaseConnection connection)
: base(connection.NameOrConnectionString)
{
Database.SetInitializer<MainContext>(null);
}
}
Finally, I registered my classes like so
container.RegisterType<IDatabaseConnection, DefaultDatabaseConnection>("DefaultDatabaseConnection", new InjectionConstructor());
container.RegisterType<MainCompanyContext>(new PerRequestLifetimeManager(), new InjectionConstructor(new ResolvedParameter<IDatabaseConnection>("DefaultDatabaseConnection")));
I will accept other answers over mine if a better answer is presented, so please guide me if there is simpler way or if there is a way to provide a raw string to the InjectionConstructor class.
Static Value
If you want to hardcode a string to be passed to the constructor, you can simply do:
container.RegisterType<IMyInterface, MyClass>(
new InjectionConstructor(
"ThisIsAHardCopedString",
new ResolvedParameter<IOtherInput>()));
Parameterized Value
However, if you want to use a string from the configuration file, for example, you can do this:
container.RegisterType<IMyInterface, MyClass>(
new InjectionConstructor(
new ResolvedParameter<string>("TheNameOfMyString"),
new ResolvedParameter<IOtherInput>()));
And in your main project, you register the string:
container.RegisterInstance<string>("TheNameOfMyString", Settings.Default.ParameterName);
It looks like you want solution 1, but I'd suggest solution 2, to make it easier to change in the future.
Note: You can have LifetimeManagers coexisting with the solution, it's just not relevant for the solution.

Accessing strongly typed configuration settings directly into class library in ASP.NET 5 (vNext)?

I have an ASP.NET 5 MVC 6 application. It has a Data Access library which needs a connection string to make a connection to the database.
Currently I am passing a strongly typed configuration settings class with connection string as a public property all the way up from the MVC controllers (Where it is received through DI) to the Data Access Class library.
I want to know if there is a better way for a class library to access strongly typed configuration settings using dependency injection or any other mechanism ?
Thank you.
EDIT : Code Example
This is a generic DbTransaction class which is called from the business layer.
public class DbTransactions<TEntity> where TEntity : DbEntity, new()
{
private readonly Query _query;
public DbTransactions(string connectionString)
{
_query = new Query(connectionString);
}
public TEntity GetById(long id)
{
var sqlGenerator = new SqlGenerator<TEntity>();
var sql = sqlGenerator.GetSelectByIdQuery();
var mapper = new NMapper.Mapper<TEntity>();
var cmd = _query.GetNpgsqlCommand(sql, new { id });
return mapper.GetObject(cmd);
}
}
The query class creates the connection object from the connection string that is provided to it.
I agree with #Steven that using IOptions<T> is a bad idea. You can however use the ConfigurationBinder extensions to read out a specific section of configuration into a strongly-typed POCO class. Just make sure you have this somewhere in your project.json's dependencies section:
"dependencies": {
[other dependencies],
"Microsoft.Extensions.Configuration.Binder": "1.0.0-rc1-final",
[other dependencies]
}
Just build up your configuration as normal. For example, say you had a Database.json configuration file that looked like this:
{
"Database": {
"ConnectionInfo": {
"connectionString": "myConnectionString"
}
}
}
You can build your configuration from the Startup method in Startup.cs:
public IConfiguration Configuration { get; private set; }
public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv) {
IConfigurationBuilder configBuilder = new ConfigurationBuilder()
.SetBasePath(appEnv.ApplicationBasePath)
.AddJsonFile("Database.json")
.AddEnvironmentVariables()
Configuration = configBuilder.Build();
}
Now we can make a POCO class to match the "Database:ConnectionInfo" section of the JSON configuraiton file. You can match it to an interface as #janhartmann suggests, but it may or may not be necessary.
public class DatabaseConnectionInfo {
public string ConnectionString { get; set; }
}
Now, how can we get that DatabaseConnectionInfo class populated with the data from the JSON config file? One way is to use the IOptions<T> framework type, but I don't like using framework types when I can avoid them. Instead, you can get an instance like so:
DatabaseConnectionInfo dbConnInfo = Configuration
.GetSection("Database:ConnectionInfo")
.Get<DatabaseConnectionInfo>();
Now you can just register the dbConnInfo type as a singleton of the type DatabaseConnectionInfo (or as a singleton of an interface type if you prefer to have an immutable configuration settings object). Once it's registered in the IoC container, you can constructor inject it where needed:
public class DbTransactions<TEntity> where TEntity : DbEntity, new()
{
private readonly Query _query;
public DbTransactions(DatabaseConnectionInfo dbConnInfo)
{
_query = new Query(dbConnInfo.ConnectionString);
}
public TEntity GetById(long id) { ... }
}
You can let your service class depend on a an interface, e.g.:
public interface IConnectionFactory {
string ConnectionString();
}
public class MyDataAccessClass {
private readonly IConnectionFactory _connectionFactory
public MyDataAccessClass(IConnectionFactory connectionFactory) {
_connectionFactory = connectionFactory;
}
public void Whatever() {
var connectionString = _connectionFactory.ConnectionString();
}
}
And then make an implementation of it (as near to your composition root as possible):
public class SqlConnectionFactory : IConnectionFactory {
public string ConnectionString() {
return "myConnectionString";
}
}
Let the interface have the methods or properties you need.
Wire like:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IConnectionFactory, SqlConnectionFactory>();
}
I use a similar method to some of those listed earlier, but I think its sufficiently different to warrant another answer.
Firstly I define an interface with all the configuration that my class needs. In this case
public interface IDbTransactionsConfiguration {
string ConnectionString { get; }
}
Then I alter my class to take this configuration via constructor injection
public class DbTransactions<TEntity> where TEntity : DbEntity, new() {
public DbTransactions(IDbTransactionsConfiguration configuration) {
...
}
}
Then I define a class that handles all the configuration for my application.
public class MyApplicationConfiguration : IDbTransactionsConfiguration, ISomeOtherConfiguration, etc {
public string ConnectionString { get; }
... other configuration
}
Then I pass this class into all classes that need it using some kind of Depenendency Injection (normally Castle Windsor or AutoFac for me).
If it is too difficult to construct DbTransactions for legacy type reasons, I define a static version of MyApplicationConfiguration and access this directly.
More details on this blog post.

InstancePerApiControllerType not working

I am attempting to configure a web service with Autofac so that I can map a different connection context for each controller:
// database connections
container.Register(c => new DocumentControllerActivator()).As<IHttpControllerActivator>().InstancePerApiControllerType(typeof(DocumentController));
container.Register(c => new WorkflowControllerActivator()).As<IHttpControllerActivator>().InstancePerApiControllerType(typeof(WorkflowController));
and:
public class WorkflowControllerActivator : IHttpControllerActivator
{
// snip...
var connectionString = "workflow connection string";
var container = new ContainerBuilder();
container.Register(c =>
{
var newConnectionContext = new SqlServerConnectionContext(connectionString) {ProductID = productId};
newConnectionContext.Open();
return newConnectionContext;
}).As<ISqlServerConnectionContext>().As<IConnectionContext>().InstancePerApiRequest();
var dr = (AutofacWebApiDependencyResolver)GlobalConfiguration.Configuration.DependencyResolver;
container.Update(dr.Container.ComponentRegistry);
return (IHttpController)request.GetDependencyScope().GetService(typeof(WorkflowController));
}
The DocumentControllerActivator differs only in the connection string the the return object type.
[AutofacControllerConfiguration]
public class WorkflowController : ApiController
When I attempt to access the service, the DocumentController throws an error saying that "Unable to cast object of type 'SearchService.Controllers.WorkflowController' to type 'SearchService.Controllers.DocumentController'." It's as if the second InstancePerApiControllerType registration is overwriting the first (i.e. it is doing it for all controllers).
Any suggestions where I've gone wrong? Or, an alternate solution? (Other than a service locator pattern in each controller.)
Not sure why the ControllerActivator approach isn't working, but a simpler and less web api stack specific alternative could be:
public interface ISqlServerConnectionContextFactory
{
ISqlServerConnectionContext Create();
}
// Register this with your ContainerBuilder
public class SqlServerConnectionContextFactory : ISqlServerConnectionContextFactory
{
private string _connectionString;
public SqlServerConnectionContextFactory(string connectionString)
{
_connectionString = connectionString;
}
public ISqlServerConnectionContext Create()
{
var connectionContext = new SqlServerConnectionContext(_connectionString);
connectionContext.Open();
return connectionContext;
}
}
public class MyController : ApiController
{
private ISqlServerConnectionContext _sqlServerConnectionContext;
public MyController(Func<string, ISqlServerConnectionContextFactory> connectionFactory)
{
_sqlServerConnectionContext = connectionFactory("MyConnectionString");
}
}
See http://nblumhardt.com/2010/01/the-relationship-zoo/ for more information about AutoFac relationships and auto generated factories
In this case, when the AutoFac Container sees the Func parameter, it passes in a delegate that acts as a factory method that returns a ISqlServerConnectionContextFactory and passes the string through to its constructor. AutoFac really is rather clever!

Categories