📜 ⬆️ ⬇️

Windows Phone + Caliburn.micro + Autofac

Recently, I started experimenting with developing for Windows Phone. I already have some experience developing under WPF and Silverlight using the MVVM template and IoC container, so this task does not present much difficulty for me. As it turned out, my favorite MVVM framework Caliburn.Micro (CM) and Autofac's favorite IoC container support this platform. I rolled up the sleeves and began to form the skeleton of the application. CM actively uses the IoC container for its work. You can use either the built-in container (SimpleContainer), or a number of others, for which there are adapters for CM. There is such an adapter for Autofac - Caliburn. Micro. Autofac, which I have already used for WPF applications. This is great, but a barrel of honey, as usual, spoiled a fly in the ointment - the Autofac adapter for CM 1.3.1 (current version) does not work on Windows Phone. Until recently, it was not even compiled, but its author David Buksbaum three weeks ago corrected this shortcoming. However, anyway, applications with its use do not work. Apparently David does not develop applications for Windows Phone himself, and this version remains unfinished. I didn’t want to give up my favorite bundle and had to write this adapter myself. Today I present to you this implementation.

I had to solve the following problems:


The final versions of AutofacBootstrapper and AutofacPhoneContainer are listed below, I sent them to the author some time ago, but for some reason he did not integrate them into his project, there is simply an idle version (in terms of storing data in isolated storage and the state of the program). A test project can be downloaded from this link .
Along the way, it's worth noting that I found an unpleasant bug in the current version of CM: the values ​​of the fields that should be stored in isolated storage are not saved in all scenarios. Building a CM in my test project is already free from this flaw. I created a bug report on the CM website describing this problem. Hopefully, it will be fixed soon.

