Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Combining MVVM Light Toolkit and Unity 2.0

This is more of a commentary than a question, though feedback would be nice. I have been tasked to create the user interface for a new project we are doing. We want to use WPF and I wanted to learn all of the modern UI design techniques available. Since I am fairly new to WPF I have been researching what is available. I think I have pretty much settled on using MVVM Light Toolkit (mainly because of its "Blendability" and the EventToCommand behavior!), but I wanted to incorporate IoC also. So, here is what I have come up with. I have modified the default ViewModelLocator class in a MVVM Light project to use a UnityContainer to handle dependency injections. Considering I didn't know what 90% of these terms meant 3 months ago, I think I'm on the right track.

// Example of MVVM Light Toolkit ViewModelLocator class that implements Microsoft 
// Unity 2.0 Inversion of Control container to resolve ViewModel dependencies.

using Microsoft.Practices.Unity;

namespace MVVMLightUnityExample
{
    public class ViewModelLocator
    {
        public static UnityContainer Container { get; set; }

        #region Constructors
        static ViewModelLocator() 
        {
            if (Container == null)
            {
                Container = new UnityContainer();

                // register all dependencies required by view models
                Container
                    .RegisterType<IDialogService, ModalDialogService>(new ContainerControlledLifetimeManager())
                    .RegisterType<ILoggerService, LogFileService>(new ContainerControlledLifetimeManager())
                    ;
            }
        }

        /// <summary>
        /// Initializes a new instance of the ViewModelLocator class.
        /// </summary>
        public ViewModelLocator()
        {
            ////if (ViewModelBase.IsInDesignModeStatic)
            ////{
            ////    // Create design time view models
            ////}
            ////else
            ////{
            ////    // Create run time view models
            ////}

            CreateMain();
        }

        #endregion

        #region MainViewModel

        private static MainViewModel _main;

        /// <summary>
        /// Gets the Main property.
        /// </summary>
        public static MainViewModel MainStatic
        {
            get
            {
                if (_main == null)
                {
                    CreateMain();
                }
                return _main;
            }
        }

        /// <summary>
        /// Gets the Main property.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
            "CA1822:MarkMembersAsStatic",
            Justification = "This non-static member is needed for data binding purposes.")]
        public MainViewModel Main
        {
            get
            {
                return MainStatic;
            }
        }

        /// <summary>
        /// Provides a deterministic way to delete the Main property.
        /// </summary>
        public static void ClearMain()
        {
            _main.Cleanup();
            _main = null;
        }

        /// <summary>
        /// Provides a deterministic way to create the Main property.
        /// </summary>
        public static void CreateMain()
        {
            if (_main == null)
            {
                // allow Unity to resolve the view model and hold onto reference
                _main = Container.Resolve<MainViewModel>();
            }
        }

        #endregion

        #region OrderViewModel

        // property to hold the order number (injected into OrderViewModel() constructor when resolved)
    public static string OrderToView { get; set; }

        /// <summary>
        /// Gets the OrderViewModel property.
        /// </summary>
        public static OrderViewModel OrderViewModelStatic
        {
            get 
            {
                // allow Unity to resolve the view model
                // do not keep local reference to the instance resolved because we need a new instance 
                // each time - the corresponding View is a UserControl that can be used multiple times 
                // within a single window/view
                // pass current value of OrderToView parameter to constructor!
                return Container.Resolve<OrderViewModel>(new ParameterOverride("orderNumber", OrderToView));
            }
        }

        /// <summary>
        /// Gets the OrderViewModel property.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
            "CA1822:MarkMembersAsStatic",
            Justification = "This non-static member is needed for data binding purposes.")]
        public OrderViewModel Order
        {
            get
            {
                return OrderViewModelStatic;
            }
        }
        #endregion

        /// <summary>
        /// Cleans up all the resources.
        /// </summary>
        public static void Cleanup()
        {
            ClearMain();
            Container = null;
        }
    }
}

And the MainViewModel class showing dependency injection usage:

