简体   繁体   中英

Prism/WPF views/viewmodels being created twice

I have a Prism 7 / WPF / MVVM app that is configured with AutowireViewModel="True" in the views that have a viewmodel. Most of my view models have dependencies that I have configured in the Prism Unity container and these dependencies are injected into the viewmodel contructor. I do not explicitly create instances of views/viewmodels in the code behind anywhere. I also do not set the data context in XAML ie using DataContext element or d:DataContext). The only references in the XAML are to views (ie part of the HamburgerMenu control).

All is working fine except each view/viewmodel is ALWAYS constructed twice for some reason, whether this is the main window, or views within modules. I have placed breakpoints in the module managers (only gets hit once) - and also in the view constructors and viewmodel constructors which are both hit twice. The following is some code for App class and also some code/view/viewmodel for a module named Messaging that is loaded at runtime not on demand. Due to wishing to use the MVVM pattern as much as possible, I have very little code in the code behind of the views. NOTE: I tried to see if this is just an issue with views for modules that are loaded on start up, but the same fate is present for the modules loaded on demand.

The App/PrismApplication class in full:

public partial class App : PrismApplication
{
    private ILoggerFactory loggerFactory;
    private TaskbarIcon _taskbarIcon;

    protected override Window CreateShell()
    {
        // Register an instance of the Window (used by the dialog service to get a handle on the window when displaying dialogs)
        Container.GetContainer().RegisterInstance(typeof(Window), "MainWindow", Container.Resolve<MainWindow>(), new ContainerControlledLifetimeManager());

        return Container.Resolve<MainWindow>();
    }

    protected override void OnInitialized()
    {
        base.OnInitialized();

        // Create the links to each module for the banner view to display
        var menuService = Container.Resolve<IMenuService>();
        var regionManager = Container.Resolve<IRegionManager>();

        menuService.AddItem("Home", "Home", () => regionManager.RequestNavigate("MainRegion", "HomeMainView"));
        menuService.AddItem("Messaging", "Messaging", () => regionManager.RequestNavigate("MainRegion", "MessagingMainView"));
        menuService.AddItem("Charts", "Charts", () => regionManager.RequestNavigate("MainRegion", "ChartsMainView"));
        menuService.AddItem("Admin", "Admin", () => regionManager.RequestNavigate("MainRegion", "AdminMainView"));

        // Register banner view with region manager and display it
        regionManager.RegisterViewWithRegion("BannerRegion", typeof(BannerView));

        // Load the desired module into the main window on start up
        Container.Resolve<IRegionManager>().RequestNavigate("MainRegion", "HomeMainView");
    }

    protected override void RegisterTypes(IContainerRegistry containerRegistry)
    {
        var container = this.ConfigureLogging(containerRegistry);

        // Register types 
        container.RegisterInstance<IDbConnectionFactory>(
            new SqlConnectionFactory(
                ConfigurationManager.ConnectionStrings["Messaging"].ConnectionString,
                loggerFactory));            

        /************************************************************/
        // TODO: Not sure if need singletons or not - to test......
        /************************************************************/
        containerRegistry.RegisterSingleton<IMetroMessageDisplayService, MetroMessageDisplayService>();
        containerRegistry.RegisterSingleton<IUserService, UserService>();
        containerRegistry.RegisterSingleton<IUserStore, UserStore>();
        containerRegistry.RegisterSingleton<IMenuService, MenuService>();
        containerRegistry.Register<ICustomerService, CustomerService>();
        containerRegistry.Register<INotifyIconService, NotifyIconService>();
        containerRegistry.RegisterDialog<DefaultDialog, DefaultDialogViewModel>("Default");
        containerRegistry.RegisterDialog<HtmlDialog, HtmlDialogViewModel>("Html");

        // Get the current user's details - prevent a deadlock in the way we use Task.Run(...).Result
        // Then add to container as we will be injecting into VMs
        var clientUser = Task.Run(GetCurrentUserDetails).Result;

        containerRegistry.RegisterInstance(clientUser);
        containerRegistry.RegisterSingleton<IClientUser, ClientUser>();

        // Add the task bar icon
        _taskbarIcon = (TaskbarIcon)FindResource("NotifyIcon");
        containerRegistry.RegisterInstance(_taskbarIcon);

        // Create a logger instance
        var logger = loggerFactory.CreateLogger<App>();

        logger.LogDebug("Finished registering types in App.xaml.cs");
    }

    private async Task<ClientUser> GetCurrentUserDetails()
    {
        var userService = Container.Resolve<IUserService>();
        var data = await userService.GetClientUsersAsync(ConfigurationManager.AppSettings.Get("TempUserName")).ConfigureAwait(true);

        if (!data.Any())
        {
            // log unable to load user from database then return as no point in loading messages for a user that cannot be found!!
            return null;
        }

        return data.FirstOrDefault();
    }

