4
\$\begingroup\$

This is an update from here. I have made some changes, and have decided to only include the UI side of things in this particular question. I wanted to make the application follow the MVVM pattern, but I have strayed in order to add functionality incompatible (or at least so I read) with the pattern.

Synopsis

This program provides a GUI for running queries on an active directory. On launch, it connects to the local active directory, and builds a TreeView of all the OUs. A user can select an OU, and then a query to run. From the query, they can write it as a CSV file, or run contextual queries by right-clicking on any result.

Queries:

  • All Users in OU
  • All Users in OU with each of their groups
  • All Groups in OU
  • All Users in OU with each of their direct reports
  • All Computers in OU

Contextual Queries:

  • User's Groups
  • Group's Users
  • User's Direct Reports
  • Direct Report's Groups
  • Direct Report's Direct Reports
  • Group's Computers

ActiveDirectoryToolView.xaml

<Window x:Class="ActiveDirectoryToolWpf.ActiveDirectoryToolView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="Active Directory Tool"
        Height="576"
        Width="1024"
        MinHeight="576"
        MinWidth="1024">
    <Grid Margin="0,0,0,0">
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="25*" />
            <ColumnDefinition Width="75*" />
        </Grid.ColumnDefinitions>
        <Grid Grid.Row="0">
            <Grid Grid.Column="0">
                <TreeView x:Name="TreeView"
                          ItemsSource="{Binding Path=Children}">
                    <TreeView.ItemTemplate>
                        <HierarchicalDataTemplate
                            ItemsSource="{Binding Path=Children}">
                            <TextBlock Text="{Binding}" />
                        </HierarchicalDataTemplate>
                    </TreeView.ItemTemplate>
                </TreeView>
            </Grid>
        </Grid>
        <Grid Grid.Column="1">
            <Grid.RowDefinitions>
                <RowDefinition Height="42px" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <Grid Grid.Row="0">
                <Button x:Name="GetUsersButton"
                        Content="Get Users Only"
                        HorizontalAlignment="Left"
                        Margin="10,10,0,0"
                        VerticalAlignment="Top"
                        Width="100"
                        Click="GetUsersButton_Click" />
                <Button x:Name="GetUsersGroupsButton"
                        Content="Get Users with Groups"
                        HorizontalAlignment="Left"
                        Margin="115,10,0,0"
                        VerticalAlignment="Top"
                        Width="130"
                        Click="GetUsersGroupsButton_Click" />
                <Button x:Name="GetGroupsButton"
                        Content="Get Groups Only"
                        HorizontalAlignment="Left"
                        Margin="250,10,0,0"
                        VerticalAlignment="Top"
                        Width="100"
                        Click="GetGroupsButton_Click" />
                <Button x:Name="GetDirectReports"
                        Content="Get Users with Direct Reports"
                        HorizontalAlignment="Left"
                        Margin="355,10,0,0"
                        VerticalAlignment="Top"
                        Width="160"
                        Click="GetDirectReports_Click" />
                <Button x:Name="WriteToFile"
                        Content="Write to File"
                        HorizontalAlignment="Left"
                        Margin="677,10,0,0"
                        VerticalAlignment="Top"
                        Width="75"
                        Click="WriteToFile__Click" />
                <Button x:Name="GetComputers"
                        Content="Get Computers Only"
                        HorizontalAlignment="Left"
                        Margin="520,10,0,0"
                        VerticalAlignment="Top"
                        Width="130" Click="GetComputers_Click" />
            </Grid>
            <Grid Grid.Row="1">
                <DataGrid x:Name="DataGrid"
                          AutoGenerateColumns="True"
                          AutoGeneratingColumn="DataGrid_AutoGeneratingColumn"
                          HorizontalAlignment="Left"
                          IsReadOnly="True"
                          ItemsSource="{Binding IsAsync=True}"
                          Margin="10,10,0,0"
                          MaxHeight="9001"
                          MaxWidth="9001"
                          MinHeight="484"
                          MinWidth="742"
                          RenderTransformOrigin="2.25,-3.615"
                          VerticalAlignment="Top" />
                <ProgressBar x:Name="ProgressBar"
                             HorizontalAlignment="Left"
                             Height="10"
                             Margin="326,242,0,0"
                             VerticalAlignment="Top"
                             Width="100"
                             IsIndeterminate="true" />
            </Grid>
        </Grid>
    </Grid>
