如何在UWP应用中使用autofac?

Kzr*_*tof 4 .net c# autofac uwp

我在UWP应用程序中使用autofac。在我的App实例中,我正在建立依赖关系,如下所示:

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>();
   }
}
Run Code Online (Sandbox Code Playgroud)

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

UWP负责Page实例化,因此它限制了我的选择。这是在UWP中完成从一页到另一页的导航的方法。从App实例:

rootFrame.Navigate(typeof(MainPage), e.Arguments);
Run Code Online (Sandbox Code Playgroud)

或从Page实例:

ContentFrame.Navigate(typeof(SomeOtherPage));
Run Code Online (Sandbox Code Playgroud)

题

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

Nko*_*osi 7

由于UWP负责Page的实例化,因此消除了将依赖项显式注入视图的能力。

最简单的方法是拥有一个可访问的服务定位器,并向其注册您的依赖项。

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

   //...
}
Run Code Online (Sandbox Code Playgroud)

然后根据需要在视图中解决依赖关系。

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

另一个更复杂的方法是使用基于约定的方法,然后进入应用程序的“框架”导航。

该计划将订阅该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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

public interface IFrameProvider {
    Frame CurrentFrame { get; }
}

public class DefaultFrameProvider : IFrameProvider {
    public Frame CurrentFrame {
        get {
            return (Window.Current.Content as Frame);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

以下扩展类提供实用程序支持

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以像启动前一样配置应用程序,但是该容器将用于初始化导航服务,其余部分将由它处理。

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

   //...
}
Run Code Online (Sandbox Code Playgroud)

通过使用以上约定,这些Frame扩展允许进行一些常规导航。

ContentFrame.Navigate<SomeOtherPage, SomeOtherPageViewModel>();
Run Code Online (Sandbox Code Playgroud)

视图可以很简单

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

由于导航服务还将在导航后设置视图的数据上下文。

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

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

    //...
}
Run Code Online (Sandbox Code Playgroud)