Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you alternate Ninject bindings based on user?

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?

like image 695
Chev Avatar asked May 17 '11 21:05

Chev


2 Answers

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>();
like image 55
Vadim Avatar answered Oct 21 '22 07:10

Vadim


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.

like image 26
StriplingWarrior Avatar answered Oct 21 '22 09:10

StriplingWarrior