Default resolving behavior of multiple implementations (v3.5.2)

104 views
Skip to first unread message

Xiao-Lu Liu (Inspur Worldwide Services Ltd)

unread,
Oct 17, 2014, 3:35:27 PM10/17/14
to aut...@googlegroups.com

Hi Guys,

 

I recently upgraded the my project to 3.5.2 but some old behaviors seem changed.

 

I am using the following code to register my services,

 

            builder.RegisterType<MemoryCacheManager>().As<ICacheManager>().Named<ICacheManager>("nop_cache_static").SingleInstance();

            builder.RegisterType<PerRequestCacheManager>().As<ICacheManager>().Named<ICacheManager>("nop_cache_per_request").InstancePerLifetimeScope();

builder.RegisterType<GenericAttributeService>().As<IGenericAttributeService>().InstancePerLifetimeScope();

 

Later on, the ICacheManager is resolved in the constructor.

 

        public GenericAttributeService(ICacheManager cacheManager,

            IRepository<GenericAttribute> genericAttributeRepository,

            IEventPublisher eventPublisher)

        {

            this._cacheManager = cacheManager;

            this._genericAttributeRepository = genericAttributeRepository;

            this._eventPublisher = eventPublisher;

        }

 

 

Before I upgraded the Autofac, the implementation of PerRequestCacheManager is resolved, but now MemoryCacheManager is resolved. So I have to change the code to following and then it works as expectation.

 

            builder.RegisterType<MemoryCacheManager>().Named<ICacheManager>("nop_cache_static").SingleInstance();

            builder.RegisterType<PerRequestCacheManager>().As<ICacheManager>().InstancePerLifetimeScope();

 

1.       Anyone knows if the behavior that ‘last registered is resolved first’ is changed?

2.       How can I debug the code of Autofac to understand the changes? Where to download the symbol files (*.pdb) and corresponding source code.

 

 

Thanks,

Sean

Travis Illig

unread,
Oct 17, 2014, 3:54:59 PM10/17/14
to aut...@googlegroups.com, v-xl...@microsoft.com
The last-in-wins behavior has not changed. It might be good to show how you're doing the resolve call. Something you'll want to be careful of is that "SingleInstance" and "InstancePerLifetimeScope" become effectively the same if you're resolving directly from the root container.

If you want to debug into the source, hook Visual Studio up to SymbolSource.org - we publish symbol/source packages with every NuGet release.

-T

Xiaolu Liu

unread,
Oct 17, 2014, 4:28:33 PM10/17/14
to Travis Illig, aut...@googlegroups.com

Thanks for your advice, Travis.

 

I will provide more detailed information in this thread later. Will try to debug first then see if I can figure out the reason.

Xiaolu Liu

unread,
Oct 18, 2014, 4:03:05 AM10/18/14
to Travis Illig, aut...@googlegroups.com

Hi Travis,

 

I still didn’t find the cause, since symbols of v3.5.2 are not available on symbolsource yet.

 

https://www.symbolsource.org/Public/Metadata/NuGet/Project/Autofac

 

for resolving services, we use the following strategy in the MVC projects.

 

        public ILifetimeScope Scope()

        {

            try

            {

                if (HttpContext.Current != null)

                    return AutofacDependencyResolver.Current.RequestLifetimeScope;

 

                //when such lifetime scope is returned, you should be sure that it'll be disposed once used (e.g. in schedule tasks)

                return Container.BeginLifetimeScope(MatchingScopeLifetimeTags.RequestLifetimeScopeTag);

            }

            catch (Exception)

            {

                //we can get an exception here if RequestLifetimeScope is already disposed

                //for example, requested in or after "Application_EndRequest" handler

                //but note that usually it should never happen

 

                //when such lifetime scope is returned, you should be sure that it'll be disposed once used (e.g. in schedule tasks)

                return Container.BeginLifetimeScope(MatchingScopeLifetimeTags.RequestLifetimeScopeTag);

            }

        }

 

Thanks,

Xiao-Lu

 

From: Xiaolu Liu [mailto:littl...@hotmail.com]
Sent: Friday, October 17, 2014 1:28 PM
To: 'Travis Illig'; aut...@googlegroups.com
Subject: RE: Default resolving behavior of multiple implementations (v3.5.2)

 

Thanks for your advice, Travis.

 

I will provide more detailed information in this thread later. Will try to debug first then see if I can figure out the reason.

 

 

 

From: Travis Illig [mailto:travis...@gmail.com]
Sent: Friday, October 17, 2014 12:55 PM
To: aut...@googlegroups.com
Cc: Xiao-Lu Liu (Inspur Worldwide Services Ltd)
Subject: Re: Default resolving behavior of multiple implementations (v3.5.2)

 

The last-in-wins behavior has not changed. It might be good to show how you're doing the resolve call. Something you'll want to be careful of is that "SingleInstance" and "InstancePerLifetimeScope" become effectively the same if you're resolving directly from the root container.

