如何在 UWP 应用中使用 autofac? [英] How to use autofac in an UWP app?

查看:34
本文介绍了如何在 UWP 应用中使用 autofac?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在 UWP 应用程序中使用 autofac.在我的 App 实例中,我正在设置依赖项,如下所示:

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

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

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

       ...

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

       //  Auto-magically resolves the IFacade implementation.
       var facadeContainer = containerBuilder.Build();
       var facadeLifetimeScope = m_facadeContainer.BeginLifetimeScope();

       return facadeLifetimeScope.Resolve<IFacade>();
   }
}

我需要将我的 IFacade 实例传递给不同的 Page 以到达我的视图模型.这是我的一个页面的示例:

I need to pass my IFacade instance around to the different Pages to reach my view-models. Here is an example of one of my 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 负责 Page 的实例化,因此它限制了我的选择.下面是如何在 UWP 中完成从一个页面到另一个页面的导航.从 App 实例:

UWP is responsible for the Pages instantiation so it limits my options. Here is how the navigation from one page to another is done in UWP. From the App instance:

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

或者来自 Page 实例:

ContentFrame.Navigate(typeof(SomeOtherPage));

问题

将我的 IFacade 实例传递给视图模型的正确方法是什么(显然不需要做任何事情)?

What would be the proper way to pass my IFacade instance around to the view-models (without doing anything hacky obviously)?

推荐答案

因为 UWP 负责 Page 的实例化,所以它删除了将依赖项显式注入视图的能力.

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

   //...
}

然后根据需要在视图中解析依赖项.

And then resolve dependencies as needed in the views.

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

另一种更复杂的方法是使用基于约定的方法并利用应用程序的框架导航.

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

计划是订阅NavigatedTo 事件

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

然后使用导航参数传递要解析的视图模型类型并将数据绑定到视图.

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

Frame 通过从当前窗口提取它的包装服务访问

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

您可以像以前一样在启动时配置应用程序,但容器将用于初始化导航服务并处理其余部分.

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

   //...
}

通过使用上述约定,Frame 扩展允许一些通用导航.

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

ContentFrame.Navigate<SomeOtherPage, SomeOtherPageViewModel>();

视图可以很简单

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.

导航也可以由注入了 INavigationService 的视图模型启动

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

    //...
}

这篇关于如何在 UWP 应用中使用 autofac?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