0

Clearly, I am missing something. I have an MVC application and have installed Ninject 3 and the MVC3 extensions (although I am running MVC4). I have a SiteSettings class that is referenced throughout the project, which looks like this:

public class SiteSettings
{
    private static readonly Common.Logging.ILog Logger = Common.Logging.LogManager.GetCurrentClassLogger();
    private static ObservableDictionary<string, string> settings;
    private static bool Initialized = false;
    private static DataPersister persister;

    public static void Initialize()
    {
        if (Initialized) throw new InvalidOperationException("The SiteSettings object has already been initialized.");
        persister = new DataPersister();
        using (var u = persister.UnitOfWorkFactory.GetUnitOfWork())
        {
            var settingsList = u.SiteSettings.GetAll();
            settings = new ObservableDictionary<string, string>(settingsList.ToDictionary(key => key.SiteSettingName, value => value.SiteSettingValue));
            settings.OnChange += new kvpChangeEvent<string, string>(settings_OnChange);
        }
        Initialized = true;
    }

    static void settings_OnChange(object sender, odKVPChangeEventArgs<string, string> e)
    {
        using (var u = persister.UnitOfWorkFactory.GetUnitOfWork())
        {
            var setting = u.SiteSettings.GetByName(e.Key);
            setting.SiteSettingValue = e.Value;
            u.SiteSettings.Update(setting);
            u.Save();
            Logger.Info(i => i("Changed the '{0}' site setting from '{1}' to '{2}'.", e.Key, e.OldValue, e.Value));
        }
    }

    private static int _ItemsPerPage;
    public static int ItemsPerPage
    {
        get
        {
            return _ItemsPerPage;
        }
        set
        {
            _ItemsPerPage = value;
            settings["itemsPerPage"] = value.ToString();
        }
    }

    private static int _SessionLifeInMinutes;
    public static int SessionLifeInMinutes
    {
        get
        {
            return _SessionLifeInMinutes;
        }
        set
        {
            _SessionLifeInMinutes = value;
            settings["sessionLifeInMinutes"] = value.ToString();
        }
    }

    private static string _DateFormat;
    public static string DateFormat
    {
        get
        {
            return _DateFormat;
        }
        set
        {
            _DateFormat = value;
            settings["defaultDateFormat"] = value;
        }
    }
}

I built a data persistence object like so:

public class DataPersister
{
    public IUnitOfWorkFactory UnitOfWorkFactory { get; set; }
}

... and I have my NinjectWebCommon.cs looks like this:

public static class NinjectWebCommon
{
    private static readonly Bootstrapper bootstrapper = new Bootstrapper();

    /// <summary>
    /// Starts the application
    /// </summary>
    public static void Start()
    {
        DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule));
        DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule));
        bootstrapper.Initialize(CreateKernel);
    }

    /// <summary>
    /// Stops the application.
    /// </summary>
    public static void Stop()
    {
        bootstrapper.ShutDown();
    }

    /// <summary>
    /// Creates the kernel that will manage your application.
    /// </summary>
    /// <returns>The created kernel.</returns>
    private static IKernel CreateKernel()
    {
        var kernel = new StandardKernel();
        kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
        kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();

        RegisterServices(kernel);
        return kernel;
    }

    /// <summary>
    /// Load your modules or register your services here!
    /// </summary>
    /// <param name="kernel">The kernel.</param>
    private static void RegisterServices(IKernel kernel)
    {
        kernel.Bind<IUnitOfWork>().To<NHUnitOfWork>();
        kernel.Bind<IUnitOfWorkFactory>().To<NHUnitOfWorkFactory>();
    }
}

It seems to me I've met all my requirements for dependency injection. My Global.asax.cs Application_Start() looks like this:

protected void Application_Start()
{
    ViewEngines.Engines.Clear();
    ViewEngines.Engines.Add(new MonoRazorViewEngine());

    AreaRegistration.RegisterAllAreas();
    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);
    ControllerBuilder.Current.DefaultNamespaces.Add("MyApplication.Application.Controllers");
    Initialize.Security();
    SiteSettings.Initialize();
}

...and yet, my SiteSettings class always has a null IUnitOfWorkFactory when I try to collect the data I need.

What am I doing wrong? Everything seems to be as all the examples suggest it should be, but I get no love.

UPDATE

Using Bassam Mehanni's advice, I rewrote my DataPersister class to look like this:

public class DataPersister
{
    private IUnitOfWorkFactory UnitOfWorkFactory;
    public DataPersister(IUnitOfWorkFactory unitOfWorkFactory)
    {
        UnitOfWorkFactory = unitOfWorkFactory;
    }
    public IUnitOfWork GetUnitOfWork()
    {
        return UnitOfWorkFactory.GetUnitOfWork();
    }
}

