I have .Net Core Web API application. There is some Get method in controller and injected IRepositoryProviderFactory. Nothing special.
[ApiController]
public class DataController : ControllerBase
{
private readonly ILogger<DataController> _logger;
private readonly IRepositoryProviderFactory _repositoryProviderFactory;
#region ctor
/// <summary>
/// ctor
/// </summary>
public DataController(ILogger<DataController> logger, IRepositoryProviderFactory repositoryProviderFactory)
{
_logger = logger;
_repositoryProviderFactory = repositoryProviderFactory;
}
[HttpPost]
public async Task<IActionResult> GetData([FromBody] SearchModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
try
{
var data = await _repositoryProviderFactory.GetDataAsync(model);
return Ok(data);
}
catch (Exception ex)
{
return StatusCode((int)HttpStatusCode.InternalServerError);
}
}
}
There are repositories based on the same Interface to get data from different data sources.
public Repository1: IDataRepository {}
public Repository2: IDataRepository {}
public Repository3: IDataRepository {}
I'm using DI so all parts are registered in services as Scoped or Transients. Some repositories are using EntityFramework.
services.AddScoped<IDataRepository, Repository1>();
services.AddScoped<IDataRepository, Repository2>();
services.AddScoped<IDataRepository, Repository3>();
Ok, now I need to implement RepositoryProviderFactory to return repository. But there is one required functionality: it must return for every call different repository.
I have injected IEnumerable and I need to return Repository1, Repository2, Repository3 and again Repository1, … etc. So all repositores are used the same time.
/// <summary>
/// ctor
/// </summary>
public RepositoryProviderFactory(
ILogger<RepositoryProviderFactory> logger,
IEnumerable<IDataRepository> dataRepositories)
{
_logger = logger;
_dataRepositories = dataRepositories;
}
public IDataRepository GetRepository()
{
// TODO: Logic to cycle repositories
var instance = dataRepositories.Where();
return instance;
}
How to do this?
Factory can't be registered as Singleton, because repositories have another dependencies that have Scoped Lifetime (DbContext etc.)
How can I create some thread safe singleton object to be able persists last used repository and serve another one, for another call?
Well, I did it this way.
I made class GlobalAppData and registered is as Singleton.
services.AddSingleton<GlobalAppData>();
Then I made simple implementation (not finished yet).
public class GlobalAppData
{
private IDataRepository[] _availableRepositories;
private IDataRepository_lastUsedRepository;
/// <summary>
/// ctor
/// </summary>
public GlobalAppData()
{
_availableRepositories = new IDataRepository[0];
}
public void TryRegisterRepositories(IEnumerable<IDataRepository> repositories)
{
if (_availableRepositories.Length > 0)
{
return;
}
_availableRepositories = repositories.ToArray();
_lastUsedRepository = null;
}
public IDataRepository GetNextRepository()
{
if (_lastUsedRepository == null)
{
_lastUsedRepository = _availableRepositories[0];
return _lastUsedRepository;
}
var lastUsedIndex = _availableRepositories.IndexOf(_lastUsedRepository);
if (lastUsedIndex < _availableRepositories.Length - 1)
{
lastUsedIndex++;
}
else
{
lastUsedIndex = 0;
}
_lastUsedRepository = _availableRepositories[lastUsedIndex];
return _lastUsedRepository;
}
}
Then because of DI there will be stored original instances, not injected ones, I made just simple compare in factory.
var instanceData = _globalAppData.GetNextRepository();
var instance = _repositories.SingleOrDefault(r => r.GetType() == instanceData.GetType());
Not perfect, but it works as a start.