An ADR Visual Studio Extension – Refactoring a Visual Studio Extension
Tags: visualstudio, csharp, extensions
Published: 2020-01-03 08:29:42

A short while ago, I wrote an <a href="https://www.pmichaels.net/2019/09/07/an-adr-visual-studio-tool-part-1-creating-a-visual-studio-extension-to-scrape-the-solution-and-list-all-items-including-solution-items-and-solution-folders/">article</a> 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 <a href="https://adr.github.io/">ADR Records</a>.

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 <a href="https://github.com/pcmichaels/ADRManager">here</a>.

<h1>What's wrong with what was there?</h1>

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 <em>know</em> any more than you do.

<h1>Onto the refactoring... Starting with MVVM basics</h1>

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:


```csharp
    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:

```csharp
        #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 <a href="https://blogs.msdn.microsoft.com/jebarson/2017/07/26/writing-an-asynchronous-relaycommand-implementing-icommand/">here</a>.  Finally, I did a bit of shuffling around:

<a href="https://www.pmichaels.net/wp-content/uploads/2019/12/adr-1.png"><img src="https://www.pmichaels.net/wp-content/uploads/2019/12/adr-1.png" alt="" width="381" height="622" class="alignnone size-full wp-image-4047" /></a>

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

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

<h1>SolutionAnalyser</h1>

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:

```csharp
    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.";
            }            
        }
    }
```


<h1>What's next?</h1>

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.