</Window>

I'm almost certain the binding I'm using for the TreeView is "wrong," as VisualStudio tells me it "cannot resolve symbol Children" but it works exactly as it is supposed to. For reference, Children is a property of ActiveDirectoryScope which is a back-end class. An instance of that class is created in the ActiveDirectoryToolView constructor, and then set as the DataContext of ActiveDirectoryToolView in the code-behind.

ActiveDirectoryToolView.xaml.cs

using System;
using System.Data;
using System.Windows;
using System.Windows.Controls;
using PrimitiveExtensions;

namespace ActiveDirectoryToolWpf
{
    public enum QueryType
    {
        Users,
        Groups,
        UserGroups,
        DirectReports,
        Computers
    }

    public partial class ActiveDirectoryToolView : IActiveDirectoryToolView
    {
        private const string UserDistinguishedName = "UserDistinguishedName";
        private const string GroupDistinguishedName = "GroupDistinguishedName";

        private const string DirectReportDistinguishedName =
            "DirectReportDistinguishedName";

        private const string GetUserGroupsMenuItemHeader =
            "User - Get Groups";

        private const string GetUserDirectReportsMenuItemHeader =
            "User - Get Direct Reports";

        private const string GetDirectReportDirectReportsMenuItemHeader =
            "Direct Report - Get Direct Reports";

        private const string GetDirectReportsUserGroupsMenuItemHeader =
            "Direct Report - Get Groups";

        private const string GetGroupComputersMenuItemHeader =
            "Group - Get Computers";

        private const string GetGroupUsersMenuItemHeader =
            "Group - Get Users";

        private const string NoOrganizationalUnitSelectedErrorMessage =
            "Please select an OU.";

        private const string WroteDataMessage = "Wrote data to ";

        private QueryType _lastQueryType;

        public ActiveDirectoryToolView()
        {
            ViewModel = new ActiveDirectoryToolViewModel(this);
            DataContext = new ActiveDirectoryScopeFetcher().Scope;
            InitializeComponent();
            ProgressBar.Visibility = Visibility.Hidden;
            DataGrid.EnableColumnVirtualization = true;
            DataGrid.EnableRowVirtualization = true;
        }

        public ActiveDirectoryToolViewModel ViewModel { get; }
        public string SelectedItemDistinguishedName { get; set; }

        public ActiveDirectoryScope Scope =>
            TreeView.SelectedItem as ActiveDirectoryScope;

        public event Action GetComputersClicked;
        public event Action GetDirectReportsClicked;
        public event Action GetGroupsClicked;
        public event Action GetUserGroupsClicked;
        public event Action GetUsersClicked;
        public event Action GetUsersGroupsClicked;
        public event Action GetGroupUsersClicked;
        public event Action GetUserDirectReportsClicked;
        public event Action GetGroupComputersClicked;

        public void SetDataGridData(DataView dataView)
        {
            DataGrid.ItemsSource = dataView;
        }

        public void ShowMessage(string message)
        {
            MessageBox.Show(message);
        }

        public void ToggleEnabled()
        {
            IsEnabled = !IsEnabled;
        }

        public void ToggleProgressBarVisibility()
        {
            ProgressBar.Visibility =
                ProgressBar.Visibility == Visibility.Visible
                    ? Visibility.Hidden
                    : Visibility.Visible;
        }

