Dependency Injection containers - how do I get a specific instance of an object? - c#

For example, I have a Repository class for getting data from a database, and there are several service classes, let's say Service1, Service2, Service3.
I will have multiple Repository instances, for example, for two or three databases. And, you should be able to configure services to work with a specific database.
I can't figure out how to implement these dependencies using the Dependency Injection container.
As far as I understand, I can register the Repository service either as a Singleton, or a new instance will be created for each dependency.
But, I only need two repositories, Repository ("DB1") and Repository ("DB2"), and when creating a service instance, I should be able to choose which database to work with. That is, as an option-Service1(Repository ("DB1")), Service2 (Repository ("DB1")), Service1 (Repository ("DB2")).
For example:
public class Program
{
static void Main()
{
var connectionStringDb1 = "DB1 connection string";
var connectionStringDb2 = "DB2 connection string";
var repositoryDb1 = new Repository(connectionStringDb1);
var repositoryDb2 = new Repository(connectionStringDb2);
var smsSendService1 = new SmsSendService(repositoryDb1);
var smsSendService2 = new SmsSendService(repositoryDb2);
var emailSendService1 = new EmailSendService(repositoryDb1);
smsSendService1.Run();
var tasks = new Task[]
{
smsSendService1.Run(),
smsSendService2.Run(),
emailSendService1.Run()
};
Task.WaitAll(tasks);
}
}
public class Repository
{
private string _connectionString;
public Repository(string connectionString)
{
_connectionString = connectionString;
}
public object GetData()
{
// Getting data from the Database
var data = ...;
return data;
}
}
public class SmsSendService
{
private readonly Repository _repository;
public SmsSendService(Repository repository)
{
_repository = repository;
}
public Task Run()
{
return Task.Run(() =>
{
// Sending SMS in a loop
while (true)
{
var data = _repository.GetData();
// ...
Task.Delay(1000);
}
});
}
}
public class EmailSendService
{
private readonly Repository _repository;
public EmailSendService(Repository repository)
{
_repository = repository;
}
public Task Run()
{
return Task.Run(() =>
{
// Sending Email in a loop
while (true)
{
var data = _repository.GetData();
// ...
Task.Delay(1000);
}
});
}
}

Try to take a look at autofac named instances

Related

C# Repositories and Http responses not returning expected data using same dbContext XUnit

I've been trying to implement integration tests with a database per test strategy, but I haven't been able to make it work as needed.
This is the factory class that uses WebApplicationFactory:
public class TestFactory<TProgram, TDbContext> : WebApplicationFactory<TProgram>
where TProgram : class where TDbContext : DbContext
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
services.RemoveDbContext<TDbContext>();
services.AddDbContext<TDbContext>(options =>
{
options.UseInMemoryDatabase(Guid.NewGuid().ToString());
});
services.EnsureDbCreated<TDbContext>();
});
}
}
This is the TestClass:
public class RolesControllerTest : IDisposable
{
private readonly TestFactory<Program, ADbContext> _factory;
private IServiceScope _scope;
private ADbContext_dbContext;
private readonly HttpClient _client;
private IRoleRepository _rolesRepository;
public RolesControllerTest()
{
_factory = new TestFactory<Program, ADbContext>();
_client = _factory.CreateClient();
_scope = _factory.Services.CreateScope();
var scopedServices = _scope.ServiceProvider;
_dbContext = scopedServices.GetRequiredService<ADbContext>();
_dbContext.Database.EnsureCreated();
}
public void Dispose()
{
_factory.Dispose();
_scope.Dispose();
}
// Tests ...
}
This is the test:
[Fact(DisplayName = "GetAsync returns a list of role models")]
public async Task GetAsync_ReturnsTaskOfRoleModelList()
{
var roleModelInDb = new RoleModel
{
Id = Guid.NewGuid(),
Name = "Role A",
Description = "Role A Description"
};
_rolesRepository = new RoleRepository(_dbContext, TestMapperHelper.GenerateTestMapper());
var roleModel = await _rolesRepository.CreateAsync(roleModelInDb);
var responseData = await _client.GetFromJsonAsync<List<RoleModel>>("/api/roles");
responseData.ShouldNotBeNull();
responseData.ShouldBeOfType<List<RoleModel>>();
responseData.Count.ShouldBe(1);
responseData[0].Id.ShouldBe(roleModel.Id);
responseData[0].Name.ShouldBe(roleModelInDb.Name);
}
The repository returns the expected data: the new roleModel that's been added to the db.
The responseData is a list as expected, but it's empty, so the test fails.
If I try to use a client instead of the repository to create the initial roleModel:
var createdResponse = await _client.PostAsJsonAsync("/api/roles", roleModelInDb);
var createdByClient = await TestResponseHelper.GetResponseContent<RoleModel>(createdResponse);
The createdResponse is a 200 OK Http response, and the role model createdByClient is a valid RoleModel, but the test fails, the list is still empty.
If I use a roleRepository to find the previously created roleModel by Id, the result is null.
If I'm using the same database context for the web factory and repositories, why is this happening?

