Разрешение зависимостей Autofac изнутри классов без параметризованных конструкторов - PullRequest
0 голосов
/ 06 сентября 2018

У меня небольшая проблема, связанная с внедрением зависимостей. У меня есть программа, использующая уважительно современные правила внедрения зависимостей, использованные на примере проекта ASP.NET MVC и некоторых консольных программ.

Но он также содержит некий «шаблон поиска служб».

Я попытаюсь проиллюстрировать это очень простым консольным проектом:

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);
            }
        }
    }
}

Анти-паттерн позволяет писать очень маленький код, работающий безупречно, но нарушающий некоторые принципы DI (из-за «сервисного локатора» + дублированный контейнер, каждый из которых имеет свое время жизни).

Эта реализация также имеет преимущество в том, что создает экземпляр UserRelatedInfoService только тогда, когда это действительно необходимо, если на самом деле вызывается связанное свойство User (имейте в виду, что пример из реального мира намного сложнее, и некоторые операции, связанные с этим может иметь стоимость)

В примере с реальным миром у меня такая ситуация во многих сборках, каждая из которых должна иметь возможность разрешать зависимости одним и тем же способом.

Мой вопрос таков: без изменения конструктора User и конструкторов всех объектов, создающих некоторые User, есть ли чистый способ избежать этого?

Каким-то «динамическим разрешением» зависимостей, например?

Обратите внимание, что User не находится в той же сборке, что и мой класс Program, поэтому я не могу получить доступ к исходному Container как к общедоступному свойству.

Я решил, что одним из решений было сохранить класс DependencyLocator, но удалить его содержимое и просто присвоить его свойство Container тому, которое создано в main.

edit: К вашему сведению, пока я просто следовал своему собственному предложению и изменил DependencyLocator, чтобы он не перестраивал свой собственный контейнер, и просто установил на нем окончательный контейнер, созданный в точке входа приложения. Это было легко сделать, и это позволило избежать большинства проблем, указанных в первоначальном вопросе. По крайней мере, код всегда будет использовать один и тот же контейнер.

Спасибо за чтение!

1 Ответ

0 голосов
/ 06 сентября 2018

Для таких крайних случаев, когда требуется разрешение во время выполнения по типу, вы можете зарегистрировать IServiceProvider или Func (или Func с объектом [], являющимся входными параметрами)

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

или

 builder.Register(c =>
        {
            var scope = c.Resolve<ILifetimeScope>();
            return (Func<Type, object>)(t => scope.Resolve(t));
        }).As<Func<Type, object>>();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...