Как реализовать универсальный c DI? - PullRequest
0 голосов
/ 29 апреля 2020

У меня есть несколько служб и несколько репозиториев. Все мои сервисы содержат репозиторий и картограф. Итак, я создал BaseService следующим образом:

public abstract class BaseService<TEntity, TRepository> : BaseService
        where TEntity : class, IEntity
        where TRepository : IRepository<TEntity>
    {
        #region Fields

        /// <summary>
        /// Data manager
        /// </summary>
        protected TRepository Repository { get; } =
            ServiceCollectionHelper.GetElementFromDependencyInjection<TRepository>();

        /// <summary>
        /// Mapper
        /// </summary>
        protected IMapper Mapper { get; } = ServiceCollectionHelper.GetElementFromDependencyInjection<IMapper>();

        #endregion
    }

    public abstract class BaseService
    {
    }

The ServiceCollectionHelper:

 public static class ServiceCollectionHelper
    {
        #region Fields

        /// <summary>
        /// Service collection
        /// </summary>
        private static IServiceProvider _serviceProvider = null;

        /// <summary>
        /// Service provider getter
        /// </summary>
        private static IServiceProvider ServiceProvider
        {
            get
            {
                if (_serviceProvider != null) return _serviceProvider;

                //Get the service collection
                _serviceProvider = new ServiceCollection().BuildServiceProvider();

                return _serviceProvider;
            }
        }

        #endregion

        /// <summary>
        /// Get an element from the dependency injection
        /// </summary>
        /// <typeparam name="TElement">Element type</typeparam>
        /// <returns>Element</returns>
        public static TElement GetElementFromDependencyInjection<TElement>()
        {
            return ServiceProvider.GetService<TElement>();
        }
    }

Пример службы, которая наследуется от BaseService:

public class DepartmentService : BaseService<Department, IDepartmentRepository>
    {
        #region Fields

        #endregion

        #region Constructor

        #endregion

        #region Methods

        /// <summary>
        /// Get all departments
        /// </summary>
        /// <param name="withAdjacentDepartments">Include adjacent departments</param>
        /// <param name="withRegion">Include region</param>
        /// <returns>Departments</returns>
        public async Task<IEnumerable<DepartmentDTO>> GetAsync(bool withAdjacentDepartments = false,
            bool withRegion = false)
        {
            //Get departments
            var departments = await this.Repository.GetAsync(withAdjacentDepartments, withRegion);

            //Map to the DTO
            return departments.Select(department => this.Mapper.Map<DepartmentDTO>(department)).ToList();
        }

        #endregion
    }

Проблема в том, что Mapper и Repository не найдены, поэтому они нулевые.

Как я могу это реализовать?

Для воспроизведения вы можете найти проект здесь и соответствующие инструменты здесь

1 Ответ

1 голос
/ 29 апреля 2020

Первое, что я узнаю о том, что вы показываете выше, это то, что вы используете шаблон Service Locator, а не Inpendency Injection, и учитывая, что вы используете ASP NET Core DI-контейнер ( или, похоже, после короткого взгляда на вашу кодовую базу) это делает ваш код намного сложнее, чем нужно.

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

Спецификация c Проблема, с которой вы столкнулись, заключается в том, что вы пытаетесь установить какой-то глобальный, статический c экземпляр IServiceProvider, но вы никогда не добавляете в него какие-либо службы. Поскольку у вас нет установщика, в первый раз, когда один из ваших локаторов пытается получить что-то с помощью вспомогательного класса stati c, он сообщает о пустой коллекции и возвращает ее, и этот экземпляр полностью отделен от того, который вы регистрировали во время Запуск.

Тем не менее, некоторая реструктуризация была бы лучшим путем, чем "исправление" этого глобального вспомогательного экземпляра stati c. Рассмотрим следующий код:

    public class Foo
    {
        public int Bar;
        public int Qux;
    }

    public interface IRepository<T> where T : class, new()
    {
        T GetEntity();
    }

    public class FooRepository : IRepository<Foo>
    {
        public Foo GetEntity()
        {
            return new Foo();
        }
    }

    public abstract class BaseService<TEntity>
         where TEntity : class, new()
    {
         protected readonly IRepository<TEntity> _entityRepository;
         protected readonly IMapper _mapper;

         protected BaseService(IRepository<TEntity> entityRepository, IMapper mapper)
         {
             _entityRepository = entityRepository;
             _mapper = mapper;
         } 
    }

    public interface IFooService
    {
        Foo ServicyStuff();
    }

    public class FooService : BaseService<Foo>, IFooService
    {
        public FooService(IRepository<Foo> entityRepository, IMapper mapper) : base(entityRepository, mapper)
        {
        }

        public Foo ServicyStuff()
        {
            var foo = _entityRepository.GetEntity(); // Our abstract class handled this for us!
            return foo;
        }
    }

И следующие регистрации служб при запуске:

services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
services.AddTransient<IRepository<Foo>, FooRepository>();
services.AddTransient<IFooService, FooService>();

Теперь мы можем просто передать экземпляр IFooService в контроллеры или другие службы с поддержкой DI и все остальное все подключено и готово к go. Когда IFooService разрешает FooService, FooService разрешает хранилище и использует абстрактный базовый класс, чтобы гарантировать, что хранилище внедрено и доступно для унаследованных классов во время создания экземпляра. Контейнер DI отвечает за всю тяжелую работу, которую вы пытаетесь выполнить с помощью абстрактного класса и расширений локатора услуг.

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;
    private readonly IFooService _fooService;

    public HomeController(ILogger<HomeController> logger, IFooService fooService)
    {
        _logger = logger;
        _fooService = fooService;
    }

    public IActionResult Index()
    {
        var myFoo = _fooService.ServicyStuff();
        return View();
    }
...