Come utilizzare autofac in un'app UWP?

Come utilizzare autofac in un'app UWP?

Perché UWP è responsabile per Page L'istanza di 's rimuove la possibilità di inserire esplicitamente dipendenze nelle viste.

L'approccio più semplice sarebbe avere un localizzatore di servizi accessibile e registrare le tue dipendenze con esso.

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<LoggingService>()
                       .As<ILoggingService>()
                       .SingleInstance();

        containerBuilder.RegisterType<SQLitePlatformService>()
                       .As<ISQLitePlatformService>()
                       .SingleInstance();

        containerBuilder.RegisterType<DiskStorageService>()
                       .As<IDiskStorageService>()
                       .SingleInstance();

        containerBuilder.RegisterType<Facade>()
                       .As<IFacade>();

        //...Register ViewModels as well

        containerBuilder.RegisterType<SomePageViewModel>()
            .AsSelf();

        //...

        var container = containerBuilder.Build();
        return container;
   }

   //...
}

E poi risolvi le dipendenze secondo necessità nelle viste.

internal sealed partial class SomePage {

    public SomePage() {
        InitializeComponent();
        ViewModel = App.Container.Resolve<SomePageViewModel>();
        this.DataContext = ViewModel;
    }

    public SomePageViewModel ViewModel { get; private set; }

    protected override void OnNavigatedTo(NavigationEventArgs e) {
        ViewModel.LoadAsync();
        base.OnNavigatedTo(e);
    }
}

Un altro modo più complicato sarebbe utilizzare un approccio basato su convenzioni e attingere alla navigazione Frame dell'applicazione.

Il piano sarebbe iscriversi al NavigatedTo evento

public interface INavigationService {
    bool Navigate<TView>() where TView : Page;
    bool Navigate<TView, TViewModel>(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<TView>() where TView : Page {
        return frame.Navigate(typeof(TView));
    }

    public bool Navigate<TView, TViewModel>(object parameter = null) where TView : Page {
        var context = new NavigationContext(typeof(TViewModel), parameter);
        return frame.Navigate(typeof(TView), context);
    }
}

e una volta lì, utilizzare l'argomento di navigazione per passare il tipo di modello di visualizzazione da risolvere e associare i dati alla visualizzazione.

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

Il Frame si accede tramite un servizio wrapper che lo estrae dalla finestra corrente

public interface IFrameProvider {
    Frame CurrentFrame { get; }
}

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

E le seguenti classi di estensione forniscono supporto di utilità

public static class ServiceProviderExtension {
    /// <summary>
    /// Get service of type <typeparamref name="TService"/> from the <see cref="IServiceProvider"/>.
    /// </summary>
    public static TService GetService<TService>(this IServiceProvider provider) {
        return (TService)provider.GetService(typeof(TService));
    }
    /// <summary>
    /// Get an enumeration of services of type <paramref name="serviceType"/> from the <see cref="IServiceProvider"/>
    /// </summary>
    public static IEnumerable<object> GetServices(this IServiceProvider provider, Type serviceType) {
        var genericEnumerable = typeof(IEnumerable<>).MakeGenericType(serviceType);
        return (IEnumerable<object>)provider.GetService(genericEnumerable);
    }
    /// <summary>
    /// Get an enumeration of services of type <typeparamref name="TService"/> from the <see cref="IServiceProvider"/>.
    /// </summary>
    public static IEnumerable<TService> GetServices<TService>(this IServiceProvider provider) {
        return provider.GetServices(typeof(TService)).Cast<TService>();
    }
    /// <summary>
    /// Get service of type <paramref name="serviceType"/> from the <see cref="IServiceProvider"/>.
    /// </summary>
    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;
    }
    /// <summary>
    /// Get service of type <typeparamref name="T"/> from the <see cref="IServiceProvider"/>.
    /// </summary>
    public static T GetRequiredService<T>(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<TView>(this Frame frame) where TView : Page {
        return frame.Navigate(typeof(TView));
    }

    public static bool Navigate<TView, TViewModel>(this Frame frame, object parameter = null) where TView : Page {
        var context = new NavigationContext(typeof(TViewModel), parameter);
        return frame.Navigate(typeof(TView), context);
    }
}

Puoi configurare l'applicazione come faresti prima all'avvio, ma il contenitore verrà utilizzato per inizializzare il servizio di navigazione e si occuperà del resto.

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<DefaultFrameProvider>()
            .As<IFrameProvider>()
            .SingleInstance();

        containerBuilder.RegisterType<ViewModelBinder>()
            .As<IViewModelBinder>()
            .SingleInstance();

        containerBuilder.RegisterType<AutofacServiceProvider>()
            .As<IServiceProvider>()

        containerBuilder.RegisterType<NavigationService>()
            .AsSelf()
            .As<INavigationService>();


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

        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<SomePage, SomePageViewModel>();
            }
            // Ensure the current window is active
            Window.Current.Activate();
        }
    }

    public class AutofacServiceProvider : IServiceProvider
        public object GetService(Type serviceType) {
            return App.Container.Resolve(serviceType);
        }
    }

   //...
}

Utilizzando la convenzione di cui sopra, il Frame le estensioni consentono una navigazione generica.

ContentFrame.Navigate<SomeOtherPage, SomeOtherPageViewModel>();

Le visualizzazioni possono essere semplici come

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

Poiché il servizio di navigazione imposterebbe anche il contesto dei dati della vista dopo la navigazione.

La navigazione può essere avviata anche visualizzando i modelli che hanno il INavigationService iniettato

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

    //...
}