Объединение MVVM Light Toolkit и Unity 2.0 - PullRequest
7 голосов
/ 22 декабря 2010

Это скорее комментарий, чем вопрос, хотя обратная связь была бы хорошей. Мне было поручено создать пользовательский интерфейс для нового проекта, который мы делаем. Мы хотим использовать WPF, и я хотел изучить все доступные современные методы проектирования пользовательского интерфейса. Поскольку я довольно новичок в WPF, я изучаю то, что доступно. Я думаю, что я в значительной степени остановился на использовании MVVM Light Toolkit (главным образом из-за его "Blendability" и поведения EventToCommand!), Но я хотел также включить IoC. Итак, вот что я придумала. Я изменил класс ViewModelLocator по умолчанию в проекте MVVM Light, чтобы использовать UnityContainer для обработки инъекций зависимостей. Учитывая, что я не знал, что означало 90% этих терминов 3 месяца назад, я думаю, что я на правильном пути.

// 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;
        }
    }
}

И класс MainViewModel, показывающий использование внедрения зависимости:

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();
        }
    }
}

И класс OrderViewModel:

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();
        }
    }
}

И код, который можно использовать для отображения вида заказа для определенного заказа:

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();
}

Эти примеры были урезаны, чтобы показать только идеи IoC. Потребовалось много проб и ошибок, поиск примеров в Интернете и обнаружение отсутствия документации Unity 2.0 (в примере кода для расширений), чтобы найти это решение. Дайте мне знать, если вы думаете, что это может быть улучшено.

1 Ответ

1 голос
/ 23 декабря 2010

Во-первых, вы должны избегать анти-паттерна сервисного локатора.

Во-вторых, вам нужно устанавливать статическую переменную каждый раз, когда вы хотите получить OrderView?Это очень уродливый дизайн.

...