I have created an ASP.NET Core 8.0 MVC app.
Here is my AppDbContext
class:
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public DbSet<User> Users { get; set; }
}
DbRepo
class:
public class DbRepo
{
internal readonly AppDbContext db;
internal DbRepo(AppDbContext context)
{
this.db = context;
}
}
Here's a user repository where I need to use DbContext
, there could be multiple repositories:
public class UserRepository : DbRepo, IUser<User, User, string>
{
public async Task<bool> Create(User user)
{
try
{
if (!await IsExist(user))
{
await db.AddAsync(user);
}
return db.SaveChanges() > 0;
}
catch (Exception)
{
return false;
}
}
}
This is the DataAccessFactory
class for loosely coupled dependencies:
public class DataAccessFactory
{
public static IUser<User, User, string> UserData()
{
return new UserRepository();
}
}
Now service will call to the DataAccessFactory
to repository methods:
public class UserService
{
public static Task<bool> CreateUser(UserDTO newUser)
{
User user = UserMapping(newUser);
return DataAccessFactory.UserData().Create(user);
}
}
And then this service will call from controller. Here is the problem, there should to give an argument for inherit DbRepo
class for every repo.
How could I solve it?
I added this in Program.cs
:
builder.Services.AddScoped<DbRepo>();
There's a confusing detail which is that UserRepository
implements an interface called IUser
. The class name describes something that stores or retrieves users. It wouldn't be a user. But I'm going to sidestep that because it's possible to answer the question anyway.
This will interfere with inversion of control:
public class UserService
{
public static Task<bool> CreateUser(UserDTO newUser)
{
User user = UserMapping(newUser);
return DataAccessFactory.UserData().Create(user);
}
}
UserService
depends on UserRepository
, but it controls how it is created. It's going to call DataAccessFactory.UserData().Create(user);
which calls new UserRepository()
.
The way it's written there's no way to change that dependency. You could get rid of DataAccessFactory
and just directly call new UserRepository()
and the result would be identical. There's a fixed dependency on UserRepository
.
Instead of UserService
making that decision, you can change it like this:
public class UserService
{
private readonly UserRepository _userRepository;
public UserService(UserRepository userRepository)
{
_userRepository = userRepository;
}
// no longer static
public Task<bool> CreateUser(UserDTO newUser)
{
User user = UserMapping(newUser);
return _userRepository.CreateUser
}
}
Now UserService
doesn't control creation of UserRepository
. Something else must create that repository and pass it to the UserService
constructor.
As a result, now the argument doesn't strictly have to be a UserRepository
. It can also be any type that inherits from UserRepository
. That means the class is no longer coupled to that exact type.
That's an improvement, but this is where we often add an interface like IUserRepository
. That's not strictly necessary but there are good reasons for it.
public interface IUserRepository
{
Task<bool> Create(User user);
}
UserRepository
would implement that interface.
Then we would change UserService
to depend on the interface instead of the concrete class.
public class UserService
{
private readonly IUserRepository _userRepository;
public UserService(IUserRepository userRepository)
{
_userRepository = userRepository;
}
// no longer static
public Task<bool> CreateUser(UserDTO newUser)
{
User user = UserMapping(newUser);
return _userRepository.CreateUser
}
}
Now UserService
only knows about the interface. We can pass to it any class that implements it. That's useful for testing. We can pass a fake or mocked implementation of IUserRepository
that always returns some expected value so we can verify that UserService
does what we expect in response to various results it receives.
At runtime (when the application actually runs) you probably want the implementation of IUserRepository
to be a specific concrete class like UserRepository
. Assuming that you're using Microsoft's dependency injection container, you'll have code in Startup
or Program
that registers dependencies with an IServiceCollection
. (I can't be more specific without knowing how your app is configured.)
In that method you'll configure the application to create various dependencies. For example, if you've modified UserService
to depend on IUserRepository
, that code might look like this:
services.AddScoped<IUserRepository, UserRepository>();
That means
When you have to supply an
IUserRepository
to some class's constructor, create an instance ofUserRepository
.
I've glossed over dependency injection some. You can read more here.
But as it directly relates to your question, this change implements inversion of control. Instead of UserService
saying
I need a repository so I'm going to create one (thereby controlling how it gets created)
it says
Please inject an
IUserRepository
into my constructor when you create me. I don't control how it is created. The application controls that.