...but of course now my SiteSettings class complains about my parameterless constructor. What should I do about that?

UPDATE 2

Ok, continuing on, I rewrote my DataPersister class like so:

public class DataPersister
{
    private static readonly Common.Logging.ILog Logger = Common.Logging.LogManager.GetCurrentClassLogger();

    private IUnitOfWorkFactory UnitOfWorkFactory { get; set; }
    public IUnitOfWork GetUnitOfWork()
    {
        return UnitOfWorkFactory.GetUnitOfWork();
    }
    [Inject]
    public DataPersister(IUnitOfWorkFactory factory)
    {
        Logger.Info("Injected constructor called");
        UnitOfWorkFactory = factory;
    }
    public DataPersister()
    {
        Logger.Info("Parameterless constructor called");
    }
}

then I rewrote my SiteSettings class like so:

public class SiteSettings
{
    private static readonly Common.Logging.ILog Logger = Common.Logging.LogManager.GetCurrentClassLogger();
    private ObservableDictionary<string, string> settings;
    private DataPersister persister;
    private SiteSettings()
    {
        persister = new DataPersister();
        using (var u = persister.GetUnitOfWork())
        {
            var settingsList = u.SiteSettings.GetAll();
            settings = new ObservableDictionary<string, string>(settingsList.ToDictionary(key => key.SiteSettingName, value => value.SiteSettingValue));
            settings.OnChange += new kvpChangeEvent<string, string>(settings_OnChange);
        }
    }
    private static SiteSettings instance;

    public static SiteSettings Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new SiteSettings();
            }
            return instance;
        }
    }

    private void settings_OnChange(object sender, odKVPChangeEventArgs<string, string> e)
    {
        using (var u = persister.GetUnitOfWork())
        {
            var setting = u.SiteSettings.GetByName(e.Key);
            setting.SiteSettingValue = e.Value;
            u.SiteSettings.Update(setting);
            u.Save();
            Logger.Info(i => i("Changed the '{0}' site setting from '{1}' to '{2}'.", e.Key, e.OldValue, e.Value));
        }
    }

    private int _ItemsPerPage;
    public int ItemsPerPage
    {
        get
        {
            return _ItemsPerPage;
        }
        set
        {
            _ItemsPerPage = value;
            settings["itemsPerPage"] = value.ToString();
        }
    }

    private int _SessionLifeInMinutes;
    public int SessionLifeInMinutes
    {
        get
        {
            return _SessionLifeInMinutes;
        }
        set
        {
            _SessionLifeInMinutes = value;
            settings["sessionLifeInMinutes"] = value.ToString();
        }
    }

    private string _DateFormat;
    public string DateFormat
    {
        get
        {
            return _DateFormat;
        }
        set
        {
            _DateFormat = value;
            settings["defaultDateFormat"] = value;
        }
    }
}

Shouldn't this work? because it doesn't. The DataPersister class always gets called with the parameterless constructor. My kernel binding looks like this:

private static void RegisterServices(IKernel kernel)
{
    kernel.Bind<IUnitOfWork>().To<NHUnitOfWork>();
    kernel.Bind<IUnitOfWorkFactory>().To<NHUnitOfWorkFactory>();
}

Is there something else I am missing? This is getting very frustrating.

Jeremy Holovacs
  • 22,480
  • 33
  • 117
  • 254
  • With MVC you can just inherit from `NinjectHttpApplication`.. – Simon Whitehead Jan 17 '13 at 23:35
  • I'm kind of new to injection libraries, and have even less experience with ninject, so I'm putting this as a comment instead of an answer... But where's your injection point? Where's the call to `Kernel.Get`? - http://ninject.codeplex.com/wikipage?title=Modules%20and%20the%20Kernel The MVC ninject library registers the ninject container for the sake of MVCs own factories, but your `IUnitOfWorkFactory` creation isn't through said factory? – Snixtor Jan 17 '13 at 23:35
  • @Snixtor it's some magic in the setup. I put a break point on the registration, and it does indeed get reached... somehow. – Jeremy Holovacs Jan 17 '13 at 23:45
  • @SimonWhitehead, I tried that but hell if I could get that to work either. – Jeremy Holovacs Jan 17 '13 at 23:45
  • The registration may be getting run, but the *retrieval* isn't. – Snixtor Jan 17 '13 at 23:58
  • @Snixtor I'm quite sure part of my problem is that I don't understand how the dependency injection works in the first place. I am trying to retrofit it into a fairly large and growing project because it seems like a good idea to uncouple the application layer from the persistence layer... but the quick tutorials seem to be leading me astray. – Jeremy Holovacs Jan 18 '13 at 00:01

