.NET Core - Статические классы, запускаются повсюду, но все еще нуждаются в DI и Entity Framework - PullRequest
0 голосов
/ 26 июня 2019

Позвольте мне начать с некоторых примеров кода, которые являются очень распространенными, очень полезными и используются во многих (веб) приложениях:

int? contactId = InfrastructureHelper.User.GetContactId()

int[] departmentIds = InfrastructureHelper.GetAuhthorizedDepartmentIds()

bool allowed = InfrastructureHelper.User.IsAllowedInPortal()

InfrastructureHelper.User.GetUserLanguage();

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

Хотя все это выглядит очень круто иполезно, это создает проблему при использовании с Entity Framework и в сильно асинхронном веб-приложении.До того, как в .NET Framework и все было синхронно (о боже, в те времена), это не было проблемой.Но теперь я начинаю видеть проблемы, и мне нужно решение.

Требуется решение:

  • Статично, если возможно, чтобы не вводить класс везде
  • Этокэширует данные для выполнения (дополнительных) проверок авторизации (отделы, авторизация на основе контроллера поверх авторизации по умолчанию на основе ролей).
  • Потоково-безопасная, потому что это, конечно, проблема сейчас, здесь я получаю исключение ниже:

System.InvalidOperationException: 'Вторая операция началась в этом контексте перед завершением предыдущей операции.Обычно это вызвано тем, что разные потоки используют один и тот же экземпляр DbContext, однако члены экземпляра не гарантированно безопасны для потоков.Это также может быть вызвано тем, что вложенный запрос оценивается на клиенте, в этом случае переписать запрос, избегая вложенных вызовов. '

Так что я понимаю , как использовать DI и другиевозможности , я видел другие вопросы , которые задают аналогичный вопрос, но решение основано на том факте, что мне нужен внедренный экземпляр Entity Framework.

InfrastructureHelper содержиткуча статических членов и 2 важных интерфейса, ICurrentSession и ICurrentUser.Эти интерфейсы реализованы по-разному для каждого веб-приложения в решении.Реализации не являются статичными, но содержат статические элементы для кэширования еще некоторых данных.ICurrentSession здесь не проблема, реализация просто вводит IHttpContextAccessor.Но CurrentPortalUser, который реализует ICurrentUser, внедряет репозитории, давайте дадим некоторый код для чтения:

public class CurrentPortalUser : ICurrentUser
{
    private readonly IUserRepository _userRepository;
    private readonly IControllerActionRepository _controllerActionRepository;
    private readonly IDepartmentRepository _departmentRepository;
    private static List<DepartmentModel> _allDepartments = new List<DepartmentModel>();
    private static List<RoleModel> _allRoles = new List<RoleModel>();
    private static List<ControllerActionModel> _allControllerActionRoles = new List<ControllerActionModel>();
    /// <summary>
    /// Simpler version of _allControllerActionRoles, Key: actionNamespace, Value: roleIds;
    /// </summary>
    private static Dictionary<string, string[]> _allControllerActionRoleIds = new Dictionary<string, string[]>();

    /// <summary>
    /// Key: CountryId, Value: DepartmentId
    /// </summary>
    private static Dictionary<int, int> _departmentCountryList = new Dictionary<int, int>();

    private readonly IHttpContextAccessor _httpContextAccessor;
    public CurrentPortalUser(IHttpContextAccessor httpContextAccessor, IUserRepository userRepository, IControllerActionRepository controllerActionRepository, IDepartmentRepository departmentRepository)
    {
        _httpContextAccessor = httpContextAccessor;
        _userRepository = userRepository;
        _controllerActionRepository = controllerActionRepository;
        _departmentRepository = departmentRepository;
    }

