Tuesday, 2 December 2014

Use of "await" with WCF-lifetime dependency injection (SimpleInjector)

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