Использование Unity Dependency Injection в многопользовательском веб-приложении: второй пользователь для входа в систему заставляет первого пользователя просматривать данные второго пользователя - PullRequest
0 голосов
/ 15 октября 2018

Я пытаюсь реализовать веб-приложение с использованием ASP.NET MVC и инфраструктуры Microsoft Unity DI.Приложение должно поддерживать несколько пользовательских сессий одновременно, каждый из которых имеет свое собственное соединение с отдельной базой данных (но все пользователи используют один и тот же DbContext; схемы базы данных идентичны, это разные данные).

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

Мой контейнер инициализируется следующим образом:

// Global.asax.cs

public static UnityContainer CurrentUnityContainer { get; set; }

protected void Application_Start()
{
    // ...other code...
    CurrentUnityContainer = UnityConfig.Initialize();
    // misc services - nothing data access related, apart from the fact that they all depend on IRepository<ClientContext>
    UnityConfig.RegisterComponents(CurrentUnityContainer);
}

// UnityConfig.cs

public static UnityContainer Initialize()
{
    UnityContainer container = new UnityContainer();
    DependencyResolver.SetResolver(new UnityDependencyResolver(container));
    GlobalConfiguration.Configuration.DependencyResolver = new Unity.WebApi.UnityDependencyResolver(container);

    return container;
}

Это код, который вызывается при входе в систему:

// UserController.cs

UnityConfig.RegisterUserDataAccess(MvcApplication.CurrentUnityContainer, UserData.Get(model.AzureUID).CurrentDatabase);

// UnityConfig.cs

public static void RegisterUserDataAccess(IUnityContainer container, string databaseName)
{
    container.AddExtension(new DataAccessDependencies(databaseName));
}

// DataAccessDependencies.cs

public class DataAccessDependencies : UnityContainerExtension
{
    private readonly string _databaseName;

    public DataAccessDependencies(string databaseName)
    {
        _databaseName = databaseName;
    }

    protected override void Initialize()
    {
        IConfigurationBuilder configurationBuilder = Container.Resolve<IConfigurationBuilder>();

        Container.RegisterType<ClientContext>(new SessionLifetimeManager(), new InjectionConstructor(configurationBuilder.GetConnectionString(_databaseName)));
        Container.RegisterType<IRepository<ClientContext>, RepositoryService<ClientContext>>(new SessionLifetimeManager());
    }
}

// SessionLifetimeManager.cs

public class SessionLifetimeManager : LifetimeManager
{
    private readonly string _key = Guid.NewGuid().ToString();

    public override void RemoveValue(ILifetimeContainer container = null)
    {
        HttpContext.Current.Session.Remove(_key);
    }

    public override void SetValue(object newValue, ILifetimeContainer container = null)
    {
        HttpContext.Current.Session[_key] = newValue;
    }

    public override object GetValue(ILifetimeContainer container = null)
    {
        return HttpContext.Current.Session[_key];
    }

    protected override LifetimeManager OnCreateLifetimeManager()
    {
        return new SessionLifetimeManager();
    }
}

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

Затем, как только второй пользователь входит в систему, происходит авария.

Последний пользователь, предложивший запросвызов RegisterUserDataAccess, кажется, всегда имеет «приоритет»;их данные отображаются на приборной панели и ничего больше .Независимо от того, инициировано ли это входом в систему или выбором доступа к базе данных в моем веб-приложении, которое вызывает тот же метод для перенаправления подключения пользователя к другой базе данных, к которой у него есть права доступа, последняя из рисовавших всегда навязывает свои данные.на всех других пользователей веб-приложения.Если я правильно понимаю, это проблема, которую SessionLifetimeManager должен был решить - к сожалению, я действительно не могу заставить ее работать.

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

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

Большое спасибо.

Ответы [ 2 ]

0 голосов
/ 15 октября 2018

Спасибо, Мухаммед.Ваш ответ поставил меня на правильный путь - в итоге я наконец-то решил эту проблему с помощью RepositoryFactory, который создается в InjectionFactory во время регистрации и возвращает репозиторий, который всегда оборачивается вокруг ClientContext, указывающего на текущего вошедшего в систему пользователя.текущая выбранная база данных.

// DataAccessDependencies.cs

protected override void Initialize()
{
    IConfigurationBuilder configurationBuilder = Container.Resolve<IConfigurationBuilder>();

    Container.RegisterType<IRepository<ClientContext>>(new InjectionFactory(c => {
        ClientRepositoryFactory repositoryFactory = new ClientRepositoryFactory(configurationBuilder);
        return repositoryFactory.GetRepository();
    }));
}

// ClientRepositoryFactory.cs

public class ClientRepositoryFactory : IRepositoryFactory<RepositoryService<ClientContext>>
{
    private readonly IConfigurationBuilder _configurationBuilder;

    public ClientRepositoryFactory(IConfigurationBuilder configurationBuilder)
    {
        _configurationBuilder = configurationBuilder;
    }

    public RepositoryService<ClientContext> GetRepository()
    {
        var connectionString = _configurationBuilder.GetConnectionString(UserData.Current.CurrentPermission);
        ClientContext ctx = new ClientContext(connectionString);
        RepositoryService<ClientContext> repository = new RepositoryService<ClientContext>(ctx);

        return repository;
    }
}

// UserData.cs (multiton-singleton-hybrid)

public static UserData Current
{
    get
    {
        var currentAADUID = (string)(HttpContext.Current.Session["currentAADUID"]);
        return Get(currentAADUID);
    }
}

public static UserData Get(string AADUID)
{
    UserData instance;

    lock(_instances)
    {
        if(!_instances.TryGetValue(AADUID, out instance))
        {
            throw new UserDataNotInitializedException();
        }
    }
    return instance;
}

public static UserData Current
{
    get
    {
        var currentAADUID = (string)(HttpContext.Current.Session["currentAADUID"]);
        return Get(currentAADUID);
    }
}

public static UserData Get(string AADUID)
{
    UserData instance;

    lock(_instances)
    {
        if(!_instances.TryGetValue(AADUID, out instance))
        {
            throw new UserDataNotInitializedException();
        }
    }
    return instance;
}
0 голосов
/ 15 октября 2018

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

SessionLifetimeManager предназначен для того, чтобы убедиться, что вы получаете только один экземпляр разрешенных вами объектов за один сеанс.

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

...