        public void GenerateContextMenu()
        {
            var contextMenu = new ContextMenu();
            if (_lastQueryType == QueryType.Users ||
                _lastQueryType == QueryType.DirectReports ||
                _lastQueryType == QueryType.UserGroups)
            {
                var getUserGroupsMenuItem = new MenuItem
                {
                    Header = GetUserGroupsMenuItemHeader
                };

                getUserGroupsMenuItem.Click += GetUserGroupsMenuItem_Click;
                contextMenu.Items.Add(getUserGroupsMenuItem);
                var getUserDirectReportsMenuItem = new MenuItem
                {
                    Header = GetUserDirectReportsMenuItemHeader
                };

                getUserDirectReportsMenuItem.Click +=
                    GetUserDirectReportsMenuItem_Click;
                contextMenu.Items.Add(getUserDirectReportsMenuItem);
            }

            if (_lastQueryType == QueryType.Groups ||
                _lastQueryType == QueryType.UserGroups)
            {
                var getGroupUsersMenuItem = new MenuItem
                {
                    Header = GetGroupUsersMenuItemHeader
                };

                getGroupUsersMenuItem.Click += GetGroupUsersMenuItem_Click;
                contextMenu.Items.Add(getGroupUsersMenuItem);
                var getGroupComputersMenuItem = new MenuItem
                {
                    Header = GetGroupComputersMenuItemHeader
                };

                getGroupComputersMenuItem.Click +=
                    GetGroupComputersMenuItem_Click;
                contextMenu.Items.Add(getGroupComputersMenuItem);
            }

            if (_lastQueryType == QueryType.DirectReports)
            {
                var getDirectReportsUserGroupsMenuItem = new MenuItem
                {
                    Header = GetDirectReportsUserGroupsMenuItemHeader
                };

                getDirectReportsUserGroupsMenuItem.Click +=
                    GetDirectReportUserGroupsMenuItem_Click;
                contextMenu.Items.Add(getDirectReportsUserGroupsMenuItem);
                var getDirectReportDirectReportsMenuItem = new MenuItem
                {
                    Header = GetDirectReportDirectReportsMenuItemHeader
                };

                getDirectReportDirectReportsMenuItem.Click +=
                    GetDirectReportDirectReportsMenuItem_Click;
                contextMenu.Items.Add(getDirectReportDirectReportsMenuItem);
            }

            DataGrid.ContextMenu = contextMenu;
        }

        private void DataGrid_AutoGeneratingColumn(
            object sender, DataGridAutoGeneratingColumnEventArgs e)
        {
            e.Column.Header = e.Column.Header.ToString().SpaceCamelCase();
        }

        private void GetComputers_Click(object sender, RoutedEventArgs e)
        {
            _lastQueryType = QueryType.Computers;
            if (Scope != null)
                GetComputersClicked?.Invoke();
            else
                ShowMessage(NoOrganizationalUnitSelectedErrorMessage);
        }

        private void GetDirectReports_Click(object sender, RoutedEventArgs e)
        {
            _lastQueryType = QueryType.DirectReports;
            if (Scope != null)
                GetDirectReportsClicked?.Invoke();
            else
                ShowMessage(NoOrganizationalUnitSelectedErrorMessage);
        }

        private void GetGroupsButton_Click(object sender, RoutedEventArgs e)
        {
            _lastQueryType = QueryType.Groups;
            if (Scope != null)
                GetGroupsClicked?.Invoke();
            else
                ShowMessage(NoOrganizationalUnitSelectedErrorMessage);
        }

        private void GetUsersButton_Click(object sender, RoutedEventArgs e)
        {
            _lastQueryType = QueryType.Users;
            if (Scope != null)
                GetUsersClicked?.Invoke();
            else
                ShowMessage(NoOrganizationalUnitSelectedErrorMessage);
        }

        private void GetUsersGroupsButton_Click(object sender,
            RoutedEventArgs e)
        {
            _lastQueryType = QueryType.UserGroups;
            if (Scope != null)
                GetUsersGroupsClicked?.Invoke();
            else
                ShowMessage(NoOrganizationalUnitSelectedErrorMessage);
        }

