简体   繁体   中英

Resolving Autofac dependencies from inside of classes, without parameterized constructors

I am facing a little problem related to dependency injection. I have a program, using respectfully state of the art rules of dependency injection, used from example of an ASP.NET MVC project, and some console programs.

But it also contains some kind of "service locator" anti pattern.

I'll attempt to illustrate it with a very simple console project :

using System;
using Autofac;
using System.Collections.Generic;
using System.Linq;

namespace AutofacIssue
{
    class Program
    {
        static void Main(string[] args)
        {
            var builder = new ContainerBuilder();
            builder.RegisterModule<MyModule>();
            var container = builder.Build();

            using (var scope = container.BeginLifetimeScope())
            {
                var service = scope.Resolve<UserService>();

                var users = service.CreateSomeUsers();

                var info = users.First().GetSomeInfo;
                Console.WriteLine(info.Something);
            }
        }
    }

    internal class MyModule : Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            base.Load(builder);

            builder.RegisterType<UserService>();
            builder.RegisterType<UserRelatedInfoService>();
        }
    }

    internal class UserRelatedInfoService
    {
        public UserInfo GetForUser(int id)
        {
            return new UserInfo("Various infos for " + id);
        }
    }

    internal class UserService
    {
        public IEnumerable<User> CreateSomeUsers()
        {
            return Enumerable.Range(1, 10).Select(r => new User(r)); // Remark "new User()" is called from many various spaces !
        }
    }

    internal class UserInfo
    {
        // Some things
        public string Something;

        public UserInfo(string someThings)
        {
            this.Something = someThings;
        }
    }

    /// <summary>
    /// "Service locator" antipattern
    /// </summary>
    class DependencyLocator
    {
        public static IContainer Container { get; }

        static DependencyLocator()
        {
            var builder = new ContainerBuilder();
            builder.RegisterModule<MyModule>();
            Container = builder.Build();
        }
    }

    internal class User
    {
        public User(int id)
        {
            this.Id = id;
        }

        public int Id { get; private set; }

        /// <summary>
        /// Here's my problematic property using the DependencyLocator
        /// </summary>
        public UserInfo GetSomeInfo
        {
            get
            {
                UserRelatedInfoService userRelatedInfoService = DependencyLocator.Container.Resolve<UserRelatedInfoService>();
                return userRelatedInfoService.GetForUser(Id);
            }
        }
    }
}

The anti pattern allows to write very small code working perfectly well, but violating some of the principles of DI (due to "service locator" + duplicated Container, having each their own lifetime).

This implementation also have the advantage to instantiate UserRelatedInfoService only when it's actually needed, if the related property of User is actually called (please keep in mind the real world example is much more complicated and some of the operations related to this may have a cost)

In the real world example, I have this situation in many assemblies, each of them needing to be able to resolve dependencies by the same way.

My question is: without to modify the User constructor, and the constructors of all objects instantiating some User , is there a clean way to avoid this?

By some kind of "dynamic resolving" of dependencies for example?

Please note that User is not in the same assembly as my Program class, so I can't access the original Container as a public property.

One solution I thought was to keep the class DependencyLocator but to remove its content, and just assign its Container property with the one created in main.

edit : FYI, so far I just followed my own suggestion and modified DependencyLocator to avoid it to rebuild its own container, and just set on it the final container built at entry point of application. It was an easy change to do and it avoids most of the problems pointed in original question. At least, the code will always use the same container.

Thanks for reading!

For edge cases like this where you need runtime resolution by type, you can register IServiceProvider or a Func (Or a Func with the object[] being input parameters)

 builder.Register(ctx => ctx as IServiceProvider ??           
   ctx.Resolve<ILifetimeScope>() as IServiceProvider)
   .InstancePerLifetimeScope().AsSelf();

or

 builder.Register(c =>
        {
            var scope = c.Resolve<ILifetimeScope>();
            return (Func<Type, object>)(t => scope.Resolve(t));
        }).As<Func<Type, object>>();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM