I was refactoring a quite large WPF project and I found some pieces of code where it can leads to error.
Ive got some services in the form
public sealed class CurrenciesService : ContainerServiceBase<Currency>, ICurrenciesService
{
public override Currency GetItem(int id)
{
return GetItemInternal(x => x.Id == id);
}
public override Task Init()
{
return PerformInitInternal(InitCacheMessages.STR_GET_COUNTERPARTS,
InitCacheMessages.STR_GET_COUNTERPARTS_FAILED,()=> CommonRepository.GetCurrenciesAsync());
}
}
where the ContainerServiceBase is something as
public class ContainerServiceBase<T> : IContainerBase<T>
where T : class
{
#region Variables
protected readonly IExceptionService ExceptionService = ServiceLocator.Default.ResolveType<IExceptionService>();
protected readonly ICommonRepository CommonRepository = ServiceLocator.Default.ResolveType<ICommonRepository>();
public delegate void ItemsChangedHandler(object sender, EventArgs e);
public event ItemsChangedHandler OnItemsChanged;
#endregion
public virtual T GetItem(int id)
{
throw new NotImplementedException();
}
public virtual Task Init()
{
return TaskEx.WhenAll();
}
public IList<T> Items { get; protected set; }
protected async Task PerformInitInternal(string okMessage, string koMessage, Func<Task<IList<T>>> function)
{
try
{
var result = await function();
if (result != null)
{
Items = result;
LogTo.Information(okMessage, result.Count);
OnItemsChanged?.Invoke(this, new EventArgs());
}
else
{
LogTo.Warning(koMessage);
}
}
catch (Exception ex)
{
ExceptionService.HandleException(ex);
}
}
protected T GetItemInternal(Func<T, bool> condition)
{
if (Items == null) throw new NullReferenceException("Items has not been initialized");
var result = Items.FirstOrDefault(condition);
return result;
}
Currently during the Application's start I perform for each service this
var taskPortolio = portfoliosService.Init();
var taskInstitutes = institutesService.Init();
var taskStatus = statusService.Init();
var taskCounterparts = counterpartsService.Init();
var taskCrosses = crossesService.Init();
var taskCurrencies = currenciesService.Init();
var taskSigns = signsService.Init();
var taskCrossSplit = crossSplitService.Init();
var taskDateMM = dateMmService.Init();
var taskSignsFx = signsFxService.Init();
var taskPortfolioTree = portfolioItemService.Init();
var taskTags = tagService.Init();
var taskInstitutesCustomerDesk = institutesCustomerDeskService.Init();
var taskProvenances = provenanceService.Init();
var taskRepoClasses = repoClassService.Init();
var taskDatesMaturity = dateMaturityService.Init();
Those methods are initialized even if they're never used.
I was writing a unit test in the form
[Test]
public async Task CurrenciesLoaded()
{
var viewmodel = viewModelFactory.CreateViewModel<HistoricalDetailPRTViewModel>(null, null);
var res = viewmodel.InitializeViewModelAsync();
await res;
Assert.NotNull(viewmodel.Currencies);
Assert.IsTrue(viewmodel.Currencies.Count>0);
}
and correctly I get null at viewmodel.Currencies since no CurrenciesService.Init has been called.
Can I put a check in the Item.getter to check if it's called the Init and if not call it? is this evil? consider that those init are now called async and I wish not to block the UI while it's loading
P.S. I'm using Catel as MVVM Framework
-
\$\begingroup\$ I'm not sure what your question is ? \$\endgroup\$Tolani– Tolani2016年08月24日 07:50:12 +00:00Commented Aug 24, 2016 at 7:50
-
\$\begingroup\$ I've edit the main question... is this way of initializing safe? \$\endgroup\$advapi– advapi2016年08月24日 07:58:49 +00:00Commented Aug 24, 2016 at 7:58
-
1\$\begingroup\$ That looks better \$\endgroup\$Tolani– Tolani2016年08月24日 08:03:25 +00:00Commented Aug 24, 2016 at 8:03
-
1\$\begingroup\$ Do the services have a long lasting initialization or why are you doing it like this? I wouldn't complicate things just to make it lazy if the initialization doesn't cost a penny. \$\endgroup\$t3chb0t– t3chb0t2016年08月24日 18:22:57 +00:00Commented Aug 24, 2016 at 18:22
1 Answer 1
From what I can see you are calling an init function on every service so you can get data and cache it at startup.
A better way to do this would be to use the dependency injection layer for example using unity
builder.RegisterType<CurrenciesService>()
.As<ICurrenciesService>()
.AutoActivate()
.OnActivating(x => x.Instance.Init())
.SingleInstance();
So this code activates your Currency Service, auto activates it when the container is building, when it gets activated calls init and puts it in single instance. Single instance is needed otherwise the init will be called on every request.