        private void WriteToFile__Click(object sender, RoutedEventArgs e)
        {
            if (DataGrid.Items.Count <= 0) return;
            var fileWriter = new DataFileWriter
            {
                Data = DataGrid,
                Scope = Scope.Context,
                QueryType = _lastQueryType
            };
            ShowMessage(WroteDataMessage + fileWriter.WriteToCsv());
        }

        private void GetUserGroupsMenuItem_Click(object sender, EventArgs e)
        {
            _lastQueryType = QueryType.UserGroups;
            var row = (DataRowView) DataGrid.SelectedItem;
            SelectedItemDistinguishedName =
                row[UserDistinguishedName].ToString();
            GetUserGroupsClicked?.Invoke();
        }

        private void GetGroupUsersMenuItem_Click(object sender, EventArgs e)
        {
            _lastQueryType = QueryType.Users;
            var row = (DataRowView) DataGrid.SelectedItem;
            SelectedItemDistinguishedName =
                row[GroupDistinguishedName].ToString();
            GetGroupUsersClicked?.Invoke();
        }

        private void GetUserDirectReportsMenuItem_Click(
            object sender, EventArgs e)
        {
            _lastQueryType = QueryType.DirectReports;
            var row = (DataRowView) DataGrid.SelectedItem;
            SelectedItemDistinguishedName =
                row[UserDistinguishedName].ToString();
            GetUserDirectReportsClicked?.Invoke();
        }

        private void GetDirectReportDirectReportsMenuItem_Click(
            object sender, EventArgs e)
        {
            _lastQueryType = QueryType.DirectReports;
            var row = (DataRowView) DataGrid.SelectedItem;
            SelectedItemDistinguishedName =
                row[DirectReportDistinguishedName].ToString();
            GetUserDirectReportsClicked?.Invoke();
        }

        private void GetGroupComputersMenuItem_Click(
            object sender, EventArgs e)
        {
            _lastQueryType = QueryType.Computers;
            var row = (DataRowView) DataGrid.SelectedItem;
            SelectedItemDistinguishedName =
                row[GroupDistinguishedName].ToString();
            GetGroupComputersClicked?.Invoke();
        }

        private void GetDirectReportUserGroupsMenuItem_Click(
            object sender, EventArgs e)
        {
            _lastQueryType = QueryType.UserGroups;
            var row = (DataRowView) DataGrid.SelectedItem;
            SelectedItemDistinguishedName =
                row[DirectReportDistinguishedName].ToString();
            GetUserGroupsClicked?.Invoke();
        }
    }
}

It feels like all my listeners are repetitive, but they all have enough difference that I can't seem to consolidate them.

IActiveDirectoryToolView.cs

using System;
using System.Data;

namespace ActiveDirectoryToolWpf
{
    public interface IActiveDirectoryToolView
    {
        ActiveDirectoryScope Scope { get; }
        string SelectedItemDistinguishedName { get; set; }
        event Action GetComputersClicked;
        event Action GetDirectReportsClicked;
        event Action GetGroupComputersClicked;
        event Action GetGroupsClicked;
        event Action GetGroupUsersClicked;
        event Action GetUserDirectReportsClicked;
        event Action GetUserGroupsClicked;
        event Action GetUsersClicked;
        event Action GetUsersGroupsClicked;
        void GenerateContextMenu();
        void SetDataGridData(DataView dataView);
        void ShowMessage(string message);
        void ToggleEnabled();
        void ToggleProgressBarVisibility();
    }
}

ActiveDirectoryToolViewModel.cs

using System;
using System.Collections.Generic;
using System.Data;
using System.DirectoryServices.AccountManagement;
using System.Dynamic;
using System.Linq;
using System.Threading.Tasks;

