This question requires a bit of context before it makes sense so I'll just start with a description of the project.
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.
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
?
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>();
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:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With