How to call class with dependency injection (DI)

Description
I want to create an object of a class with dependency injection. If I set the parameter manually I got the exception Cannot access a disposed of the object..
This Application is a Blazor wasm with Dotnet core 3.1. I´ve created a Middleware that should connect to a query console. So I have a static list that contains all query clients. If a client is missing it will be created.
Invoke Async in the middleware:
public async Task InvokeAsync(HttpContext context,
IConfiguration configuration,
IInstanceControlRepository instanceControlRepository,
IServiceProvider serviceProvider)
{
_configuration = configuration;
_instanceControlRepository = instanceControlRepository;
long timestamp = new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds();
var instances = _instanceControlRepository.GetAllInstances();
if (_time + 3 <= timestamp)
{
_time = timestamp;
// Remove
foreach(var client in TeamspeakInstanceQueryClients.ToList())
{
var cl = instances.ToList().Find(el => el.Id == client.Instance.Id);
if(cl == null)
{
client.Dispose();
TeamspeakInstanceQueryClients.RemoveAll(el => el.Instance.Equals(client.Instance));
}
}
// Create & Update
foreach (var instance in instances)
{
var queryClient = TeamspeakInstanceQueryClients.Find(el => el.Instance.Id == instance.Id);
if(queryClient == null)
{
//var test = ActivatorUtilities.CreateInstance<ApplicationDbContext>(serviceProvider);
//var dbContext = serviceProvider.GetService<ApplicationDbContext>();
//queryClient = new TeamspeakInstanceQueryClient(new InstancesControlRepository(ActivatorUtilities.CreateInstance<ApplicationDbContext>(serviceProvider)));
queryClient = new TeamspeakInstanceQueryClient(serviceProvider);
_ = queryClient.Connect(instance);
TeamspeakInstanceQueryClients.Add(queryClient);
}
else
{
_ = queryClient.CheckInstanceData(instance);
}
}
}
await _next(context);
}
TeamspeakInstanceQueryClient.cs
public partial class TeamspeakInstanceQueryClient : ITeamspeakInstanceQueryClient
{
private IInstanceControlRepository _instanceControlRepository;
private const short MAX_RETRYS = 3;
private const short TIME_TO_RETRY = 10;
private EventHandler OnConnected;
public Instance Instance { get; internal set; }
public TeamSpeakClient Client { get; internal set; }
public bool IsSelected { get; internal set; }
private short _connectionTrys = 0;
public TeamspeakInstanceQueryClient(IServiceProvider serviceProvider)
{
_instanceControlRepository = new InstancesControlRepository(ActivatorUtilities.CreateInstance<ApplicationDbContext>(serviceProvider));
Init();
}
}
InstancesControlRepository.cs
public class InstancesControlRepository : IInstanceControlRepository
{
private readonly ApplicationDbContext _applicationDbContext;
public InstancesControlRepository(ApplicationDbContext applicationDbContext)
{
_applicationDbContext = applicationDbContext;
}
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(option =>
option.UseMySql(
Configuration.GetConnectionString("DefaultConnection"),
mySqlOptions => mySqlOptions.ServerVersion(new System.Version(10, 4, 13), ServerType.MariaDb)
)
);
services.AddScoped<IInstanceControlRepository, InstancesControlRepository>();
services.AddScoped<IServerQueryRepository, ServerQueryRepository>();
What I´ve tried
I´ve tried to create the class with the service provider but it comes to the same error
I´ve tried to create the missing parameters with the service provider in the created class. But I need to inject the service provider which also creates the exception Cannot access a disposed of the object. Object name: 'IServiceProvider'.
I´ve tried to make the service provider static so it can´t be disposed but it is disposed.
It seems that instance of IServiceProvider is a scoped one and it is disposed when the scope ends (in the end of request I assume). You can try define singleton factory for your TeamspeakInstanceQueryClient and use it:
class ClientFactory
{
private IServiceProvider _sp { get; set; }
private IServiceScope _scope { get; set; }
public MyClass(IServiceProvider sp)
{
_sp = sp;
_scope = sp.CreateScope();
}
public TeamspeakInstanceQueryClient Create() => new TeamspeakInstanceQueryClient(_scope.ServiceProvider);
}
// register it as singleton
services.AddSingleton<ClientFactory>();
and use it in InvokeAsync:
var factory = serviceProvider.GetRequiredService<ClientFactory>();
queryClient = factory.Create();
P.S. this code can be improved vastly and is used only for demonstration purposes.

Cannot create Moq test with Interface while making the function virtual works

I'm trying to test this class:
public class Tasks : ITaskEnumerableProvider
{
protected string ConnectionString;
DAL_EFCore.AdventureWorks2017Context CurrentContext;
public Tasks(DAL_EFCore.AdventureWorks2017Context currentContext)
{
CurrentContext = currentContext;
}
public Tasks(string connectionString)
{
ConnectionString = connectionString;
}
public DAL_EFCore.AdventureWorks2017Context GetContext()
{
if (CurrentContext != null)
return CurrentContext;
else
{
var serviceCollection = new ServiceCollection()
.AddDbContextPool<DAL_EFCore.AdventureWorks2017Context>
(
options => options.UseSqlServer(ConnectionString)
);
var serviceProvider = serviceCollection.BuildServiceProvider();
CurrentContext = serviceProvider.GetService<DAL_EFCore.AdventureWorks2017Context>();
return CurrentContext;
}
}
public IEnumerable<SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders> SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders()
{
return GetContext().Employee
.GroupBy(e => e.Gender)
.Select(n => new SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders
{
Gender = n.Key,
Count = n.Count()
})
.ToList();
}
}
And this one:
public class DataReports : IDataReports
{
protected ITaskEnumerableProvider TaskProvider;
protected string ConnectionString;
protected string DalModeSelected;
public DataReports() {}
public DataReports(DBConfig config)
{
ConnectionString = config.ConnectionString;
DalModeSelected = config.DAL;
}
public ITaskEnumerableProvider GetTaskProvider()
{
switch (DalModeSelected)
{
case "ADO":
return new ADOTaskProvider.Tasks(ConnectionString);
case "EFCore":
return new EFCoreTaskProvider.Tasks(ConnectionString);
default:
throw new FormatException("The format of the variable which represend the selected DAL was not correct");
}
}
public IEnumerable<SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders> SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders()
{
return GetTaskProvider().SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders().ToList();
}
I'm testing with this code:
[TestMethod]
public void SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrdersTest()
{
// Setup Mock Data and context
var options = new DbContextOptionsBuilder<DAL_EFCore.AdventureWorks2017Context>()
.UseInMemoryDatabase(databaseName: "SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrdersTest")
.Options;
using (var context = new DAL_EFCore.AdventureWorks2017Context(options))
{
InsertData(options);
}
using (var context = new DAL_EFCore.AdventureWorks2017Context(options))
{
// Mock EFCoreTaskProvider.Tasks
var mockEFCoreTaskProvider = new Mock<EFCoreTaskProvider.Tasks>(ConnectionString);
mockEFCoreTaskProvider.As<IGetContext>();
mockEFCoreTaskProvider.CallBase = true;
mockEFCoreTaskProvider.Setup(x => x.GetContext()).Returns(context);
// Mock CoreReportService.DataReports
var config = new DBConfig { DAL = "EFCore", ConnectionString = ConnectionString };
var mockDataReports = new Mock<DataReports>(config).As<IDataReports>();
mockDataReports.CallBase = true;
mockDataReports.Setup(x => x.GetTaskProvider()).Returns(mockEFCoreTaskProvider.Object);
var test = mockDataReports.Object.SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders().ToList();
Assert.IsTrue(test.Count == 1);
}
}
I'm using an inmemorydatabase to test the data but the test.Count of the TestCase has the count that corresponds from the real database.
If I make GetContext() and GetTaskProvider() virtual I'm getting the correct Count from the virtual database but I don't want them to be virtual, I also prefer if they were not public, what am I doing wrong?
This is a matter of design.
what am i doing wrong?
DataReports is tightly coupled to implementation concerns and is also violating Single Responsibility Principle (SRP) and Separation of Concerns (SoC).
By having DataReports create the providers, it is tightly coupled to them, and prevents you from being able to replace them when testing.
Abstract the provider creation out into its own concern
For example
//Abstraction
public interface ITaskProviderFactory {
ITaskEnumerableProvider GetTaskProvider();
}
//Implementation
public class DefaultTaskProviderFactory : ITaskProviderFactory{
private readonly DBConfig config;
public DefaultTaskProviderFactory(DBConfig config) {
this.config = config;
}
public ITaskEnumerableProvider GetTaskProvider() {
switch (config.DAL) {
case "ADO":
return new ADOTaskProvider.Tasks(config.ConnectionString);
case "EFCore":
return new EFCoreTaskProvider.Tasks(config.ConnectionString);
default:
throw new FormatException("The format of the variable which represent the selected DAL was not correct");
}
}
}
And refactor the DataReports accordingly
public class DataReports : IDataReports {
private readonly ITaskProviderFactory factory;
public DataReports(ITaskProviderFactory factory) {
this.factory = factory;
}
private ITaskEnumerableProvider getTaskProvider() {
return factory.GetTaskProvider();
}
public IEnumerable<SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders> SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders() {
return getTaskProvider().SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders().ToList();
}
}
At run-time in production, the appropriate implementation can be explicitly injected.
For an integration test of DataReports the actual implementations can be swapped out as needed in order to verify the expected behavior.
For example
[TestMethod]
public void SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrdersTest() {
//Arrange
var options = new DbContextOptionsBuilder<DAL_EFCore.AdventureWorks2017Context>()
.UseInMemoryDatabase(databaseName: "SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrdersTest")
.Options;
using (var context = new DAL_EFCore.AdventureWorks2017Context(options)) {
InsertData(options);
}
using (var context = new DAL_EFCore.AdventureWorks2017Context(options)) {
//actual EFCoreTaskProvider.Tasks targeting in-memory database
var taskProvider = new EFCoreTaskProvider.Tasks(context);
//mock factory configured to return the desired provider
var mockFactory = Mock.Of<ITaskProviderFactory>(_ =>
_.GetTaskProvider() == taskProvider //return the actual provider for testing
);
// actual CoreReportService.DataReports (Subject under test)
var dataReports = DataReports(mockFactory);
//Act
var result = dataReports.SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders().ToList();
//Assert
Assert.IsTrue(result.Count == 1);
}
}
The original design of your classes is not very flexible so it makes it difficult to isolate parts for testing.

Entity Framework mocking requires global context

I have recently began to dig into Entity Framework unit-testing with Entity Framework 6 mocking.
I have noticed the following thing:
Entity Framework mocking forces me to create a global context in my BL class, for example:
public class RefundRepayment : IDisposable
{
protected DbContext _dbContext = new DbContext();
/* more properties and class code */
public void Dispose()
{
_dbContext.Dispose();
}
}
I can't quite figure it out, as I'd rather implement the using statement in every method in order to deal with the DbContext, my code will look like:
public class RefundRepayment
{
/* more properties and class code */
public void AccessDb()
{
using(DbContext dbContext = new DbContext())
{
/* db code here */
}
}
}
Is there any specific reason why should we initialize a global context instead of implementing the using statement?
First off, you need to be using DI (via ninject, Unity, Core, etc) to pull this off.
Let me show you a simple sample of an EF GetAll() testing my MVC controller.
[Fact]
public void GetAllOk()
{
// Arrange
// Act
var result = _controller.GetAll() as OkObjectResult;
// Assert
Assert.NotNull(result);
var recordList = result.Value as List<DTO.Account>;
Assert.NotNull(recordList);
Assert.Equal(4, recordList.Count);
}
It relies on this startup code...
public class AccountsControllerTests
{
DatabaseFixture _fixture;
AccountsControllerV1 _controller;
public AccountsControllerTests(DatabaseFixture fixture)
{
_fixture = fixture;
_controller = new AccountsControllerV1(_fixture._uow);
}
What is DatabaseFixture? Glad you asked...
public class DatabaseFixture : IDisposable
{
public ApplicationDbContext _context;
public DbContextOptions<ApplicationDbContext> _options;
public IUoW _uow;
public DatabaseFixture()
{
var x = Directory.GetCurrentDirectory();
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.Tests.json", optional : true)
.Build();
_options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(databaseName: "ProviderTests")
.Options;
_context = new ApplicationDbContext(_options);
_context.Database.EnsureCreated();
Initialize();
_uow = new UoW(_context);
}
private void Initialize()
{
_context.Accounts.Add(new Entities.Account() { AccountNumber = "Number 1", AccountID = "", AccountUniqueID = "" });
_context.Accounts.Add(new Entities.Account() { AccountNumber = "Number 2", AccountID = "", AccountUniqueID = "" });
_context.Accounts.Add(new Entities.Account() { AccountNumber = "Number 3", AccountID = "", AccountUniqueID = "" });
_context.Accounts.Add(new Entities.Account() { AccountNumber = "Number 4", AccountID = "", AccountUniqueID = "" });
_context.SaveChanges();
}
public void Dispose()
{
// Clean Up
_context.Database.EnsureDeleted();
}
}
[CollectionDefinition("Database Collection")]
public class DatabaseCollection : ICollectionFixture<DatabaseFixture>
{
}
A few definitions used in the above code. I used a Unit of Work Pattern that contains references to all my EF repositories. I kept Entity (Database) classes and DTO (Data Transfer Object) Classes separate. I used an in-memory replacement for the EF database that I initialize at the beginning of each run and/or test so that my data is always known. I inject the Database Fixture into my test class (not each test) so I am not creating/destroying constantly. Then I create my controller passing in my database UoW definition.
You're real controller requires injection of the UoW container you've created with the real database. You are merely substituting a controlled database environment for your test.
public AccountsControllerV1(IUoW uow)
{
_uow = uow;
}
And yes, I use versioning for the sharp-eyed. And yes, this is a Core 2 example. Still applicable for EF 6, just need 3rd party DI ;)
And the controller method I am testing?
[HttpGet("accounts", Name ="GetAccounts")]
public IActionResult GetAll()
{
try
{
var recordList = _uow.Accounts.GetAll();
List<DTO.Account> results = new List<DTO.Account>();
if (recordList != null)
{
results = recordList.Select(r => Map(r)).ToList();
}
log.Info($"Providers: GetAccounts: Success: {results.Count} records returned");
return Ok(results);
}
catch (Exception ex)
{
log.Error($"Providers: GetAccounts: Failed: {ex.Message}");
return BadRequest($"Providers: GetAccounts: Failed: {ex.Message}");
}
}

Using stored procedures for repository pattern with Service Class [duplicate]

This question already has answers here:
How should I setup my repositories to use the same context?
(2 answers)
Closed 4 years ago.
Background
In my web application, I create stored procedures and then create an edmx file to use the stored procedures to handle all the database interaction.
But I started to wonder if I'm doing this right because, as you'll see in the example below,
I'm instantiating two instances of Context every time the Controller gets called even when the called method doesn't require database work
I'm instantiating an instances of Context in each repository, so when a request needs to get data from Repository A and B, I have two instances of Context.
Repository A
public class RepositoryA
{
private readonly Context _context;
public RepositoryA()
{
_context = new Context();
}
public List<CLASS> GetA(int id)
{
return _context.GetByID(id);
}
}
Repository B
public class RepositoryB
{
private readonly Context _context;
public RepositoryB()
{
_context = new Context();
}
public List<CLASS> GetB(int id)
{
return _context.GetByID(id);
}
}
Controller
public class Controller
{
private readonly IRepositoryA _reposA;
private readonly IRepositoryB _reposB;
public Controller() : this(new RepositoryA(), new RepositoryB())
{}
public Controller(IRepositoryA a, IRepositoryB b)
{
_respoA = a;
_reposB = b;
}
public ActionResult METHOD()
{
//do something with both RepositoryA and RepositoryB
var dataFromA = _reposA.GetA(ID);
var dataFromB = _reposB.GetB(ID);
return View(someData);
}
}
Now the question is: I'm not sure if this is supposed to be the normal implementation, so I've been trying to figure out how I can set this up in more efficient and testable way, and I tried something like this.
I believe that this design solves some of my concerns:
Service gets called every time Controller gets called, but Context doesn't get instantiated every time (Context gets instantiated per request).
When a service requires both Repository A and B, it uses the same Context
However, with how Service is set up, I can't unit test Service with test data because I can't use my mock repositories.
public class Controller
{
private Service _service;
public Controller()
{
_service = new Service();
}
public ActionResult METHOD()
{
_service.DoSomethingWithRepoAandB();
return View();
}
}
public class Service
{
public void DoSomethingWithRepoAandB()
{
using (var _context = new Context())
{
RepositoryA a = new RepositoryA(_context);
RepositoryB b = new RepositoryB(_context);
something = a.DoSomethingWithA();
otherThing = b.DoOtherThingWithB();
}
}
}
So, I'm thinking I should set up Service like this.
With this design, Context will be instantiated every time Controller gets called (unless I instantiate Service in a Controller method), but I can unit test by passing mock repositories.
public class Service
{
private readonly Context _context;
private IRepositoryA _a;
private IRepositoryB _b;
public Service()
{
_context = new Context();
_a = new RepositoryA(_context);
_b = new RepositoryB(_context);
}
// Pass Mock Repositories in unit tests
public Service(RepositoryA a, RepositoryB b)
{
_a = a;
_b = b;
}
public void DoSomethingWithRepoAandB()
{
something = _a.DoSomethingWithA();
otherThing =_b.DoOtherThingWithB();
}
}
Am I doing this completely wrong or on the ok track? I'd appreciate any advice.
Your services should NOT have any information about your context, that information should only be accessible by the repository, following SOLID principles.
The access of your layers should be as follows:
Controller -> Service -> Repository -> Context
On your IRepository interfaces, you will have the methods you're going to call, and you either instantiate them manually, but to implement it correctly, you'll have to setup your dependency injection.
Also, your Repositories constructors dont receive any context as a parameter, so you cant do something like this:
RepositoryA a = new RepositoryA(_context);
On another point, your controller shouldn't access repositories as well, as it breaks your architecture
public ActionResult METHOD()
{
//do something with both RepositoryA and RepositoryB
var dataFromA = _reposA.GetA(ID);
var dataFromB = _reposB.GetB(ID);
return View(someData);
}
Here's the correct code, for further understanding:
public class RepositoryA : IRepositoryA
{
private readonly Context _context;
public RepositoryA(Context context)
{
_context = context;
}
public List<CLASS> GetA(int id)
{
return _context.GetByID(id);
}
}
public interface IRepositoryA
{
List<CLASS> GetA(int id);
}
public class RepositoryB : IRepositoryB
{
private readonly Context _context;
public RepositoryB(Context context)
{
_context = context;
}
public List<CLASS> GetB(int id)
{
return _context.GetByID(id);
}
}
public interface IRepositoryB
{
List<CLASS> GetB(int id);
}
public class Controller
{
private IService _service;
public Controller(IService service)
{
_service = service;
}
public ActionResult METHOD(int id)
{
//do something with both RepositoryA and RepositoryB THROUGH the service. The service needs to hold the business rules, and repositories should only care about querying data and handling contexts.
var data = _service.DoSomethingWithRepoAandB(id)
return View(data);
}
}
public class Service : IService
{
private IRepositoryA _a;
private IRepositoryB _b;
// Pass Mock Repositories in unit tests -> PS: You can't have 2 constructors if you're using dependency injection.
public Service(RepositoryA a, RepositoryB b)
{
_a = a;
_b = b;
}
public void DoSomethingWithRepoAandB(int id)
{
var something = _a.GetA(id);
var otherThing = _b.GetB(id);
}
}
public interface IService
{
void DoSomethingWithRepoAandB(int id);
}
public class Bootstrapper
{
//this class should be in a separated assembly, responsible for handling the dependency injection. Using Simple Injection syntax just as an example
public static void RegisterServices(Container container) //IoC Container
{
container.Register<IService, Service>(Lifestyle.Scoped);
container.Register<IRepositoryA, RepositoryA>(Lifestyle.Scoped);
container.Register<IRepositoryB, RepositoryB>(Lifestyle.Scoped);
container.Register<Context>(() => {
var options = // Configure your ContextOptions here
return new Context(options);
});
}
}
public class Startup
{
//This is your startup configuration if you're using WebApi. If you're on MVC, you can do this on your Global.asax
public void Configuration()
{
var container = new Container();
container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
BootStrapper.RegisterServices(container);
}
}

Categories