namespace ActiveDirectoryToolWpf
{
    public class ActiveDirectoryToolViewModel
    {
        private const string NoResultsErrorMessage =
            "No results found. Please ensure you are searching for the " +
            "correct principal type in the correct OU.";

        private readonly ActiveDirectoryAttribute[]
            _defaultComputerAttributes =
            {
                ActiveDirectoryAttribute.ComputerName,
                ActiveDirectoryAttribute.ComputerDescription,
                ActiveDirectoryAttribute.ComputerDistinguishedName
            };

        private readonly ActiveDirectoryAttribute[]
            _defaultDirectReportsAttributes =
            {
                ActiveDirectoryAttribute.UserDisplayName,
                ActiveDirectoryAttribute.UserSamAccountName,
                ActiveDirectoryAttribute.DirectReportDisplayName,
                ActiveDirectoryAttribute.DirectReportSamAccountName,
                ActiveDirectoryAttribute.UserDistinguishedName,
                ActiveDirectoryAttribute.DirectReportDistinguishedName
            };

        private readonly ActiveDirectoryAttribute[] _defaultGroupAttributes =
        {
            ActiveDirectoryAttribute.GroupSamAccountName,
            ActiveDirectoryAttribute.GroupManagedBy,
            ActiveDirectoryAttribute.GroupDescription,
            ActiveDirectoryAttribute.GroupDistinguishedName
        };

        private readonly ActiveDirectoryAttribute[]
            _defaultGroupUsersAttributes =
            {
                ActiveDirectoryAttribute.UserSurname,
                ActiveDirectoryAttribute.UserGivenName,
                ActiveDirectoryAttribute.UserDisplayName,
                ActiveDirectoryAttribute.UserSamAccountName,
                ActiveDirectoryAttribute.UserIsActive,
                ActiveDirectoryAttribute.UserIsAccountLockedOut,
                ActiveDirectoryAttribute.UserDescription,
                ActiveDirectoryAttribute.UserTitle,
                ActiveDirectoryAttribute.UserCompany,
                ActiveDirectoryAttribute.UserManager,
                ActiveDirectoryAttribute.UserHomeDrive,
                ActiveDirectoryAttribute.UserHomeDirectory,
                ActiveDirectoryAttribute.UserScriptPath,
                ActiveDirectoryAttribute.UserEmailAddress,
                ActiveDirectoryAttribute.UserStreetAddress,
                ActiveDirectoryAttribute.UserCity,
                ActiveDirectoryAttribute.UserState,
                ActiveDirectoryAttribute.UserVoiceTelephoneNumber,
                ActiveDirectoryAttribute.UserPager,
                ActiveDirectoryAttribute.UserMobile,
                ActiveDirectoryAttribute.UserFax,
                ActiveDirectoryAttribute.UserVoip,
                ActiveDirectoryAttribute.UserSip,
                ActiveDirectoryAttribute.UserUserPrincipalName,
                ActiveDirectoryAttribute.UserDistinguishedName,
                ActiveDirectoryAttribute.GroupSamAccountName,
                ActiveDirectoryAttribute.GroupManagedBy,
                ActiveDirectoryAttribute.GroupDescription,
                ActiveDirectoryAttribute.GroupDistinguishedName
            };

