如何根据用户替换 Ninject 绑定?
这个问题需要一些上下文才能有意义,所以我将从项目的描述开始。
项目背景
我有一个开源项目,它是一个命令提示符风格的网站(U413.com,U413.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
我的问题是 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
您应该使用提供程序,而不是将逻辑放入模块中。首先,您可以创建类似 SecurityInformation 类的内容,它可以告诉您用户是否经过身份验证及其角色。目前,我认为您的实现仅使用第一个用户的授权信息来启动应用程序。但是,您希望在每次请求此模块的实例时检查当前用户的权限。
然后绑定将被指定为
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.
The binding would then be specified like
您的应用程序中运行的内核数量应该(非常)有限:在大多数情况下最好只有一个。不要尝试为每个用户创建新内核,而是让您的绑定为每个用户生成不同的实现。正如 Vadim 指出的那样,这可以使用
IProvider
来完成。以下是同一想法的变体:在此实现中,我希望使用一个类来实现 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
IProvider
s as Vadim points out. Following is a variation on the same idea:In this implementation, I would expect
ISessionManager
to be implemented with a class that checks the currentHttpContext
to determine who is logged in, and provide basic information about this person.InRequestScope()
now resides in theNinject.Web.Common
library, and will help to avoid re-performing all this logic more than once per request.