AutofacBootstrapper
// Project: Caliburn.Micro.Autofac // File name: AutofacBootstrapper.cs // File GUID: C5C743E2-7AD1-496C-824A-9AEFBF5F8979 // Authors: David Buksbaum (david@buksbaum.us), Mike Eshva (mike@eshva.ru) // Date of creation: 20.05.2012 using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Reflection; using System.Windows.Controls; using Autofac; using Microsoft.Phone.Controls; using IContainer = Autofac.IContainer; namespace Caliburn.Micro.Autofac { /// <summary> /// Autofac specific <see cref="PhoneBootstrapper"/> realization. /// </summary> public class AutofacBootstrapper : PhoneBootstrapper { #region Public properties /// <summary> /// Gets or sets flag should the namespace convention be enforced for type registration. The /// default is true. For views, this would require a views namespace to end with Views For /// view-models, this would require a view models namespace to end with ViewModels. /// </summary> /// <remarks> /// Case is important as views would not match. /// </remarks> public bool EnforceNamespaceConvention { get; set; } /// <summary> /// Gets or sets flag should the view be treated as loaded when registering the /// <see cref="INavigationService"/>. /// </summary> public bool TreatViewAsLoaded { get; set; } /// <summary> /// Gets or sets the base type required for a view model. /// </summary> public Type ViewModelBaseType { get; set; } /// <summary> /// Gets or sets method for creating the window manager. /// </summary> public Func<IWindowManager> CreateWindowManager { get; set; } /// <summary> /// Gets or sets method for creating the event aggregator. /// </summary> public Func<IEventAggregator> CreateEventAggregator { get; set; } /// <summary> /// Gets or sets method for creating the frame adapter. /// </summary> public Func<FrameAdapter> CreateFrameAdapter { get; set; } /// <summary> /// Gets or sets method for creating the phone application service adapter. /// </summary> public Func<PhoneApplicationServiceAdapter> CreatePhoneApplicationServiceAdapter { get; set; } /// <summary> /// Gets or sets method for creating the vibrate controller. /// </summary> public Func<IVibrateController> CreateVibrateController { get; set; } /// <summary> /// Gets or sets method for creating the sound effect player. /// </summary> public Func<ISoundEffectPlayer> CreateSoundEffectPlayer { get; set; } #endregion #region Protected interface /// <summary> /// Gets Autofac container instance. /// </summary> protected IContainer Container { get; private set; } /// <summary> /// Do not override this method. This is where the IoC container is configured. /// </summary> /// <exception cref="NullReferenceException"> /// Either CreateFrameAdapter or CreateWindowManager or CreateEventAggregator or /// CreatePhoneApplicationServiceAdapter or CreateVibrateController or /// CreateSoundEffectPlayer is null. /// </exception> protected override void Configure() { // allow base classes to change bootstrapper settings ConfigureBootstrapper(); // validate settings if (CreateFrameAdapter == null) { throw new NullReferenceException("CreateFrameAdapter is not specified."); } if (CreateWindowManager == null) { throw new NullReferenceException("CreateWindowManager is not specified."); } if (CreateEventAggregator == null) { throw new NullReferenceException("CreateEventAggregator is not specified."); } if (CreatePhoneApplicationServiceAdapter == null) { throw new NullReferenceException( "CreatePhoneApplicationServiceAdapter is not specified."); } if (CreateVibrateController == null) { throw new NullReferenceException("CreateVibrateController is not specified."); } if (CreateSoundEffectPlayer == null) { throw new NullReferenceException("CreateSoundEffectPlayer is not specified."); } // Configure container. ContainerBuilder lBuilder = new ContainerBuilder(); // Register phone services. Assembly lCaliburnAssembly = typeof (IStorageMechanism).Assembly; // Register IStorageMechanism implementors. lBuilder.RegisterAssemblyTypes(lCaliburnAssembly) .Where( aType => typeof (IStorageMechanism).IsAssignableFrom(aType) && !aType.IsAbstract && !aType.IsInterface) .As<IStorageMechanism>() .InstancePerLifetimeScope(); // Register IStorageHandler implementors. lBuilder.RegisterAssemblyTypes(AssemblySource.Instance.ToArray()) .Where( aType => typeof (IStorageHandler).IsAssignableFrom(aType) && !aType.IsAbstract && !aType.IsInterface) .As<IStorageHandler>() .InstancePerLifetimeScope(); // Register view models. lBuilder.RegisterAssemblyTypes(AssemblySource.Instance.ToArray()) // Must be a type with a name that ends with ViewModel. .Where(aType => aType.Name.EndsWith("ViewModel")) // Mmust be in a namespace ending with ViewModels. .Where( aType => !EnforceNamespaceConvention || (!string.IsNullOrEmpty(aType.Namespace) && aType.Namespace.EndsWith("ViewModels"))) // Must implement INotifyPropertyChanged (deriving from PropertyChangedBase will statisfy this). .Where(aType => aType.GetInterface(ViewModelBaseType.Name, false) != null) // Registered as self. .AsSelf() // Subscribe on Activated event for viewmodels to make storage mechanism work. .OnActivated(aArgs => ActivateInstance(aArgs.Instance)) // Always create a new one. .InstancePerDependency(); // Register views. lBuilder.RegisterAssemblyTypes(AssemblySource.Instance.ToArray()) // Must be a type with a name that ends with View. .Where(aType => aType.Name.EndsWith("View")) // Must be in a namespace that ends in Views. .Where( aType => !EnforceNamespaceConvention || (!string.IsNullOrEmpty(aType.Namespace) && aType.Namespace.EndsWith("Views"))) // Registered as self. .AsSelf() // Always create a new one. .InstancePerDependency(); // Register the singletons. lBuilder.Register<IPhoneContainer>( aContext => new AutofacPhoneContainer(aContext.Resolve<IComponentContext>())) .SingleInstance(); lBuilder.RegisterInstance<INavigationService>(CreateFrameAdapter()) .SingleInstance(); PhoneApplicationServiceAdapter lPhoneService = CreatePhoneApplicationServiceAdapter(); lBuilder.RegisterInstance<IPhoneService>(lPhoneService) .SingleInstance(); lBuilder.Register(aContext => CreateEventAggregator()) .SingleInstance(); lBuilder.Register(aContext => CreateWindowManager()) .SingleInstance(); lBuilder.Register(aContext => CreateVibrateController()) .SingleInstance(); lBuilder.Register(aContext => CreateSoundEffectPlayer()) .SingleInstance(); lBuilder.RegisterType<StorageCoordinator>().AsSelf() .SingleInstance(); lBuilder.RegisterType<TaskController>().AsSelf() .SingleInstance(); // Allow derived classes to add to the container. ConfigureContainer(lBuilder); // Build the container Container = lBuilder.Build(); // Get the phone container instance. PhoneContainer = (AutofacPhoneContainer) Container.Resolve<IPhoneContainer>(); // Start the storage coordinator. StorageCoordinator lStorageCoordinator = Container.Resolve<StorageCoordinator>(); lStorageCoordinator.Start(); // Start the task controller. TaskController lTaskController = Container.Resolve<TaskController>(); lTaskController.Start(); // Add custom conventions for the phone. AddCustomConventions(); } /// <summary> /// Do not override unless you plan to full replace the logic. This is how the framework /// retrieves services from the Autofac container. /// </summary> /// <param name="aService"> /// The service to locate. /// </param> /// <param name="aKey"> /// The key to locate. /// </param> /// <returns> /// The located service. /// </returns> /// <exception cref="Exception"> /// Could not locate any instances of service. /// </exception> protected override object GetInstance(Type aService, string aKey) { object lInstance; if (string.IsNullOrEmpty(aKey)) { if (Container.TryResolve(aService, out lInstance)) { return lInstance; } } else { if (Container.TryResolveNamed(aKey, aService, out lInstance)) { return lInstance; } } throw new Exception( string.Format( "Could not locate any instances of service {0}.", aKey ?? aService.Name)); } /// <summary> /// Do not override unless you plan to full replace the logic. This is how the framework /// retrieves services from the Autofac container. /// </summary> /// <param name="aService"> /// The service to locate. /// </param> /// <returns> /// The located services. /// </returns> protected override IEnumerable<object> GetAllInstances(Type aService) { IEnumerable<object> lResult = Container.Resolve(typeof (IEnumerable<>).MakeGenericType(aService)) as IEnumerable<object>; return lResult; } /// <summary> /// Do not override unless you plan to full replace the logic. This is how the framework /// retrieves services from the Autofac container. /// </summary> /// <param name="aInstance"> /// The instance to perform injection on. /// </param> protected override void BuildUp(object aInstance) { Container.InjectProperties(aInstance); } /// <summary> /// Override to provide configuration prior to the Autofac configuration. You must call the base version BEFORE any /// other statement or the behaviour is undefined. /// Current Defaults: /// EnforceNamespaceConvention = true /// TreatViewAsLoaded = false /// ViewModelBaseType = <see cref="System.ComponentModel.INotifyPropertyChanged"/> /// CreateWindowManager = <see cref="Caliburn.Micro.WindowManager"/> /// CreateEventAggregator = <see cref="Caliburn.Micro.EventAggregator"/> /// CreateFrameAdapter = <see cref="Caliburn.Micro.FrameAdapter"/> /// CreatePhoneApplicationServiceAdapter = <see cref="Caliburn.Micro.PhoneApplicationServiceAdapter"/> /// CreateVibrateController = <see cref="Caliburn.Micro.SystemVibrateController"/> /// CreateSoundEffectPlayer = <see cref="Caliburn.Micro.XnaSoundEffectPlayer"/> /// </summary> protected virtual void ConfigureBootstrapper() { // By default, enforce the namespace convention. EnforceNamespaceConvention = true; // By default, do not treat the view as loaded. TreatViewAsLoaded = false; // The default view model base type. ViewModelBaseType = typeof (INotifyPropertyChanged); // Default window manager. CreateWindowManager = () => new WindowManager(); // Default event aggregator. CreateEventAggregator = () => new EventAggregator(); // Default frame adapter. CreateFrameAdapter = () => new FrameAdapter(RootFrame, TreatViewAsLoaded); // Default phone application service adapter. CreatePhoneApplicationServiceAdapter = () => new PhoneApplicationServiceAdapter(RootFrame); // Default vibrate controller. CreateVibrateController = () => new SystemVibrateController(); // Default sound effect player. CreateSoundEffectPlayer = () => new XnaSoundEffectPlayer(); } /// <summary> /// Override to include your own Autofac configuration after the framework has finished its /// configuration, but before the container is created. /// </summary> /// <param name="aBuilder"> /// The Autofac configuration builder. /// </param> protected virtual void ConfigureContainer(ContainerBuilder aBuilder) { } /// <summary> /// Activates an instance in phone container. Derived types should call this method for /// registering types that should support storage mechanism. /// </summary> /// <param name="aInstance"> /// Instance to activate. /// </param> /// <remarks> /// Use this method as event handler on service registation in /// <see cref="ConfigureContainer"/> method. ViewModels already registering with using it. /// </remarks> protected void ActivateInstance(object aInstance) { if (PhoneContainer == null) { return; } PhoneContainer.ActivateInstance(aInstance); } #endregion #region Private properties private AutofacPhoneContainer PhoneContainer { get; set; } #endregion #region Private methods private static void AddCustomConventions() { ConventionManager.AddElementConvention<Pivot>( ItemsControl.ItemsSourceProperty, "SelectedItem", "SelectionChanged").ApplyBinding = (aViewModelType, aPath, aProperty, aElement, aConvention) => { if (ConventionManager .GetElementConvention(typeof (ItemsControl)) .ApplyBinding(aViewModelType, aPath, aProperty, aElement, aConvention)) { ConventionManager .ConfigureSelectedItem( aElement, Pivot.SelectedItemProperty, aViewModelType, aPath); ConventionManager .ApplyHeaderTemplate( aElement, Pivot.HeaderTemplateProperty, null, aViewModelType); return true; } return false; }; ConventionManager.AddElementConvention<Panorama>( ItemsControl.ItemsSourceProperty, "SelectedItem", "SelectionChanged").ApplyBinding = (aViewModelType, aPath, aProperty, aElement, aConvention) => { if (ConventionManager .GetElementConvention(typeof (ItemsControl)) .ApplyBinding(aViewModelType, aPath, aProperty, aElement, aConvention)) { ConventionManager .ConfigureSelectedItem( aElement, Panorama.SelectedItemProperty, aViewModelType, aPath); ConventionManager .ApplyHeaderTemplate( aElement, Panorama.HeaderTemplateProperty, null, aViewModelType); return true; } return false; }; } #endregion } } 