        private readonly ActiveDirectoryAttribute[] _defaultUserAttributes =
        {
            ActiveDirectoryAttribute.UserSurname,
            ActiveDirectoryAttribute.UserGivenName,
            ActiveDirectoryAttribute.UserDisplayName,
            ActiveDirectoryAttribute.UserSamAccountName,
            ActiveDirectoryAttribute.UserIsActive,
            ActiveDirectoryAttribute.UserIsAccountLockedOut,
            ActiveDirectoryAttribute.UserDescription,
            ActiveDirectoryAttribute.UserTitle,
            ActiveDirectoryAttribute.UserCompany,
            ActiveDirectoryAttribute.UserManager,
            ActiveDirectoryAttribute.UserHomeDrive,
            ActiveDirectoryAttribute.UserHomeDirectory,
            ActiveDirectoryAttribute.UserScriptPath,
            ActiveDirectoryAttribute.UserEmailAddress,
            ActiveDirectoryAttribute.UserStreetAddress,
            ActiveDirectoryAttribute.UserCity,
            ActiveDirectoryAttribute.UserState,
            ActiveDirectoryAttribute.UserVoiceTelephoneNumber,
            ActiveDirectoryAttribute.UserPager,
            ActiveDirectoryAttribute.UserMobile,
            ActiveDirectoryAttribute.UserFax,
            ActiveDirectoryAttribute.UserVoip,
            ActiveDirectoryAttribute.UserSip,
            ActiveDirectoryAttribute.UserUserPrincipalName,
            ActiveDirectoryAttribute.UserDistinguishedName
        };

        private readonly ActiveDirectoryAttribute[]
            _defaultUserGroupsAttributes =
            {
                ActiveDirectoryAttribute.UserSamAccountName,
                ActiveDirectoryAttribute.GroupSamAccountName,
                ActiveDirectoryAttribute.UserName,
                ActiveDirectoryAttribute.UserDistinguishedName,
                ActiveDirectoryAttribute.GroupDistinguishedName
            };

        private readonly IActiveDirectoryToolView _view;
        private IEnumerable<ExpandoObject> _data;
        private DataPreparer _dataPreparer;
        private ActiveDirectorySearcher _searcher;

        public ActiveDirectoryToolViewModel(IActiveDirectoryToolView view)
        {
            _view = view;
            _view.GetComputersClicked += OnGetComputers;
            _view.GetDirectReportsClicked += OnGetDirectReports;
            _view.GetGroupsClicked += OnGetGroups;
            _view.GetUsersClicked += OnGetUsers;
            _view.GetUsersGroupsClicked += OnGetUsersGroups;
            _view.GetUserGroupsClicked += OnGetUserGroups;
            _view.GetGroupUsersClicked += OnGetGroupUsers;
            _view.GetUserDirectReportsClicked += OnGetUserDirectReports;
            _view.GetGroupComputersClicked += OnGetGroupComputers;
        }

        private void FinishTask()
        {
            _view.ToggleProgressBarVisibility();
            try
            {
                _view.SetDataGridData(_data.ToDataTable().AsDataView());
            }
            catch (ArgumentNullException)
            {
                _view.ShowMessage(NoResultsErrorMessage);
            }

            _view.GenerateContextMenu();
            _view.ToggleEnabled();
        }

        private async void OnGetGroupComputers()
        {
            StartTask();
            await Task.Run(() =>
            {
                var principalContext = new PrincipalContext(
                    ContextType.Domain);
                var groupPrincipal = GroupPrincipal.FindByIdentity(
                    principalContext, _view.SelectedItemDistinguishedName);
                _dataPreparer = new DataPreparer
                {
                    Data = ActiveDirectorySearcher.GetComputersFromGroup(
                        groupPrincipal),
                    Attributes = _defaultComputerAttributes.ToList()
                };
                _data = _dataPreparer.GetResults();
            });

            FinishTask();
        }

        private async void OnGetComputers()
        {
            StartTask();
            await Task.Run(() =>
            {
                _dataPreparer = new DataPreparer
                {
                    Data = _searcher.GetComputers(),
                    Attributes = _defaultComputerAttributes.ToList()
                };
                _data = _dataPreparer.GetResults();
            });

            FinishTask();
        }

        private async void OnGetDirectReports()
        {
            StartTask();
            await Task.Run(() =>
            {
                _dataPreparer = new DataPreparer
                {
                    Data = _searcher.GetDirectReports(),
                    Attributes = _defaultDirectReportsAttributes.ToList()
                };
                _data = _dataPreparer.GetResults();
            });

            FinishTask();
        }