using GalaSoft.MvvmLight;
using Microsoft.Practices.Unity;

namespace MVVMLightUnityExample
{
    public class MainViewModel : ViewModelBase
    {
        private IDialogService _dialogs;
        private ILoggerService _logger;

        /// <summary>
        /// Initializes a new instance of the MainViewModel class. This default constructor calls the 
        /// non-default constructor resolving the interfaces used by this view model.
        /// </summary>
        public MainViewModel() 
            : this(ViewModelLocator.Container.Resolve<IDialogService>(), ViewModelLocator.Container.Resolve<ILoggerService>())
        {
            if (IsInDesignMode)
            {
                // Code runs in Blend --> create design time data.
            }
            else
            {
                // Code runs "for real"
            }
        }

        /// <summary>
        /// Initializes a new instance of the MainViewModel class.
        /// Interfaces are automatically resolved by the IoC container.
        /// </summary>
        /// <param name="dialogs">Interface to dialog service</param>
        /// <param name="logger">Interface to logger service</param>
        public MainViewModel(IDialogService dialogs, ILoggerService logger)
        {
            _dialogs = dialogs;
            _logger = logger;

            if (IsInDesignMode)
            {
                // Code runs in Blend --> create design time data.
                _dialogs.ShowMessage("Running in design-time mode!", "Injection Constructor", DialogButton.OK, DialogImage.Information);
                _logger.WriteLine("Running in design-time mode!");
            }
            else
            {
                // Code runs "for real"
                _dialogs.ShowMessage("Running in run-time mode!", "Injection Constructor", DialogButton.OK, DialogImage.Information);
                _logger.WriteLine("Running in run-time mode!");
            }
        }

        public override void Cleanup()
        {
            // Clean up if needed
            _dialogs = null;
            _logger = null;

            base.Cleanup();
        }
    }
}

And the OrderViewModel class:

using GalaSoft.MvvmLight;
using Microsoft.Practices.Unity;

namespace MVVMLightUnityExample
{
    /// <summary>
    /// This class contains properties that a View can data bind to.
    /// <para>
    /// Use the <strong>mvvminpc</strong> snippet to add bindable properties to this ViewModel.
    /// </para>
    /// <para>
    /// You can also use Blend to data bind with the tool's support.
    /// </para>
    /// <para>
    /// See http://www.galasoft.ch/mvvm/getstarted
    /// </para>
    /// </summary>
    public class OrderViewModel : ViewModelBase
    {

        private const string testOrderNumber = "123456";
        private Order _order;

        /// <summary>
        /// Initializes a new instance of the OrderViewModel class.
        /// </summary>
        public OrderViewModel()
            : this(testOrderNumber)
        {

        }

        /// <summary>
        /// Initializes a new instance of the OrderViewModel class.
        /// </summary>
        public OrderViewModel(string orderNumber)
        {
            if (IsInDesignMode)
            {
                // Code runs in Blend --> create design time data.
                _order = new Order(orderNumber, "My Company", "Our Address");
            }
            else
            {
                _order = GetOrder(orderNumber);
            }
        }

        public override void Cleanup()
        {
            // Clean own resources if needed
            _order = null;

            base.Cleanup();
        }
    }
}

And the code that could be used to display an order view for a specific order:

public void ShowOrder(string orderNumber)
{
    // pass the order number to show to ViewModelLocator to be injected
    //into the constructor of the OrderViewModel instance
    ViewModelLocator.OrderToShow = orderNumber;

    View.OrderView orderView = new View.OrderView();
}

These examples have been stripped down to show only the IoC ideas. It took a lot of trial and error, searching the internet for examples, and finding out that the Unity 2.0 documentation is lacking (in sample code for extensions) to come up with this solution. Let me know if you think it could be improved.

like image 640
acordner Avatar asked Dec 22 '10 19:12

acordner


1 Answers

First, you should avoid service locator anti-pattern.

Second, you have to set a static variable every time you want to get an OrderView? It's a very ugly design.

like image 102
onof Avatar answered Sep 29 '22 09:09

onof