')
AutofacBootstrapper
 // Project: Caliburn.Micro.Autofac // File name: AutofacBootstrapper.cs // File GUID: CB807DF7-2D69-4B05-AC17-C3E191D7C182 // Authors: Mike Eshva (mike@eshva.ru) // Date of creation: 20.05.2012 using System; using System.IO.IsolatedStorage; using Autofac; namespace Caliburn.Micro.Autofac { /// <summary> /// Component that allows storage system to communicate with Autofac IoC container. /// </summary> public class AutofacPhoneContainer : IPhoneContainer { #region Constructors /// <summary> /// Initialize new instance of component that allows storage system to communicate with /// Autofac IoC container. /// </summary> /// <param name="aContext"> /// Autofac IoC context. /// </param> /// <exception cref="ArgumentNullException"> /// Autofac IoC context not specified. /// </exception> public AutofacPhoneContainer(IComponentContext aContext) { if (aContext == null) { throw new ArgumentNullException("aContext", "Autofac IoC context is not specified."); } Context = aContext; } #endregion #region IPhoneContainer Members /// <summary> /// Registers the service as a singleton stored in the phone state. /// </summary> /// <param name="aService"> /// The service. /// </param> /// <param name="aPhoneStateKey"> /// The phone state key. /// </param> /// <param name="aImplementation"> /// The implementation. /// </param> public void RegisterWithPhoneService (Type aService, string aPhoneStateKey, Type aImplementation) { string lObjectKey = aPhoneStateKey ?? aService.FullName; IPhoneService lPhoneService = Context.Resolve<IPhoneService>(); if (!lPhoneService.State.ContainsKey(lObjectKey)) { // There is no object with such a key in the phone state. // Create a plain new object and store it the phone state. object lEntireGraphObject = Context.Resolve(aService); lPhoneService.State[lObjectKey] = lEntireGraphObject; // Effectively replace service registration in // Autofac-container (the last registration wins). ContainerBuilder lBuilder = new ContainerBuilder(); lBuilder.RegisterType(aImplementation) .As(aService) .OnActivating( // Replace an object build by the container with one // from the phone state using OnActivating event. aArgs => { IPhoneService lInnerPhoneService = aArgs.Context.Resolve<IPhoneService>(); if (lInnerPhoneService.State.ContainsKey(lObjectKey)) { object lInstance = lInnerPhoneService.State[lObjectKey]; aArgs.ReplaceInstance(lInstance); } }); lBuilder.Update(Context.ComponentRegistry); } } /// <summary> /// Registers the service as a singleton stored in the app settings. /// </summary> /// <param name="aService"> /// The service. /// </param> /// <param name="aPhoneSettingsKey"> /// The app settings key. /// </param> /// <param name="aImplementation"> /// The implementation. /// </param> /// <exception cref="ArgumentNullException"> /// Service or implementation not specified. /// </exception> public void RegisterWithAppSettings (Type aService, string aPhoneSettingsKey, Type aImplementation) { string lObjectKey = aPhoneSettingsKey ?? aService.FullName; if (!IsolatedStorageSettings.ApplicationSettings.Contains(lObjectKey)) { // There is no object with such a key in the application isolated storage. // Probably it is the first time application executed. // Create a plain new object and store it the application isolated storage. object lEntireGraphObject = Context.Resolve(aImplementation); IsolatedStorageSettings.ApplicationSettings[lObjectKey] = lEntireGraphObject; } // Effectively replace service registration in // Autofac-container (the last registration wins). ContainerBuilder lBuilder = new ContainerBuilder(); lBuilder.RegisterType(aImplementation) .As(aService) .OnActivating( // Replace an object build by the container with one // from the application isolated storage using OnActivating event. aArgs => { if (IsolatedStorageSettings.ApplicationSettings.Contains(lObjectKey)) { object lInstance = IsolatedStorageSettings.ApplicationSettings[lObjectKey]; aArgs.ReplaceInstance(lInstance); } }); lBuilder.Update(Context.ComponentRegistry); } /// <summary> /// Occurs when a new instance is created. /// </summary> public event Action<object> Activated = delegate { }; #endregion #region Assembly interface /// <summary> /// Activates <paramref name="aInstance"/>. /// </summary> /// <param name="aInstance"> /// Instance to activate. /// </param> internal void ActivateInstance(object aInstance) { Activated(aInstance); } #endregion #region Private properties private IComponentContext Context { get; set; } #endregion } } 



PS Immediately make a reservation that this is an entry from my blog, and I know about the rule not to make reprints. But the fact is that my blog is not promoted, and the information that I share, in my opinion, can be useful to many. As an excuse for me, I hope, it will be counted that I do not give links to my blog.

Source: https://habr.com/ru/post/145583/


All Articles