Как вы чередуете привязки Ninject на основе пользователя?

Этот вопрос требует некоторого контекста, прежде чем он станет понятен, поэтому я просто начну с описания проекта.

История проекта

У меня есть проект с открытым исходным кодом, который представляет собой веб-сайт в стиле командной строки (U413.com, U413.GoogleCode.com). Этот проект построен в ASP.NET MVC 3 и использует Entity Framework 4. По сути, сайт позволяет передавать команды и аргументы, а затем сайт возвращает некоторые данные. Концепция довольно проста, но я не хотел использовать один гигантский оператор IF для обработки команд. Вместо этого я решил сделать что-то несколько уникальное и создать объект, который содержит все возможные команды в качестве методов объекта.

Сайт использует отражение, чтобы найти методы, которые соответствуют отправленной команде, и выполнить их. Этот объект создается динамически на основе текущего пользователя, поскольку некоторые пользователи имеют доступ к командам, отличным от других пользователей (например, администраторы имеют больше модераторов, а моды имеют больше пользователей, и т.д. И т.д.).

Я построил пользовательский CommandModuleFactory, который будет создан в контроллере MVC, и назову его методом BuildCommandModule для создания объекта командного модуля. Сейчас я использую Ninject для внедрения зависимостей и хочу постепенно прекратить этот CommandModuleFactory в пользу введения ICommandModule в контроллер без выполнения какой-либо работы контроллера.

ICommandModule имеет один определенный метод, например:

public interface ICommandModule
{
    object InvokeCommand(string command, List<string> args);
}

InvokeCommand - это метод, который выполняет отражение над собой, чтобы найти все методы, которые могут соответствовать переданной команде.

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

AdministratorCommandModule наследуется от ModeratorCommandModule, который наследуется от UserCommandModule, который наследуется от BaseCommandModule.

Затем у меня также есть VisitorCommandModule, который наследуется от BaseCommandModule, потому что посетители не будут иметь доступа ни к одной из команд в трех других командных модулях.

Надеюсь, вы можете начать видеть, как это работает. Я очень горжусь тем, как все это работает до сих пор.

Вопрос

Я хочу, чтобы Ninject собрал для меня свой командный модуль и связал его с ICommandModule, чтобы я мог просто сделать свой контроллер MVC зависимым от ICommandModule, и он получит правильную его версию. Вот как выглядит мой модуль Ninject, где происходит привязка.

public class BuildCommandModule : NinjectModule
{
    private bool _isAuthenticated;
    private User _currentUser;

    public BuildCommandModule(
        bool isAuthenticated,
        string username,
        IUserRepository userRepository
        )
    {
        this._isAuthenticated = isAuthenticated;
        this._currentUser = userRepository.GetUserBy_Username(username);
    }

    public override void Load()
    {
        if (_isAuthenticated)
            if (_currentUser.Administrator)
                //load administrator command module
                this.Bind<ICommandModule>().To<AdministratorCommandModule>();
            else if (_currentUser.Moderator)
                //Load moderator command module
                this.Bind<ICommandModule>().To<ModeratorCommandModule>();
            else
                //Load user command module
                this.Bind<ICommandModule>().To<UserCommandModule>();
        else
            //Load visitor command module
            this.Bind<ICommandModule>().To<VisitorCommandModule>();
    }
}

Здесь происходит пара вещей. Во-первых, модуль Ninject зависит от нескольких вещей. Это зависит от логического значения, указывающего, аутентифицирован ли пользователь или нет (чтобы определить, будет ли он одним из вошедших в систему командных модулей или командным модулем посетителя). Далее это зависит от имени пользователя и строки IUserRepository. Здесь мои отображения определены в Global.asax.

    protected override IKernel CreateKernel()
    {
        var kernel = new StandardKernel();

        kernel.Bind<IBoardRepository>().To<BoardRepository>();
        kernel.Bind<IReplyRepository>().To<ReplyRepository>();
        kernel.Bind<ITopicRepository>().To<TopicRepository>();
        kernel.Bind<IUserRepository>().To<UserRepository>();

        kernel.Load(new BuildCommandModule(User.Identity.IsAuthenticated, User.Identity.Name, kernel.Get<IUserRepository>()));

        return kernel;
    }

Вы можете видеть, что я сопоставляю IUserRepository его конкретный тип, прежде чем загружать модуль Ninject для сборки моего командного модуля (постарайтесь не путать связывающие модули Ninject с моими командными модулями: S). Затем я использую kernel.Get<IUserRepository>() для разрешения зависимости моего модуля Ninject.

Моя проблема здесь в том, что HttpContext.Current.User является нулевым. Я не уверен, как определить, вошел ли пользователь в систему на этапе привязки Ninject. Есть идеи?

Как я могу получить ссылку на вошедшего в систему пользователя, когда я делаю привязки Ninject? Или вы можете придумать лучший способ для меня сделать условное связывание для моего ICommandModule?

Ответ 1

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

public class CommandModuleProvider : IProvider
{
    public Type Type { get { return typeof(ICommandModule); } }
    public object Create(IContext context)
    {
        var securityInfo = context.Kernel.Get<SecurityInformation>();
        if (securityInfo.IsAuthenticated)
            if (securityInfo.IsCurrentUserAdministrator)
                //load administrator command module
                return context.Kernel.Get<AdministratorCommandModule>();
            else if (securityInfo.IsCurrentUserModerator)
                //Load moderator command module
                return context.Kernel.Get<ModeratorCommandModule>();
            else
                //Load user command module
                return context.Kernel.Get<UserCommandModule>();
        else
            //Load visitor command module
            return context.Kernel.Get<VisitorCommandModule>();
     }
}   

Затем привязка будет указана как

Kernel.Bind<ICommandModule>().ToProvider<CommandModuleProvider>();

Ответ 2

В вашем приложении должно быть (очень) ограниченное количество ядер: желательно в большинстве случаев только один. Вместо того, чтобы пытаться создать новое ядро ​​для каждого пользователя, сделайте свою привязку для другой реализации для каждого пользователя. Это можно сделать, используя IProvider, как указывает Вадим. Ниже приведена вариация по той же идее:

public override void Load()
{
    Bind<ICommandModule>().ToMethod(
        c =>
            {
                var sessionManager = c.Kernel<ISessionManager>();
                if (!sessionManager.IsAuthenticated)
                    return c.Kernel.Get<VisitorCommandModule>();
                var currentUser = sessionManager.CurrentUser;
                if (currentUser.IsAdministrator)
                    return c.Kernel.Get<AdministratorCommandModule>();
                if (currentUser.IsModerator)
                    return c.Kernel.Get<ModeratorCommandModule>();
                return c.Kernel.Get<UserCommandModule>();
            }).InRequestScope();
}

В этой реализации я ожидал бы, что ISessionManager будет реализован с классом, который проверяет текущий HttpContext, чтобы определить, кто входит в систему, и предоставить основную информацию об этом человеке.

InRequestScope() теперь находится в библиотеке Ninject.Web.Common и поможет избежать повторной реализации всей этой логики более одного раза за запрос.