Как вы чередуете привязки Ninject в зависимости от пользователя? - PullRequest
12 голосов
/ 18 мая 2011

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

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

У меня есть проект с открытым исходным кодом, который представляет собой веб-сайт в стиле командной строки ( 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?

Любая помощь приветствуется!

Ответы [ 2 ]

12 голосов
/ 18 мая 2011

Вы должны использовать провайдера вместо того, чтобы помещать логику в свой модуль.Сначала вы можете создать что-то вроде класса 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>();
3 голосов
/ 18 мая 2011

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

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 и поможет избежать повторного выполнения всей этой логики более одного раза за запрос.

...