        private async void OnGetGroups()
        {
            StartTask();
            await Task.Run(() =>
            {
                _dataPreparer = new DataPreparer
                {
                    Data = _searcher.GetGroups(),
                    Attributes = _defaultGroupAttributes.ToList()
                };
                _data = _dataPreparer.GetResults();
            });

            FinishTask();
        }

        private async void OnGetGroupUsers()
        {
            StartTask();
            await Task.Run(() =>
            {
                var principalContext = new PrincipalContext(
                    ContextType.Domain);
                var groupPrincipal = GroupPrincipal.FindByIdentity(
                    principalContext, _view.SelectedItemDistinguishedName);
                _dataPreparer = new DataPreparer
                {
                    Data = ActiveDirectorySearcher.GetUsersFromGroup(
                        groupPrincipal),
                    Attributes = _defaultGroupUsersAttributes.ToList()
                };
                _data = _dataPreparer.GetResults();
            });

            FinishTask();
        }

        private async void OnGetUserDirectReports()
        {
            StartTask();
            await Task.Run(() =>
            {
                var principalContext = new PrincipalContext(
                    ContextType.Domain);
                var userPrincipal = UserPrincipal.FindByIdentity(
                    principalContext, _view.SelectedItemDistinguishedName);
                _dataPreparer = new DataPreparer
                {
                    Data = new[]
                    {
                        ActiveDirectorySearcher.GetDirectReportsFromUser(
                            userPrincipal)
                    },
                    Attributes = _defaultDirectReportsAttributes.ToList()
                };
                _data = _dataPreparer.GetResults();
            });

            FinishTask();
        }

        private async void OnGetUserGroups()
        {
            StartTask();
            await Task.Run(() =>
            {
                var principalContext = new PrincipalContext(
                    ContextType.Domain);
                var userPrincipal = UserPrincipal.FindByIdentity(
                    principalContext, _view.SelectedItemDistinguishedName);
                _dataPreparer = new DataPreparer
                {
                    Data = new[]
                    {
                        ActiveDirectorySearcher.GetUserGroupsFromUser(
                            userPrincipal)
                    },
                    Attributes = _defaultUserGroupsAttributes.ToList()
                };
                _data = _dataPreparer.GetResults();
            });

            FinishTask();
        }

        private async void OnGetUsers()
        {
            StartTask();
            await Task.Run(() =>
            {
                _dataPreparer = new DataPreparer
                {
                    Data = _searcher.GetUsers(),
                    Attributes = _defaultUserAttributes.ToList()
                };
                _data = _dataPreparer.GetResults();
            });

            FinishTask();
        }

        private async void OnGetUsersGroups()
        {
            StartTask();
            await Task.Run(() =>
            {
                _dataPreparer = new DataPreparer
                {
                    Data = _searcher.GetUsersGroups(),
                    Attributes = _defaultUserGroupsAttributes.ToList()
                };
                _data = _dataPreparer.GetResults();
            });

            FinishTask();
        }

        private void StartTask()
        {
            _view.SetDataGridData(null);
            _view.ToggleProgressBarVisibility();
            _searcher = new ActiveDirectorySearcher(_view.Scope);
            _view.ToggleEnabled();
        }
    }
}

So yeah, I know that's not really a true ViewModel. Those ActiveDirectoryAttribute arrays are placeholders for future configuration files. All the different event handlers were the best solution I had to allow for the data fetching to be run in the background (so the app doesn't lock up and crash) while still disabling the UI of the app to prevent unwanted user input. The progress bar (indefinite) is a cheap way of informing users that the app is working. I didn't know where to begin in regards to reporting progress from the actual back-end, as it would have to "bubble" through multiple classes to reach the UI.

enter image description here