Xiaolu Liu

unread,
Oct 18, 2014, 8:16:49 PM10/18/14
to Travis Illig, aut...@googlegroups.com

I can now resolve all symbols and code for 3.5.2. I guess something wrong or changed within the following method in ExternalRegistrySource.cs. The sequence of _implements member in ServiceRegistrationInfo is unexpectedly modified. It only happens when I registered an implementation was both KeyedService and TypedService.

 

        /// <summary>

        /// Retrieve registrations for an unregistered service, to be used

        /// by the container.

        /// </summary>

        /// <param name="service">The service that was requested.</param>

        /// <param name="registrationAccessor">A function that will return existing registrations for a service.</param>

        /// <returns>Registrations providing the service.</returns>

        public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor)

        {

            var seenRegistrations = new HashSet<IComponentRegistration>();

            var seenServices = new HashSet<Service>();

            var lastRunServices = new List<Service> { service };

 

            while (lastRunServices.Any())

            {

                var nextService = lastRunServices.First();

                lastRunServices.Remove(nextService);

                seenServices.Add(nextService);

                foreach (var registration in _registry.RegistrationsFor(nextService).Where(r => !r.IsAdapting()))

                {

                    if (seenRegistrations.Contains(registration))

                        continue;

 

                    seenRegistrations.Add(registration);

                    lastRunServices.AddRange(registration.Services.Where(s => !seenServices.Contains(s)));

 

                    var r = registration;

                    yield return RegistrationBuilder.ForDelegate(r.Activator.LimitType, (c, p) => c.ResolveComponent(r, p))

                        .Targeting(r)

                        .As(r.Services.ToArray())

                        .ExternallyOwned()

                        .CreateRegistration();

The last-in-wins behavior has not changed. It might be good to show how you're doing the resolve call. Something you'll want to be careful of is that "SingleInstance" and "InstancePerLifetimeScope" become effectively the same if you're resolving directly from the root container.

Alex Meyer-Gleaves

unread,
Oct 18, 2014, 11:25:10 PM10/18/14
to aut...@googlegroups.com, travis...@gmail.com, littl...@hotmail.com
Hi Xiao-Lu,

What version did you upgrade from? We need to know this to determine what could have changed.

Does the test below represent your scenario? This is currently passing with the latest source version.

interface ICache
{
}

class MemoryCache : ICache
{
}

class PerRequestCache : ICache
{
}

class UsesCache
{
internal ICache Cache { get; private set; }

public UsesCache(ICache cache)
{
Cache = cache;
}
}

[Test]
public void Bug()
{
var cb = new ContainerBuilder();
cb.RegisterType<MemoryCache>().As<ICache>().Named<ICache>("static").SingleInstance();
cb.RegisterType<PerRequestCache>().As<ICache>().Named<ICache>("perRequest").InstancePerLifetimeScope();
cb.RegisterType<UsesCache>().As<UsesCache>().InstancePerLifetimeScope();
var container = cb.Build();
using (container.BeginLifetimeScope(MatchingScopeLifetimeTags.RequestLifetimeScopeTag))
{
var o = container.Resolve<UsesCache>();
Assert.That(o.Cache, Is.InstanceOf<PerRequestCache>());
}
}

If this the test isn't correct it would be helpful if you could make the required adjustments. Without access to your source code working with a unit test is the best bet.

When executing the unit test you should some debug information in the output.

[Autofac] Overriding default for: 'Autofac.Tests.Core.ContainerTests+ICache' with: 'Activator = PerRequestCache (ReflectionActivator), Services = [Autofac.Tests.Core.ContainerTests+ICache, perRequest (Autofac.Tests.Core.ContainerTests+ICache)], Lifetime = Autofac.Core.Lifetime.CurrentScopeLifetime, Sharing = Shared, Ownership = OwnedByLifetimeScope' (was 'Activator = MemoryCache (ReflectionActivator), Services = [Autofac.Tests.Core.ContainerTests+ICache, static (Autofac.Tests.Core.ContainerTests+ICache)], Lifetime = Autofac.Core.Lifetime.RootScopeLifetime, Sharing = Shared, Ownership = OwnedByLifetimeScope')

You can see the per request registration is overriding the first.

Cheers,

Alex.

Xiaolu Liu

unread,
Oct 19, 2014, 12:57:36 AM10/19/14
to Alex Meyer-Gleaves, aut...@googlegroups.com, travis...@gmail.com
Hi Alex,

We upgraded the project from autofac 3.0.x and  made many changes related to the scope (we were using InstancePerRequest). Please allow more time for me to investigate when I have time. Will reply in this thread with updates.

Thanks for your followup. 

Xiao-Lu


Subject: Re: Default resolving behavior of multiple implementations (v3.5.2)

Reply all
Reply to author
Forward
0 new messages