WP7 - Registering an object as a singleton

Mar 18, 2011 at 3:07 AM

I'm working on a little test app to learn how to implement singletons in WP7 using Caliburn.Micro. The app is very straightforward:

  • In the bootstrapper, I register a singleton object called "DataModel," which has a single property--a string with a getter and setter.
  • This singleton needs to be accessible from two viewmodels, MainPageViewModel and DataPageViewModel.
  • I want to update the contents of the string from my second page (DataPage) and display the value on MainPage when I navigate back to it.

Once I get this figured out I will be using it to implement a master/details view in the for-production app I'm working on.

The test project builds and runs if I remove all references to the DataModel singleton. If I add the references back in and step through the code in debug mode, I find that DataModel is registered by the bootstrapper. Once the bootstrapper calls AddCustomConventions(), I am getting a null reference exception on line 113 of SimpleContainer.cs:

System.NullReferenceException was unhandled
  Message=NullReferenceException
  StackTrace:
       at System.Activator.InternalCreateInstance(Type type, BindingFlags invokeAttr, Binder binder, Object[] args, CultureInfo culture, StackCrawlMark& stackMark)
       at System.Activator.CreateInstance(Type type, Object[] args)
       at IoCTest.Framework.SimpleContainer.ActivateInstance(Type type, Object[] args)
       at IoCTest.Framework.PhoneContainer.ActivateInstance(Type type, Object[] args)
       at IoCTest.Framework.SimpleContainer.BuildInstance(Type type)
       at IoCTest.Framework.SimpleContainer.<>c__DisplayClass7.b__6()
       at IoCTest.Framework.SimpleContainer.GetInstance(Type service, String key)
       at IoCTest.AppBootstrapper.GetInstance(Type service, String key)
       at IoCTest.Framework.ViewModelLocator.<.cctor>b__0(Type viewType)
       at IoCTest.Framework.ViewModelLocator.<.cctor>b__1(Object view)
       at IoCTest.Framework.FrameAdapter.OnNavigated(Object sender, NavigationEventArgs e)
       at System.Windows.Navigation.NavigationService.RaiseNavigated(Object content, Uri uri, PhoneApplicationPage existingContentPage, PhoneApplicationPage newContentPage)
       at System.Windows.Navigation.NavigationService.CompleteNavigation(DependencyObject content)
       at System.Windows.Navigation.NavigationService.ContentLoader_BeginLoad_Callback(IAsyncResult result)
       at System.Windows.Navigation.PageResourceContentLoader.BeginLoad_OnUIThread(AsyncCallback userCallback, PageResourceContentLoaderAsyncResult result)
       at System.Windows.Navigation.PageResourceContentLoader.<>c__DisplayClass4.b__0(Object args)
       at System.Reflection.RuntimeMethodInfo.InternalInvoke(RuntimeMethodInfo rtmi, Object obj, BindingFlags invokeAttr, Binder binder, Object parameters, CultureInfo culture, Boolean isBinderDefault, Assembly caller, Boolean verifyAccess, StackCrawlMark& stackMark)
       at System.Reflection.RuntimeMethodInfo.InternalInvoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, StackCrawlMark& stackMark)
       at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
       at System.Delegate.DynamicInvokeOne(Object[] args)
       at System.MulticastDelegate.DynamicInvokeImpl(Object[] args)
       at System.Delegate.DynamicInvoke(Object[] args)
       at System.Windows.Threading.DispatcherOperation.Invoke()
       at System.Windows.Threading.Dispatcher.Dispatch(DispatcherPriority priority)
       at System.Windows.Threading.Dispatcher.OnInvoke(Object context)
       at System.Windows.Hosting.CallbackCookie.Invoke(Object[] args)
       at System.Windows.Hosting.DelegateWrapper.InternalInvoke(Object[] args)
       at System.Windows.RuntimeHost.ManagedHost.InvokeDelegate(IntPtr pHandle, Int32 nParamCount, ScriptParam[] pParams, ScriptParam& pResult)

It seems like I need to actually instantiate the object at some point, but I'm not sure where/how I would do that, and I can't find any examples. (The WP7 blog post shows singleton viewmodels in use, but the sample app doesn't use singletons.)

Here's my bootstrapper code:

        protected override void Configure()
        {
            LogManager.GetLog = type => new DebugLogger(type);

            container = new PhoneContainer(this);

            container.RegisterSingleton(typeof(MainPageViewModel), "MainPageViewModel", typeof(MainPageViewModel));
            container.RegisterPerRequest(typeof(DataPageViewModel), "DataPageViewModel", typeof(DataPageViewModel));
            container.RegisterSingleton(typeof(DataModel), "DataModel", typeof(DataModel));

            container.RegisterInstance(typeof(INavigationService), null, new FrameAdapter(RootFrame));
            container.RegisterInstance(typeof(IPhoneService), null, new PhoneApplicationServiceAdapter(PhoneService));

            AddCustomConventions();
        }

And an example of one of the viewmodels:

using IoCTest.Framework;
using IoCTest.Models;
using System;
namespace IoCTest
{
    public class MainPageViewModel : PropertyChangedBase
    {
        private readonly INavigationService navigationService;
        private readonly DataModel dataModel;

        public MainPageViewModel(INavigationService navigation, DataModel dataModel)
        {
            navigationService = navigation;
            this.dataModel = dataModel;
        }

        public void GetData()
        {
            navigationService.Navigate(new Uri("/Views/DataPage.xaml", UriKind.RelativeOrAbsolute));
        }
    }
}

I'm not currently doing anything with dataModel; I was just trying to get the project to build before I worried about actually manipulating it.

Am I supposed to create an instance of DataModel and somehow assign it to the container, or does the bootstrapper take care of that for me? What else might I be missing?

Thanks for the help!

Coordinator
Mar 18, 2011 at 12:14 PM

For the simple container, you should only provide the "key" value if you intend to resolve by it. In the case of DataModel, you do not want to resolve by key, but by type. So, your registration should be:

container.RegisterSingleton(typeof(DataModel), null, typeof(DataModel));

I'm not sure if that will fix everything, but it's definitely a problem.

Mar 18, 2011 at 12:52 PM

Yep, that appears to be the problem. No more null reference exceptions, and I seem to be able to access the object from my viewmodels. Thanks!

One other related question that I had: What are the pitfalls I should look out for in using singletons in my projects? They seem to come under a lot of criticism, but for an environment like Silverlight where it's sometimes clunky to maintain state, the judicious use of a few singletons seems like a good way to ease the pain. But I also don't want to get too comfortable in a pattern that has its own drawbacks... Any words of wisdom would be much appreciated.