\$\endgroup\$
  • \$\begingroup\$ Quite Lengthy, will send some few backs after some days. \$\endgroup\$ – Siobhan Jun 29 '16 at 13:27
  • \$\begingroup\$ @TolaniJaiye-Tikolo You should have seen my last question about this. It had all the code, and I had to omit some lines to stay under the limit. :P If you have any ideas on how I could split it up and still provide "the full picture" please let me know. \$\endgroup\$ – Michael Brandon Morris Jun 29 '16 at 13:37
2
\$\begingroup\$

You set some of the properties to their default values. For example Button's alignments are Top and Left by default, RowDefinition.Height is * by default, etc. It makes your xaml larger then it needs to be.


You should not use Margin to specify element's absolute position. In most cases it's a bad practice. Use appropriate container instead. DockPanel will do:

    <DockPanel>
       <Button Margin="5" DockPanel.Dock="Left"  .../>
       <Button Margin="5" DockPanel.Dock="Left" .../>
       ...
       <Button Margin="5" DockPanel.Dock="Right" Content="Write to File" .../>
    </DockPanel>

Notice how Margin is used to specify offset between individual items in container, but the layout itself is defined by DockPanel. You can also use StackPanel or multi-column Grid as containers for slightly different layouts.


x:Name breaks encapsulation by exposing inner controls. Only use it when you actually need to access control by name from code, or at least add private modifier: x:FieldModifier="private".


You set some of the UI properties in constructor. For example: ProgressBar.Visibility = Visibility.Hidden;. Why? It makes those properties easy to miss. Move those to xaml.


You severely break MVVM:

  1. You should not use events to wire up buttons to your view model. You should use ICommand interface instead. Declare a property on your viewmodel:

    public ICommand GetComputersCommand { get; private set; }
    

    and bind it to your view:

    <Button Content="Get Computers Only" Command="{Binding GetComputersCommand}"/>
    

    So that leaves us with:

    public interface IActiveDirectoryToolView
    {
        ActiveDirectoryScope Scope { get; }
        string SelectedItemDistinguishedName { get; set; }
        void GenerateContextMenu();
        void SetDataGridData(DataView dataView);
        void ShowMessage(string message);
        void ToggleEnabled();
        void ToggleProgressBarVisibility();
    }
    
  2. To get DataGrid.SelectedItem you can bind it to property on your viewmodel. So you can remove IActiveDirectoryToolView.SelectedItemDistinguishedName as well. Same goes for ToggleEnabled() (bind to IsEnabled), Scope (bind to TreeView.SelectedItem), ToggleProgressBarVisibility() (bind to ProgressBar.Visibility), SetDataGridData (bind to DataGrid.ItemsSource). That leaves:

    public interface IActiveDirectoryToolView
    {
        void GenerateContextMenu();
        void ShowMessage(string message);
    }
    
  3. Binding viewmodel to ContextMenu.ItemsSource is tricky, but possible. An alternative solution is to declare QueryType LastQueryType dependency property on your view and bind it to your view model. Then you will be able to update context menu in code-behind whenever this property changes. Both of those approaches will require a deeper understanding of how MVVM and WPF work though.

  4. As for ShowMessage, I would create a separate service, for example:

    interface INotification
    {
        void ShowMessage(string message);
    }
    

    and use it in both view and view model.

Long story short: situations where you have to pass your view to your viewmodel constructor are really rare, and should be avoided. Before refactoring your code I think you should spend some time to truly understand what MVVM is and how to use it for trivial tasks (binding commands to buttons and such).


I won't go over business logic, as this answer already turned way larger than I intended. :) But both _default...Attributes fields and copy-pasted event handlers smell fishy to me. So hopefully someone else will cover this part of your code.

\$\endgroup\$
  • \$\begingroup\$ Thank you so much for your answer. From what you put here, I have been able to extrapolate and actually do other things I wanted in the first place, but was unable to figure out. I now have binding working as intended, and the code is so much cleaner (and hopefully MVVM compliant). \$\endgroup\$ – Michael Brandon Morris Jul 1 '16 at 12:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.