    protected override IModuleCatalog CreateModuleCatalog()
    {
        // We are returning a type that reads the modules from the config file.
        return new ConfigurationModuleCatalog();
    }

    protected override void OnExit(ExitEventArgs e)
    {
        // The icon would clean up automatically, but this is cleaner
        _taskbarIcon.Dispose();

        base.OnExit(e);
    }

    private IUnityContainer ConfigureLogging(IContainerRegistry containerRegistry)
    {
        // Configure logging - Needed Unity.Microsoft.Logging package 
        // see https://github.com/unitycontainer/microsoft-logging and https://github.com/unitycontainer/examples/tree/master/src/Logging/Microsoft.Logging
        var serilogLogger = new LoggerConfiguration()
            .ReadFrom.AppSettings()
            .CreateLogger();

        this.loggerFactory = new LoggerFactory().AddSerilog(serilogLogger);

        var container = containerRegistry.GetContainer();
        container.AddExtension(new LoggingExtension(loggerFactory));

        return container;
    }
}

The messaging module:

public class MessagingModule : IModule
{
    public void OnInitialized(IContainerProvider containerProvider)
    {
        // Register main view with region manager
        var regionManager = containerProvider.Resolve<IRegionManager>();
        regionManager.RegisterViewWithRegion("MainRegion", typeof(MessagingMainView));
    }

    public void RegisterTypes(IContainerRegistry containerRegistry)
    {
        containerRegistry.RegisterSingleton<IMessageStore, MessageStore>();
        containerRegistry.RegisterSingleton<IMessageService, MessageService>();
        containerRegistry.RegisterSingleton<IChatService, ChatService>();
        containerRegistry.RegisterSingleton<IFileDialogService, FileDialogService>();
        containerRegistry.RegisterDialog<InboxMessageView, InboxMessageViewModel>("HtmlMessage");

        // Here we are loading the config/settings from the app.config for this module so we
        // can register a ChatServiceConfiguration type as it is injected into ChatService
        var filename = Assembly.GetExecutingAssembly().Location;

        var configuration = ConfigurationManager.OpenExeConfiguration(filename);

        if (configuration != null)
        {
            var hubUrl = configuration.AppSettings.Settings["HubUrl"].Value;

            if (string.IsNullOrEmpty(hubUrl))
            {
                throw new ArgumentException("The HubUrl app setting cannot ne null or whitespace.");
            }

            containerRegistry.RegisterInstance(new ChatServiceConfiguration(hubUrl));
            containerRegistry.RegisterSingleton<IChatServiceConfiguration, ChatServiceConfiguration>();
        }
    }
}

The MessagingMainView:

<UserControl x:Class="Ascensos.Wpf.Modules.Messaging.Views.MessagingMainView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:Controls="http://metro.mahapps.com/winfx/xaml/controls"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks"
         xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
         xmlns:materialDesignConverters="clr-namespace:MaterialDesignThemes.Wpf.Converters;assembly=MaterialDesignThemes.Wpf"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:views="clr-namespace:Ascensos.Wpf.Modules.Messaging.Views"
         xmlns:helpers="clr-namespace:Ascensos.Wpf.Modules.Messaging.Helpers"
         xmlns:prism="http://prismlibrary.com/"
         prism:ViewModelLocator.AutoWireViewModel="True"
         d:DesignHeight="300"
         d:DesignWidth="400"
         mc:Ignorable="d">

</UserControl>

The code behind:

public sealed partial class MessagingMainView : UserControl
{
    public MessagingMainView()
    {
        this.InitializeComponent();
    }
}

The viewmodel (calls a base message view model class):

public sealed class MessagingMainViewModel
{
    public MessagingMainViewModel()
    {            
    }
}

UPDATE: I have removed code from my app, so that only the above code is within the view and viewmodel. The view constructor and viewmodel constructor is still being hit twice during the module initialization.

I do not explicitly create instances of views/viewmodels in the code behind anywhere (ie only in XAML).

This is a contradiction - creating a view model in xaml means creating one in the same way as in code behind or anywhere else.

As said before, just don't do it. Remove all references to your view models' constructors from xaml, too (like <DataContext><MyViewViewModel/></DataContext> ). To get intellisense in xaml, use d:DataContext .

I have found the issue. It is the following line in App CreateShell() method:

Container.GetContainer().RegisterInstance(typeof(Window), "MainWindow", Container.Resolve<MainWindow>(), new ContainerControlledLifetimeManager());

Commenting this out has sorted the problem. Looking at it again I have been rather silly to not spot this, like the novice I am. This code was added so I could get access to the MainWindow/MetroWindow in a service class - but I will need to try this another way. Thanks for your time @Haukinger.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM