Found an issue where SimpleInjector WCF-lifetime dependency-injection is broken by
asynchronous methods. The code in question is as follows. (The deferred construction is
performed because the legacy repository is not required by any of the other
methods on the class)
public async Task<UnitQueryResult[]>
GetUnitsByVinAsync(string vin)
{
return await ExecuteFaultHandledOperation(async () => {
var result = (await _factory
.Build<ISalesForceRepository>()
.GetUnitWithReferencesByVin(vin)) ?? new UnitQueryResult[0];
if (_settings.IsLegacyUnitLookupEnabledForSalesForce)
{
result = result.Concat(
(await
_factory.Build<ISvrLegacyRepository>()
.GetUnitWithReferencesByVin(vin)) ??
new UnitQueryResult[0]).ToArray();
}
return result;
});
}
The exception raised by the yellow code when called from a WCF method is:
The
registered delegate for type ISvrLegacyRepository threw an exception.
The
ISvrLegacyRepository is registered as 'WCF Operation' lifestyle, but
the
instance is requested outside the context of a WCF Operation.
The reason is clear but worrying … by the time the method on
the legacy repository is called, the original thread (with its context)
has been lost due to the prior await a method on the other repository
… so a workaround would be to instantiate the legacy repository before the first await – for example:
public async Task<UnitQueryResult[]>
GetUnitsByVinAsync(string vin)
{
return await
ExecuteFaultHandledOperation(async () => {
var legacyRepository
= _settings.IsLegacyUnitLookupEnabledForSalesForce?
_factory.Build<ISvrLegacyRepository>():
null;
var result = (await _factory
.Build<ISalesForceRepository>()
.GetUnitWithReferencesByVin(vin)) ?? new UnitQueryResult[0];
if (legacyRepository
!= null)
{
result = result.Concat(
(await legacyRepository.GetUnitWithReferencesByVin(vin)) ??
new UnitQueryResult[0]).ToArray();
}
return result;
});
}
However, that makes for fragile code, because it depends on
the whole call-chain before the use of the factory: if any method in the call-stack prior to a use of the factory to instantiate
a WCF-lifetime object has already performed an await, the WCF context is unavailable (we’re now running on a different thread, even
though the WCF context is still alive) and instantiation via the factory will fail. And that obviously won’t be picked up at
unit-test L
So, another clear reason to strongly prefer all
dependencies to be resolved by the constructor wherever possible.
No comments:
Post a Comment