How to use autofac in an UWP app?

后端 未结 4 753
伪装坚强ぢ
伪装坚强ぢ 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:51

    Instead of using a smelly locator or some other hack to intercept the frame's navigation, I recommend to avoid the Frame as content host and also avoid Page as content.

    In WPF, the recommended view management should be based on the view-model-first pattern. Also in WPF it is not recommended to use the heavy Frame as content host. Both recommendations also apply to UWP.

    We should view a UWP application as single-page application (SPA) in terms of pages based on the Page class and the hosting root frame.

    This means MainPage.xaml is only used as host for our custom page system, which is based on a ContentControl, a set of page view models and a set of DataTemplate definition for each page view model and a PageNavigationViewModel class that controls the page navigation. MainPage (or Page) is the equivalent to the WPF Window: the element tree root or visual host.

    The pattern

    The following example shows the pattern by seetting up a basic two page application (a landing page and a settings page) using dependency injection with Autofac (which of course can be replaced with any other IoC framework).
    The important detail is not the IoC framework or the IoC container configuration, but the way the application is structured to allow page navigation combined with dependency injection.

    The Goal
    The goal is to display a LandingPage view (UserControl) based on a LandingPageViewModel and a SettingsPage view based on a SettingsPageViewModel.
    All view model instances are created by the IoC container, while all associated views are instantiated implicitly by the UWP framework using DataTemplate.

    The example is structured into three sections:

    1. Setting up the navigation infrastructure
    2. Creating the pages/views
    3. Bootstrapping the application

    There is some extra complexity like factories introduced due to depndency injection. In a real world example, we would depend on interfaces instead of concrete implementations (Dependencsy Inversion principle). For simplicity there are no interfaces used except those that are relevant to the infrastructure.


    1 Setting up the navigation infrastructure

    PageId.cs
    Each view is identified by an enum. This makes selecting a page model e.g., via command parameter and refactoring easier. It also eliminates magic strings.

    public enum PageId
    {
      Undefined = 0,
      LandingPage,
      SettingsPage
    }
    

    Factory delegates (Autofac specific details)
    The delegates are required to allow Autofac to create factories. Other IoC frameworks may h ave a different requirement to generate factories. MEF for example uses the ExportFactory type as constructor depndency. The framework would then automatically generate the appropriate factory.

    IoC generated factories allow dynamic type creation, where the instances are wired up according to the IoC container configuration. Factories are used to avoid passing around a reference to the IoC container (or even worse making the container a Singleton). Such practice is an anti-pattern, which contradicts the use of the IoC container.

    The delagates should be added to the common namespace of the PageIndexFactory and Bootstrapper classes (see below) and marked as internal.

    private LandingPageModelFactory LandingPageModelFactory { get; }
    private SettingsPageModelFactory SettingsPageModelFactory { get; }
    

    PageIndexFactory.cs
    The individual page view models are initialized by a PageIndexFactory, which is injected into the PageNavigationViewModel. The purpose is to create the navigation index. PageIndexFactory makes use of delegate factories.
    Every serious IoC framework supports auto-generation of factories. This way the IoC container is still able to wire up the dependencies (note that passing around the original IoC container instance is an anti-pattern).

    public class PageIndexFactory
    {
      private LandingPageModelFactory LandingPageModelFactory { get; }
      private SettingsPageModelFactory SettingsPageModelFactory { get; }
    
      public PageIndexFactory(LandingPageModelFactory landingPageModelFactory,
        SettingsPageModelFactory settingsPageModelFactory)
      {
        this.LandingPageModelFactory = landingPageModelFactory;
        this.SettingsPageModelFactory = settingsPageModelFactory;
      }
    
      public Dictionary CreateIndex()
      {
        var index = new Dictionary()
        {
          {PageId.LandingPage, this.LandingPageModelFactory.Invoke()},
          {PageId.SettingsPage, this.SettingsPageModelFactory.Invoke()}
        };
        return index;
      }
    }
    

    PageNavigationViewModel.cs
    This is the view model that handles the navigation. It exposes a SelectViewCommand, which can be assigned to an ICommandSource like a Button. The CommandParameter must be the PageId, which actually selects the IPageModel from the page index. The PageNavigationViewModel is assigned to the application's original MainPage, which is the host of the custom navigation infrastructure.

    The PageNavigationViewModel exposes a SelectedView property which holds a view model e.g., IPageModel. This property is bound to the hosting ContentControl.Content property.

    public class PageNavigationViewModel : INotifyPropertyChanged
    {
      public PageNavigationViewModel(PageIndexFactory pageIndexFactory)
      {
        this.PageIndex = pageIndexFactory.CreateIndex();
        if (this.PageIndex.TryGetValue(PageId.LandingPage, out IPageModel welcomePageModel))
        {
          this.SelectedView = welcomePageModel;
        }
      }
    
      private void ExecuteSelectPage(object commandParameter)
      {
        var pageId = (PageId) commandParameter;
        if (this.PageIndex.TryGetValue(pageId, out IPageModel selectedPageModel))
        {
          this.SelectedView = selectedPageModel;
        }
      }
    
      public ICommand SelectViewCommand => new RelayCommand(ExecuteSelectPage);
    
      private Dictionary PageIndex { get; }
    
      private IPageModel selectedView;   
      public IPageModel SelectedView
      {
        get => this.selectedView;
        set 
        { 
          this.selectedView = value; 
          OnPropertyChanged();
        }
      }
    }
    

    IPageModel.cs
    The interface, which must be implemented by the individual page view models.

    public interface IPageModel : INotifyPropertyChanged
    {
      string PageTitle { get; }
    }
    

    INavigationHost.cs
    This interface is implemented by the application Page host e.g., MainPage. It allows to assign the PageNavigationViewModel anonymously.

    interface INavigationHost
    {
      PageNavigationViewModel NavigationViewModel { get; set; }
    }
    

    MainPage.xaml.cs
    The host of the custom navigation infrastructure. This instance is created via reflection by the hosting Frame. We use the implementation of INavigationHost to initialze this class with an instance of PageNavigationviewModel (see App.xaml.cs* below).
    MainPage is the only class that is not instantiated by the IoC container. As this class has no reponsibilities, except exposing the PageNavigationViewModel and hosting the ContentControl (to host the real pages/views), it will have no relevant dependencies.

    public sealed partial class MainPage : Page, INavigationHost
    {
      private PageNavigationViewModel navigationViewModel;
    
      public PageNavigationViewModel NavigationViewModel
      {
        get => this.navigationViewModel;
        set
        {
          this.navigationViewModel = value;
          this.DataContext = this.NavigationViewModel;
        }
      }
    
      public MainPage()
      {
        this.InitializeComponent();
      }
    }
    

    MainPage.xaml
    Hosts the real view and the DataTemplateSelector for the ContentControl. host and optionally the navigation elements like navigation buttons.

    The ContentControl loads the DataTemplate, that is associated with the IPageModel instance. This DataTemplate contains e.g., a UserControl, which hosts the actual page content.

    The DataContext of each view is set by the ContentControl and is the current Content (which is the PageNavigationViewModel.SelectedView).

    
      
        
          
            
              
            
    
            
              
            
          
        
      
    
      
    
        
        
        
    
        
        
      
    
    

    PageTemplateSelector.cs
    UWP does not support implicit data templates like WPF. Therefore we have to use a template selector that is assigned to the hosting ContentControl. ContentControl.Content will hold the PageNavigationViewModel.SelectedViw view model and the PageTemplateSelector will select the matching DataTemplate. Since DataTemplate has not DataType property (opposed to the WPF version), we have to introduce an attached property to hoöd this value. Note, that x:DataType is a compiler directive and not accessible by code.

    public class PageTemplateSelector : DataTemplateSelector
    {
      public DataTemplateCollection DataTemplateCollection { get; set; }
      #region Overrides of DataTemplateSelector
    
      public PageTemplateSelector()
      {
        this.DataTemplateCollection = new DataTemplateCollection();
      }
    
      /// 
      protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
      {
        if (item != null
            && this.DataTemplateCollection.First(template => Element.GetDataType(template) == item.GetType()) is DataTemplate dataTemplate)
        {
          return dataTemplate;
        }
    
        return base.SelectTemplateCore(item, container);
      }
    
      /// 
      protected override DataTemplate SelectTemplateCore(object item)
      {
        if (item != null
            && this.DataTemplateCollection.First(template => Element.GetDataType(template) == item.GetType()) is
              DataTemplate dataTemplate)
        {
          return dataTemplate;
        }
    
        return base.SelectTemplateCore(item);
      }
    
      #endregion
    }
    

    DataTemplateCollection.cs
    The XAML collection to hold the page view DataTemples definitions. It is used by the PageTemplateSelector.

    public class DataTemplateCollection : List
    {}
    

    Element.cs
    Class that defines the attached DataType property, which is used by the PageTemplateSelector to filter the DataTemplate definitions. This attached property should be therefore set on every DataTemplate that is associated with a page view model.

    public class Element : DependencyObject
    {
      #region Type attached property
    
      public static readonly DependencyProperty DataTypeProperty = DependencyProperty.RegisterAttached(
        "DataType",
        typeof(Type),
        typeof(Element),
        new PropertyMetadata(default(Type)));
    
      public static void SetDataType([NotNull] DependencyObject attachingElement, Type value) => attachingElement.SetValue(Element.DataTypeProperty, value);
    
      public static Type GetDataType([NotNull] DependencyObject attachingElement) =>
        (Type) attachingElement.GetValue(Element.DataTypeProperty);
    
      #endregion
    }
    

    2 Creating the pages/views

    All dependencies are resolved by the IoC container. The PageIndexfactory controls the instantiation via auto-generated factories.

    To keep it short, the following example implementation only shows the LandingPage related UserControl and LandingPageViewModel. The same pattern applies to the SettingsPage and SettingsPageViewModel.

    LandingPageViewModel.cs
    The view model for the welcome view.

    public class LandingPageViewModel : IPageModel, INotifyPropertyChanged
    {
      public LandingPageViewModel(IFacade someExampleDependency)
      {
        this.Facade = someExampleDependency;
        this.PageTitle = "Welcome Page";
      }
    
      public string PageTitle { get; }
      private string IFacade Facade { get; }
    }
    

    LandingPage.xaml.cs
    The DataContext is set implicitly by the hosting ContentControl.

    public sealed partial class LandingPage : UserControl
    {
      // Enable x:Bind
      public LandingPageViewModel ViewModel { get; private set; }
    
      public LandingPage()
      {
        this.InitializeComponent();
    
        // Delegate the DataContext to the ViewModel property to enable x:Bind
        this.DataContextChanged += OnDataContextChanged;
      }
    
      private void OnDataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
      {
        this.ViewModel = args.NewValue as LandingPageViewModel;
      }
    }
    

    LandingPage.xaml
    The view that is displayed via DataTemplate by the ContentControl.

        
      
        
      
    
    

    3 Bootstrapping the application

    The following section shows how to set up the IoC container and bootstrap the UWP application.

    Bootstrapper.cs
    Encapsulates the IoC container configuration and bootstrapping of the application.

    internal sealed class Bootstrapper
    {
      internal static void InitializeApplication(INavigationHost navigationHost)
      {
        // Don't use the dependency container outside this class
        using (IContainer services = Bootstrapper.BuildDependencies())
        {
          PageNavigationViewModel navigationViewModel = services.Resolve();
          navigationHost.NavigationViewModel = navigationViewModel;
        }
      }
    
      internal static IContainer BuildDependencies()
      {
        var builder = new ContainerBuilder();
        builder.RegisterType();
        builder.RegisterType();
        builder.RegisterType();
        builder.RegisterType();
    
        // Dependency to address your question
        builder.RegisterType().As();
    
        // Don't forget to dispose the IContainer instance (caller's responsibility)
        return builder.Build();
      }
    }
    

    App.xaml.cs
    Bootstarpping the UWP application.

    sealed partial class App : Application
    {
        /// 
        /// Initializes the singleton application object.  This is the first line of authored code
        /// executed, and as such is the logical equivalent of main() or WinMain().
        /// 
        public App()
        {
            this.InitializeComponent();
            this.Suspending += OnSuspending;
        }
    
        /// 
        /// Invoked when the application is launched normally by the end user.  Other entry points
        /// will be used such as when the application is launched to open a specific file.
        /// 
        /// Details about the launch request and process.
        protected override void OnLaunched(LaunchActivatedEventArgs e)
        {
          Frame rootFrame = Window.Current.Content as Frame;
    
          // Do not repeat app initialization when the Window already has content,
          // just ensure that the window is active
          if (rootFrame == null)
          {
            // Create a Frame to act as the navigation context and navigate to the first page
            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;
          }
    
          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(typeof(MainPage), e.Arguments);
    
    /****************** Build depndencies and initialize navigation *************/
    
              if (rootFrame.Content is INavigationHost navigationHost)
              {
                Bootstrapper.InitializeApplication(navigationHost);
              }
            }
    
            // Ensure the current window is active
            Window.Current.Activate();
          }
        }
    
        ...
    }
    

提交回复
热议问题