如何根据用户替换 Ninject 绑定?

发布于 2024-11-07 17:07:36 字数 3852 浏览 0 评论 0原文

这个问题需要一些上下文才能有意义,所以我将从项目的描述开始。

项目背景

我有一个开源项目,它是一个命令提示符风格的网站(U413.comU413.com, ="http://u413.googlecode.com" rel="nofollow noreferrer">U413.GoogleCode.com)。该项目是在 ASP.NET MVC 3 中构建的,并使用 Entity Framework 4。本质上,该站点允许您传递命令和参数,然后站点返回一些数据。这个概念相当简单,但我不想使用一个巨大的 IF 语句来处理命令。相反,我决定做一些有点独特的事情,并构建一个对象,其中包含所有可能的命令作为该对象的方法。

该站点使用反射来定位与发送的命令相对应的方法并执行它们。该对象是根据当前用户动态构建的,因为某些用户可以访问与其他用户不同的命令(例如,管理员拥有多个主持人,mods 拥有多个用户,等等)。

我构建了一个自定义 CommandModuleFactory ,它将在 MVC 控制器中创建,并调用它的 BuildCommandModule 方法来构建命令模块对象。我现在使用 Ninject 进行依赖项注入,我想逐步淘汰这个 CommandModuleFactory,转而将 ICommandModule 注入到控制器中,而无需控制器执行任何工作。

ICommandModule 定义了一个方法,如下所示:

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

InvokeCommand 是对自身执行反射以查找可能与传入命令匹配的所有方法的方法。

然后,我有五个继承自 ICommandModule 的不同对象(其中一些也继承自其他模块,因此我们不会重复命令):

AdministratorCommandModule 继承自 ModeratorCommandModule 继承自 UserCommandModule,而 UserCommandModule 继承自 BaseCommandModule

然后,我还有从 BaseCommandModule 继承的 VisitorCommandModule,因为访问者将无法访问其他三个命令模块中的任何命令。

希望您能开始了解这是如何工作的。我对迄今为止这一切的运作方式感到非常自豪。

问题

我希望 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;
    }

您可以看到,在加载 Ninject 模块来构建我的命令模块之前,我将 IUserRepository 映射到其具体类型(尽量不要将 Ninject 绑定模块与我的命令模块混淆:S)。然后,我使用 kernel.Get() 来解决 Ninject 模块对其的依赖关系。

我的问题是 HttpContext.Current.User 为空。我不确定如何判断用户在 Ninject 绑定阶段是否登录。有什么想法吗?

当我进行 Ninject 绑定时,如何获取登录用户的引用?或者您能为我想出更好的方法来为我的 ICommandModule 进行条件绑定吗?

This question requires a bit of context before it makes sense so I'll just start with a description of the project.

Project Background

I have an open source project which is a command-prompt style website (U413.com, U413.GoogleCode.com). This project is built in ASP.NET MVC 3 and uses Entity Framework 4. Essentially the site allows you to pass in commands and arguments and then the site returns some data. The concept is fairly simple, but I didn't want to use one giant IF statement to handle the commands. Instead, I decided to do something somewhat unique and build an object that contains all the possible commands as methods on the object.

The site uses reflection to locate methods that correspond to the sent command and execute them. This object is built dynamically based on the current user because some users have access to different commands than other users (e.g. Administrators have more than moderators, and mods have more than users, etc, etc).

I built a custom CommandModuleFactory that would be created in the MVC controller and would call it's BuildCommandModule method to build out a command module object. I am now using Ninject for dependency injection and I want to phase out this CommandModuleFactory, in favor of having the ICommandModule injected into the controller without the controller doing any work.

ICommandModule has one method defined, like this:

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

InvokeCommand is the method that performs the reflection over itself to find all methods that might match the passed in command.

I then have five different objects that inherit from ICommandModule (some of them inherit from other modules as well so we don't repeat commands):

AdministratorCommandModule inherits from ModeratorCommandModule which inherits from UserCommandModule which inherits from BaseCommandModule.

I then also have VisitorCommandModule which inherits from BaseCommandModule because visitors will not have access to any of the commands in the other three command modules.

Hopefully you can start to see how this works. I'm pretty proud of the way this is all working so far.

The Question

I want Ninject to build my command module for me and bind it to ICommandModule so that I can just make my MVC controller dependent upon ICommandModule and it will receive the correct version of it. Here is what my Ninject module looks like where the binding takes place.

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>();
    }
}

A couple things are happening here. Firstly, the Ninject module depends on a few things. It depends on a boolean indicating whether or not the user is authenticated (to determine if it will be one of the logged in command modules, or the visitor command module). Next it depends on a string username and IUserRepository. Here is where my mappings are defined in 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;
    }

You can see that I map IUserRepository to its concrete type before I load the Ninject module to build my command module (try not to confuse Ninject binding modules with my command modules :S). I then use kernel.Get<IUserRepository>() to resolve my Ninject module's dependency on it.

My problem here is that HttpContext.Current.User is null. I'm not sure how to tell whether or not a user is logged in during the Ninject binding phase. Any ideas?

How might I get reference to the logged in user when I'm doing my Ninject bindings? Or can you think of a better way for me to do conditional binding for my ICommandModule?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

梦亿 2024-11-14 17:07:36

您应该使用提供程序,而不是将逻辑放入模块中。首先,您可以创建类似 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>();

You should use a provider instead of putting the logic in your module. First you can create something like a SecurityInformation class that can tell you whether the user is authenticated and their role. Currently your implementation I think only uses the authorization information of the first user to start the app. However you want to check the current user's permissions every time an instance of this module is requested.

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>();
     }
}   

The binding would then be specified like

Kernel.Bind<ICommandModule>().ToProvider<CommandModuleProvider>();
り繁华旳梦境 2024-11-14 17:07:36

您的应用程序中运行的内核数量应该(非常)有限:在大多数情况下最好只有一个。不要尝试为每个用户创建新内核,而是让您的绑定为每个用户生成不同的实现。正如 Vadim 指出的那样,这可以使用 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 库中,有助于避免每个请求多次重新执行所有这些逻辑。

There should be a (very) limited number of Kernels running in your application: preferably just one in most cases. Instead of trying to create a new kernel for each user, make your binding produce a different implementation for each user. This can be done using IProviders as Vadim points out. Following is a variation on the same idea:

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();
}

In this implementation, I would expect ISessionManager to be implemented with a class that checks the current HttpContext to determine who is logged in, and provide basic information about this person.

InRequestScope() now resides in the Ninject.Web.Common library, and will help to avoid re-performing all this logic more than once per request.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文