How to use autofac in an UWP app?

后端 未结 4 754
伪装坚强ぢ
伪装坚强ぢ 2021-02-04 18:58

I am using autofac in an UWP application. In my App instance, I am setting up the dependency, like this:

public sealed partial class App
{
   privat         


        
4条回答
  •  一向
    一向 (楼主)
    2021-02-04 19:31

    Because UWP is responsible for the Page's instantiation it removes the ability to explicitly inject dependencies into views.

    The simplest approach would be to have an accessible service locator and register your dependencies with it.

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

    And then resolve dependencies as needed in the views.

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

    Another more complicated way would be to use a convention base approach and tapping into the application's Frame navigation.

    The plan would be to subscribe to the NavigatedTo event

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

    and once there using the navigation argument to pass the view model type to be resolved and data bind to the view.

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

    The Frame is accessed via a wrapper service that extracts it from the current window

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

    And the following extension classes provide utility support

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

    You could configure the application like you would before at start up but the container will be used to initialize the navigation service and it will handle the 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);
            }
        }
    
       //...
    }
    

    By using the above convention the Frame extensions allow some generic navigation.

    ContentFrame.Navigate();
    

    Views can be as simple as

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

    As the navigation service would also set the data context of the view after navigation.

    Navigation can also be initiated by view models that have the INavigationService injected

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

    提交回复
    热议问题