An ADR Visual Studio Tool – Part 2 – Refactoring

A short while ago, I wrote an article about how to create a new extension for Visual Studio. The end target of this is to have a tool that will allow easy management of ADR Records.

In this post, I’m going to clean up some of the code in that initial sample. There’s nothing new here, just some basic WPF good practices. If you’re interested in downloading, or just seeing the code, it’s here.

What’s wrong with what was there?

The extension worked (at least as far as it went), but it used code behind to execute the functionality. This means that the logic and the UI are tightly coupled. My guess is that soon (maybe as part of .Net 5) the extensions will move over to another front end tech (i.e. not WPF), which means that people that have written extensions may need to re-write them. This is a guess – I don’t know any more than you do.

Onto the refactoring… Starting with MVVM basics

Let’s start with a simple View Model; previously, the code was in the code behind, so we’ll move that all over to a view model:

    public class AdrControlViewModel : INotifyPropertyChanged
    {
        public AdrControlViewModel()
        {
            Scan = new RelayCommandAsync<object>(ScanCommand);
        }

        private string _summary;
        public string Summary 
        { 
            get => _summary; 
            set => UpdateField(ref _summary, value); 
        }

        public RelayCommandAsync<object> Scan { get; set; }

        private async Task ScanCommand(object arg)
        {
            var solutionAnalyser = new SolutionAnalyser();
            Summary = await solutionAnalyser.ScanSolution();            
        }
    }

You’ll also need the following INotifyPropertyChanged boilerplate code:

        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName]string fieldName = null) =>        
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(fieldName));

        private void UpdateField<T>(ref T field, T value, [CallerMemberName]string fieldName = null)
        {
            field = value;
            OnPropertyChanged(fieldName);
        }
        #endregion

One day, this can go into a base class, if we ever create a second View Model. We’ll come back to SolutionAnalyser in a while. I shamelessly pilfered the RelayCommand code from here. Finally, I did a bit of shuffling around:

Finally, the code behind needs to be changed as follows:

    public partial class AdrControl : UserControl
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="AdrControl"/> class.
        /// </summary>
        public AdrControl()
        {
            this.InitializeComponent();
            DataContext = new AdrControlViewModel();
        }   
    }

SolutionAnalyser

This is, essentially, the only real code that actually does anything. It’s likely to be severely refactored in a later incarnation, but for now, it’s just in its own class:

    public class SolutionAnalyser
    {
        internal async Task<string> ScanSolution()
        {
            try
            {
                await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
                var dte = (DTE)Package.GetGlobalService(typeof(DTE));

                var sln = Microsoft.Build.Construction.SolutionFile.Parse(dte.Solution.FullName);
                string summaryText = $"{sln.ProjectsInOrder.Count.ToString()} projects";

                foreach (Project p in dte.Solution.Projects)
                {
                    summaryText += $"{Environment.NewLine} {p.Name} {p.ProjectItems.Count}";
                }
                return summaryText;
            }
            catch
            {
                return "Solution is not ready yet.";
            }            
        }
    }

What’s next?

The next stage is to introduce a search and create facility. I’m going to start creating some issues in the GitHub repo when I get some time.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.