    public void ReloadDepartmentAndRoleData()
    {
        _allDepartments = _departmentRepository.GetAll().ToList();
        int[] departmentIds = _allDepartments.Select(x => x.DepartmentId).ToArray();
        List<DepartmentCountryModel> allDepartmentCountries = _departmentRepository.GetDepartmentOperatingCountries(departmentIds);
        _departmentCountryList = allWarehouseCountries.ToDictionary(x => x.CountryId, y => y.DepartmentId);

        //load role and controller configuration, basically tells me what role is allowed to execute what controller action within the web app.
        _allRoles = _userRepository.GetRoles().ToList();
        _allControllerActionRoles = _controllerActionRepository.GetAllWithRoles().ToList();
        _allControllerActionRoleIds = _allControllerActionRoles.ToDictionary(x => x.NameSpace.ToLower(), y => y.Roles.Select(z => z.RoleId).ToArray());

    }

    public List<DepartmentModel> GetAllowedDepartments()
    {
        if (!_allDepartments.Any())//store in memory, just like we are going to store all roles in memory
        {
            ReloadDepartmentAndRoleData();
        }
        if (!_allRoles.Any())
        {
            //load role and controller configuration
            ReloadDepartmentAndRoleData();
        }
        if (IsInRole(RoleEnum.SuperAdmin))
        {
            return _allDepartments;
        }
        List<DepartmentModel> allowedDepartments = new List<DepartmentModel>();
        foreach (DepartmentModel department in _allDepartments)
        {
            if (IsInRole(department.Name))
            {
                allowedDepartments.Add(department);
            }
        }
        return allowedDepartments;
    }

    public string GetUserId()
    {
        if (_httpContextAccessor.HttpContext.User.Identity.IsAuthenticated)
        {
            return _httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.Name)?.Value;
        }
        return null;
    }

    public int? GetContactId()
    {
        int? contactId = null;
        if (_httpContextAccessor.HttpContext.User.Identity.IsAuthenticated)
        {
            contactId = _userRepository.GetContactId(GetUserId());
        }
        return contactId;
    }
}

Есть еще кое-что, в основном сводится к получению некоторых данных, если они кэшируются, то получим ихиз кеша, иначе возьмите его из базы данных и кешируйте или поместите в сессию.

Итак, теперь наступает момент, когда я показываю вам ужасный код, за который меня немного смущает:).

public stattic class InfrastructureHelper
{
    //..more static members that are heavily used here, but these one are a bit-less depended on a database, as long as data can be obtained from IcurrentUser


    public static ICurrentUser User
    {
        get
        {
            return _container.GetInstance<ICurrentUser>();
        }
    }  

    //yup..that's static...
    private static ISimpleContainer _container;
    //this method is used via the Startup class, so only once
    public static void Initialize(ISimpleContainer container)
    {
        _container = container;
    }
}

//Since I don't have Asp.Net references in the project of InfrastructureHelper, I use a simple interface that of course does the same as the normal container
public interface ISimpleContainer
{
    TService GetInstance<TService>() where TService : class;
}

И последнее, но не менее важное: регистрация ICurrentUser является временной (в классе запуска)

services.AddTransient<ICurrentSession, CurrentPortalSession>();
services.AddTransient<ICurrentUser, CurrentPortalUser>();

Я получаю сообщение об исключении на InfrastructureHelper.User.GetContactId(), поскольку во время или после входа в систему оно используетсямного одновременно.Я мог бы попытаться избежать одновременного выполнения некоторых запросов или создать блокировку вокруг этого, но это не кажется правильным.

Ждем ваших ответов!Извините, это очень много анти-паттернов, это кусок старого кода, который был легко скопирован в новую базовую среду asp.net без перепроверки, потому что он прост в использовании:).

Обновление

Так что, согласно комментариям, я искал способ успешно внедрить InfrastructureHelper везде.Но когда я пытаюсь внедрить это в каждый репозиторий (сделать его частью BaseRepository), тогда, конечно, будет обнаружена циклическая зависимость: ICurrentUser зависит от некоторых репозиториев, и каждый репозиторий зависит от InfrastructureHelper (и, таким образом, ICurrentUser).

...