Comment utiliser l’autofac dans une application UWP?

J’utilise autofac dans une application UWP. Dans mon exemple d’ App , je configure la dépendance, comme ceci:

 public sealed partial class App { private readonly IFacade m_facade; public App() { InitializeComponent(); m_facade = InitializeDependencies(); Suspending += OnSuspending; } private IFacade InitializeDependencies() { var containerBuilder = new ContainerBuilder(); // Registers all the platform-specific implementations of services. containerBuilder.RegisterType() .As() .SingleInstance(); containerBuilder.RegisterType() .As() .SingleInstance(); containerBuilder.RegisterType() .As() .SingleInstance(); ... containerBuilder.RegisterType() .As(); // Auto-magically resolves the IFacade implementation. var facadeContainer = containerBuilder.Build(); var facadeLifetimeScope = m_facadeContainer.BeginLifetimeScope(); return facadeLifetimeScope.Resolve(); } } 

Je dois transmettre mon instance IFacade aux différentes Page pour atteindre mes modèles de vues. Voici un exemple d’une de mes pages:

 internal sealed partial class SomePage { public SomePageViewModel ViewModel { get; } public SomePage() { ViewModel = new SomePageViewModel(/* need an IFacade implementation here!! */); InitializeComponent(); } protected override void OnNavigatedTo(NavigationEventArgs e) { ViewModel.LoadAsync(); base.OnNavigatedTo(e); } } 

UWP est responsable de l’instanciation de la Page ce qui limite mes options. Voici comment se fait la navigation d’une page à l’autre dans UWP. Depuis l’instance de l’ App :

 rootFrame.Navigate(typeof(MainPage), e.Arguments); 

Ou à partir d’un exemple de Page :

 ContentFrame.Navigate(typeof(SomeOtherPage)); 

Question

Quel serait le bon moyen de transmettre mon instance d’ IFacade aux vues-modèles (sans rien faire de mal évidemment)?

UWP étant responsable de l’instanciation de la Page , il n’est plus possible d’injecter explicitement des dépendances dans les vues.

L’approche la plus simple serait d’avoir un localisateur de service accessible et d’enregistrer vos dépendances avec celui-ci.

 public sealed partial class App { public App() { InitializeComponent(); Container = ConfigureServices(); Suspending += OnSuspending; } public static IContainer Container { get; set; } private IContainer ConfigureServices() { var containerBuilder = new ContainerBuilder(); // Registers all the platform-specific implementations of services. containerBuilder.RegisterType() .As() .SingleInstance(); containerBuilder.RegisterType() .As() .SingleInstance(); containerBuilder.RegisterType() .As() .SingleInstance(); containerBuilder.RegisterType() .As(); //...Register ViewModels as well containerBuilder.RegisterType() .AsSelf(); //... var container = containerBuilder.Build(); return container; } //... } 

Puis résolvez les dépendances selon les besoins dans les vues.

 internal sealed partial class SomePage { public SomePage() { InitializeComponent(); ViewModel = App.Container.Resolve(); this.DataContext = ViewModel; } public SomePageViewModel ViewModel { get; private set; } protected override void OnNavigatedTo(NavigationEventArgs e) { ViewModel.LoadAsync(); base.OnNavigatedTo(e); } } 

Une autre méthode plus compliquée consisterait à utiliser une approche conventionnelle et à exploiter la navigation Frame de l’application.

Le plan serait de s’abonner à l’événement NavigatedTo

 public interface INavigationService { bool Navigate() where TView : Page; bool Navigate(object parameter = null) where TView : Page; } public class NavigationService : INavigationService { private readonly Frame frame; private readonly IViewModelBinder viewModelBinder; public NavigationService(IFrameProvider frameProvider, IViewModelBinder viewModelBinder) { frame = frameProvider.CurrentFrame; frame.Navigating += OnNavigating; frame.Navigated += OnNavigated; this.viewModelBinder = viewModelBinder; } protected virtual void OnNavigating(object sender, NavigatingCancelEventArgs e) { } protected virtual void OnNavigated(object sender, NavigationEventArgs e) { if (e.Content == null) return; var view = e.Content as Page; if (view == null) throw new ArgumentException("View '" + e.Content.GetType().FullName + "' should inherit from Page or one of its descendents."); viewModelBinder.Bind(view, e.Parameter); } public bool Navigate() where TView : Page { return frame.Navigate(typeof(TView)); } public bool Navigate(object parameter = null) where TView : Page { var context = new NavigationContext(typeof(TViewModel), parameter); return frame.Navigate(typeof(TView), context); } } 

et une fois là, en utilisant l’argument de navigation pour transmettre le type de modèle de vue à résoudre et les données liées à la vue.

 public interface IViewModelBinder { void Bind(FrameworkElement view, object viewModel); } public class ViewModelBinder : IViewModelBinder { private readonly IServiceProvider serviceProvider; public ViewModelBinder(IServiceProvider serviceProvider) { this.serviceProvider = serviceProvider; } public void Bind(FrameworkElement view, object viewModel) { InitializeComponent(view); if (view.DataContext != null) return; var context = viewModel as NavigationContext; if (context != null) { var viewModelType = context.ViewModelType; if (viewModelType != null) { viewModel = serviceProvider.GetService(viewModelType); } var parameter = context.Parameter; //TODO: figure out what to do with parameter } view.DataContext = viewModel; } static void InitializeComponent(object element) { var method = element.GetType().GetTypeInfo() .GetDeclaredMethod("InitializeComponent"); method?.Invoke(element, null); } } 

On accède à Frame via un service d’encapsuleur qui l’extrait de la fenêtre en cours

 public interface IFrameProvider { Frame CurrentFrame { get; } } public class DefaultFrameProvider : IFrameProvider { public Frame CurrentFrame { get { return (Window.Current.Content as Frame); } } } 

Et les classes d’extension suivantes fournissent un support utilitaire

 public static class ServiceProviderExtension { ///  /// Get service of type  from the . ///  public static TService GetService(this IServiceProvider provider) { return (TService)provider.GetService(typeof(TService)); } ///  /// Get an enumeration of services of type  from the  ///  public static IEnumerable GetServices(this IServiceProvider provider, Type serviceType) { var genericEnumerable = typeof(IEnumerable<>).MakeGenericType(serviceType); return (IEnumerable)provider.GetService(genericEnumerable); } ///  /// Get an enumeration of services of type  from the . ///  public static IEnumerable GetServices(this IServiceProvider provider) { return provider.GetServices(typeof(TService)).Cast(); } ///  /// Get service of type  from the . ///  public static object GetRequiredService(this IServiceProvider provider, Type serviceType) { if (provider == null) { throw new ArgumentNullException("provider"); } if (serviceType == null) { throw new ArgumentNullException("serviceType"); } var service = provider.GetService(serviceType); if (service == null) { throw new InvalidOperationException(ssortingng.Format("There is no service of type {0}", serviceType)); } return service; } ///  /// Get service of type  from the . ///  public static T GetRequiredService(this IServiceProvider provider) { if (provider == null) { throw new ArgumentNullException("provider"); } return (T)provider.GetRequiredService(typeof(T)); } } public class NavigationContext { public NavigationContext(Type viewModelType, object parameter = null) { ViewModelType = viewModelType; Parameter = parameter; } public Type ViewModelType { get; private set; } public object Parameter { get; private set; } } public static class NavigationExtensions { public static bool Navigate(this Frame frame) where TView : Page { return frame.Navigate(typeof(TView)); } public static bool Navigate(this Frame frame, object parameter = null) where TView : Page { var context = new NavigationContext(typeof(TViewModel), parameter); return frame.Navigate(typeof(TView), context); } } 

Vous pouvez configurer l’application de la même manière qu’au démarrage, mais le conteneur servira à initialiser le service de navigation et se chargera du rest.

 public sealed partial class App { public App() { InitializeComponent(); Container = ConfigureServices(); Suspending += OnSuspending; } public static IContainer Container { get; set; } private IContainer ConfigureServices() { //... code removed for brevity containerBuilder .RegisterType() .As() .SingleInstance(); containerBuilder.RegisterType() .As() .SingleInstance(); containerBuilder.RegisterType() .As() containerBuilder.RegisterType() .AsSelf() .As(); var container = containerBuilder.Build(); return container; } protected override void OnLaunched(LaunchActivatedEventArgs e) { Frame rootFrame = Window.Current.Content as Frame; if (rootFrame == null) { rootFrame = new Frame(); rootFrame.NavigationFailed += OnNavigationFailed; if (e.PreviousExecutionState == ApplicationExecutionState.Terminated) { //TODO: Load state from previously suspended application } // Place the frame in the current Window Window.Current.Content = rootFrame; } //Activating navigation service var service = Container.Resolve(); if (e.PrelaunchActivated == false) { if (rootFrame.Content == null) { // When the navigation stack isn't restored navigate to the first page, // configuring the new page by passing required information as a navigation // parameter rootFrame.Navigate(); } // Ensure the current window is active Window.Current.Activate(); } } public class AutofacServiceProvider : IServiceProvider public object GetService(Type serviceType) { return App.Container.Resolve(serviceType); } } //... } 

En utilisant la convention ci-dessus, les extensions Frame permettent une navigation générique.

 ContentFrame.Navigate(); 

Les vues peuvent être aussi simples que

 internal sealed partial class SomePage { public SomePage() { InitializeComponent(); } public SomePageViewModel ViewModel { get { return (SomePageViewModel)DataContext;} } protected override void OnNavigatedTo(NavigationEventArgs e) { ViewModel.LoadAsync(); base.OnNavigatedTo(e); } } 

Comme le service de navigation définirait également le contexte de données de la vue après la navigation.

La navigation peut également être lancée par les modèles de vue ayant INavigationService injecté

 public class SomePageViewModel : ViewModel { private readonly INavigationService navigation; private readonly IFacade facade; public SomePageViewModel(IFacade facade, INavigationService navigation) { this.navigation = navigation; this.facade = facade; } //... public void GoToSomeOtherPage() { navigation.Navigate(); } //... }