Sunday, February 24, 2013

WinRT, Caliburn.Micro and IoC - Part 3 (SuspensionManager)

In my previous posts I integrated both Ninject and Unity into Caliburn.Micro for WinRT. This fixed issues I was having with a recent 1.4.1 code change, removing implicit self bindings.

The next thing I wanted to work through was state management. The VisualStudio templates have a nice implementation of state management through SuspensionManager and LayoutAwarePage.  LayoutAwarePage isn't an ideal match as a View for Caliburn.Micro MVVM. It tries to implement it's own MVVM pattern with the DefaultViewModel property, it contains a whole nested observable class definition, LoadState/SaveState logic in the View, and just plain does too much non-view stuff. 

With that said, LayoutAwarePage has many things going for it with VisualState switching management and hooking key and mouse navigation. LayoutAwarePage is only loosely coupled to SuspensionManager, so I figured I could fix these issues while still using SuspensionManager. To start out, I copied the code from LayoutAwarePage into a new class called AppPage. Then, trimmed out the DefaultViewModel and the observable collection definition.


AppPage.cs (interesting bits)


namespace CaliburnMicro_IoC_Sample.Code
{

    public class AppPage : Page
    {

.
.
.
        #region Process lifetime management

        private String _pageKey;

        /// <summary>
        /// Invoked when this page is about to be displayed in a Frame.
        /// </summary>
        /// <param name="e">Event data that describes how this page was reached.  The Parameter
        /// property provides the group to be displayed.</param>
protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            // Returning to a cached page through navigation shouldn't trigger state loading
            if (this._pageKey != null)
                return;

            var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
            this._pageKey = "Page-" + this.Frame.BackStackDepth;

            if (e.NavigationMode == NavigationMode.New)
            {
                // Clear existing state for forward navigation when adding a new page to the
                // navigation stack
                var nextPageKey = this._pageKey;
                int nextPageIndex = this.Frame.BackStackDepth;
                while (frameState.Remove(nextPageKey))
                {
                    nextPageIndex++;
                    nextPageKey = "Page-" + nextPageIndex;
                }

                // Pass the navigation parameter to the new page
                this.LoadState(e.Parameter, null, e);
            }
            else
            {
                // Pass the navigation parameter and preserved page state to the page, using
                // the same strategy for loading suspended state and recreating pages discarded
                // from cache
                this.LoadState(e.Parameter, (Dictionary<String, Object>)frameState[this._pageKey], e);
            }
        }

.
.
.

        /// <summary>
        /// Populates the page with content passed during navigation.  Any saved state is also
        /// provided when recreating a page from a prior session.
        /// </summary>
        /// <param name="navigationParameter">The parameter value passed to
        /// <see cref="Frame.Navigate(Type, Object)"/> when this page was initially requested.
        /// </param>
/// <param name="pageState">A dictionary of state preserved by this page during an earlier
        /// session.  This will be null the first time a page is visited.</param>
protected virtual void LoadState(Object navigationParameter, Dictionary<String, Object> pageState, NavigationEventArgs e)
        {
            if (DataContext == null)
            {
                var svc = ServiceLocator.Current.GetInstance<inavigationservice>() as SuspensionFrameAdapter;
                if (svc != null) svc.DoNavigated(this, e);
            }

            if (DataContext is IHaveState && pageState != null && pageState.Count() > 0)
            {
                var haveState = DataContext as IHaveState;
                haveState.LoadState(Convert.ToString(navigationParameter), pageState);
            }
        }

        /// <summary>
        /// Preserves state associated with this page in case the application is suspended or the
        /// page is discarded from the navigation cache.  Values must conform to the serialization
        /// requirements of <see cref="SuspensionManager.SessionState"/>.
        /// </summary>
        /// <param name="pageState">An empty dictionary to be populated with serializable state.</param>
protected virtual void SaveState(Dictionary<String, Object> pageState)
        {
            if (DataContext is IHaveState)
            {
                var haveState = DataContext as IHaveState;
                haveState.SaveState(pageState, SuspensionManager.KnownTypes);
            }
        }

        #endregion

    }
You may notice a strange DataContext null test in LoadState, that has to do with a problem with Frames and SuspensionManager, described below. It is not my best work, and I should probably refactor it to utilize another interface, rather than a little hidden knowledge that the INavigationService was implemented by SuspensionFrameAdapter.

Tuesday, February 12, 2013

WinRT, Caliburn.Micro and IoC - Part 2 (Unity)

In a previous post I started by wrapping Ninject (or the closest build I could find that worked with WinRT) for use in Caliburn.Micro.

I wanted to do the same thing for Unity, so I could compare and contrast the two in WinRT applications.  The good news is that Unity has a WinRT build. The bad news is only the core dll is available in WinRT.  Also the UnityServiceLocator.cs file is not included in the default build.  You will either need to copy the code into your project, or pull down the source, and rebuild after including the cs file into the WinRT project.

For those who don't want to pull down the source, you can just add this class to your project:

Monday, February 11, 2013

WinRT, Caliburn.Micro and IoC - Part 1 (Ninject)

I had been preparing to roll out a Windows 8 Store application (a rewrite of an existing Windows Phone app LEAF Buddy) and was trying to find the best components to use together. I like Caliburn.Micro, and it does have a build for WinRT. Unfortunately its SimpleContainer got even more simple in version 1.4.1, and will not auto-create concrete classes! You have to register everything you ever want to resolve.

This left me in the market for a good IoC for WinRT.  I have used Ninject in the past and really like it.  I have also used Unity.  I could really go either way.  I figured I would see how bad it was to use both and if I ran into any issues further on in my project.  To isolate myslef from any future migration issues I decided I would wrap any IoC I used in an IServiceLocator from the CommonServiceLocator project.  This way it should be fairly trivial to switch (as long as I stuck with constructor injection.

First up was Ninject.  I needed a ServiceLocator wrapper, and a CaliburnApplication wrapper.  Well, first I needed an instance of Ninject that would run on WinRT...  If you do what I did, your first stop was NuGet, which fails...

This was packaged 5/9/2012, so it must have been built with the pre-release WinRT build and .Net 4.5 framework.  The latest release on the Ninject download site is from March, 2012, and has an even earlier version number.

Anyway Remo Gloor has code in a github fork with a start towards a WinRT build. You will have to add a few conditional compile directives and fix up 2 or 3 class references, with the biggest part being disabling lightweight code generation... But, it shouldn't take more than an hour to get it compiling.