2 Answers2

3

Typically ninject will inject your service in a constructor or something, it doesn't magically turn all your interfaces to object instances at run time

e.g.:

public class MyController : Controller
{
   private IServiceThatINeed _serviceThatINeed;
   public MyController(IServiceThatINeed serviceThatINeed)
   { 
       _serviceThatINeed = _serviceThatINeed;
   }
}

in this case since you registered your kernel instance, mvc knows how to resolve this dependence and will pass an instance of an object that implement IServiceThatINeed (assuming that you told ninject how to resolve this dependency.

Now there might be instance where you will need to get a service without it being injected in a constructor by the mvc framework, in these instances (like the one you have here), you will need to use ServiceLocator

e.g.:

var myService = ServiceLocator.Current.GetInstance<IServiceThatINeed>()

to use the ServiceLocator, you need to add a reference to Microsoft.Practices.ServiceLocation

Hope that helps!

Bassam Mehanni
  • 14,796
  • 2
  • 33
  • 41
  • Thanks for the answer. I tried to write my `DataPersister` class in the way you suggested (see update), but the parameter thing still baffles me. Can you advise? – Jeremy Holovacs Jan 17 '13 at 23:58
  • @Jeremy Holovacs - This answer by Bassam is what I was trying to get at. – Snixtor Jan 17 '13 at 23:58
  • when you register the ninject with mvc it will use it whenever the mvc framework new up a class, but when you are the one new-ing up the class, you will have to resolve the dependency yourself, using the serviceLoator, inside your `SiteSettings` and then pass it in the constructor of `DataPersister` – Bassam Mehanni Jan 18 '13 at 00:07
  • 2
    Exactly. Some of it's automagic, some of it isn't. @JeremyHolovacs - This is possibly where some of the quick tutorials have led you astray, in not discussing when the MVC framework will and won't handle the injection "for you". – Snixtor Jan 18 '13 at 00:10
  • 2
    It should be noted that there are arguments against the service locator pattern and you should read up and decide whether it will impact you. The alternative is having Ninject inject factories that you can use to create objects. – Simon Whitehead Jan 18 '13 at 00:45
  • @SimonWhitehead Agreed! it's kindda of an anti-pattern and should be used as a last resort. – Bassam Mehanni Jan 18 '13 at 00:46
3

Static classes that depencend on none static classes is something you shouldn't do when using an IoC container. Instead you should create a none static class with a singleton lifetime.

  1. Make your SiteSettings class none static.
  2. Inject all dependencies e.g. IUnitOfWorkFactory into SiteSettings using constructor injection
  3. Create a binding in singleton scope for SiteSettings
  4. Get an instance of SiteSettings wherever you need access unsing constructor injection.

Example:

public class SiteSettings {
    public SiteSettings(IUnitOfWorkFactory uowFactory) { .... }

    ....
}


public class INeedToAccessSiteSettings
{
    public INeedToAccessSiteSettings(SiteSettings siteSettings) { .... }
}

kenrel.Bind<SiteSettings>().ToSelf().InSingletonScope();
Remo Gloor
  • 32,665
  • 4
  • 68
  • 98
  • Please see my update. I changed my `SiteSettings` class to a singleton model, but I still don't get any love. – Jeremy Holovacs Jan 19 '13 at 00:23
  • You still don't do dependency injection. You use the singelton pattern, new up the persister (persister = new DataPersister();) that can't work! Inject the persister in the constructor and remove the singleton pattern – Remo Gloor Jan 19 '13 at 00:55
  • Sorry for being dense, but how would I instantiate your `INeedToAccessSiteSettings` class? – Jeremy Holovacs Jan 19 '13 at 01:09
  • I don't know where you need to access the SiteSettings class. E.g. this could be a controller. – Remo Gloor Jan 19 '13 at 01:14
  • I use the site settings a lot in helpers and whatnot, which are in static classes. It seems I would need a wrapper of some kind, but I still cannot wrap my brain around how to properly instantiate this. – Jeremy Holovacs Jan 19 '13 at 01:22
  • To use IoC containers properly, your code has to fulfill some requirements. One of which is that you don't use static classes that have dependencies. So you have two options: get rid of all static classes an make them none static or pass all dependencies to the static methods when you call them. Using an IoC in a way it is not intended gets you the problems you have. don't expect the IoC container to do magic things it can't – Remo Gloor Jan 19 '13 at 01:28
  • Thanks. This got me close enough. I'll work some of those helpers into the base web view class, that should be fine. – Jeremy Holovacs Jan 19 '13 at 03:10