다음을 통해 공유


NuGet Package Browser (C#)

Introduction

NuGet packages have become the standard for adding references to a project in Microsoft Visual Studio were in solutions, which span more than one project there may be times when a package is used in multiple projects and by accident, each project uses a different version of the same package, which can lead to building of the solution problematic. 

When there is, one package a developer can visually inspect packages by right clicking on the top node of solution explorer; select manage NuGet packages and see if there is more than one version of a package being used. In some cases even with one package, there can be dependency conflicts also.

Alternately, there is the Package Manager Console which a developer can use Get-Package | ft –AutoSize to display packages an versions yet this still can be difficult to match up packages and versions.

Important

Note

Make sure to read the direction for downloading the source code as the method is different than conventional cloning a GitHub repository.

 

NuGet Package Browser

A solution to this is a utility presented which provides the ability to

  • Find all packages in a solution
  • Works with conventional .NET, .NET Core and .NET 5
  • Visually show different packages and versions
  • Group packages by version
  • Export to HTML
  • Export to CSV

The following is a screenshot without any selections.

Basic usage

After building the executable add the executable under Tools-> External Tools so it's available when needed. Suggest placing the menu item directly under NuGet package Browser as shown below.

  1. Fire up the utility.
  2. Select a language from the combo box, choices are current C# (default) and VB.NET
  3. Select the button "Select a solution. A custom folder dialog is used so it's easier than the standard .NET component.
  4. For a solution with many packages they are displayed as shown in figure 1
  5. In the second combo box select a package as shown in figure 2.
  6. Suppose there is an issue with Entity Framework Core for SQL-Server, select this package and click "Check versions". Results are shown in figure 3.
  7. In figure 3 note the version count is 2 shown under the "Check versions" button.
  8. To get a much better view click the "Report" button, this opens a modal window as presented in figure 4.
  9. Clicking the "Export" button on the modal window exports the data displayed into a comma delimited file in the executable folder. Feel free to use a save dialog if so desired along with using Process.Start to open the comma delimited file.

Figure 1

Figure 2 (shows different results than figure 1 on purpose)

Figure 3

Figure 4

Source code learning points

Besides being a useful utility there are learning points in the source code.

  • Unhandled exceptions (if any) are handled in Program.cs. Exceptions are not cumulative, each time there is an exception the former file is overwritten. In an application for customers the file would be appended too although this is a developer tool thus no need for appended to an existing file.
  • If there is an exception captured in a try-catch statement an extension method is used found in the following repository. This code has been placed into a separate class project for reusability in other projects in the same solution or other solutions.
  • Not DataTable or DataSet containers are used as they carry more luggage than needed, instead classes are used found in the following folder.
  • The majority of operations are shown below, only code which must reside in forms does. Note the use of delegate/events and lambda grouping operations to learn from.
  • The selected choice for displaying information is by ListView. Many developers forego ListView controls for DataGridView controls while the best way to know which control to use is by having choices of more than one control type. In this case like DataTable containers being heavy so is a DataGridView.

Core code

001.using System;
002.using System.Collections.Generic;
003.using System.Diagnostics;
004.using System.IO;
005.using System.Linq;
006.using System.Text;
007.using System.Xml.Linq;
008.using System.Xml.XPath;
009.using NuGetPackageBrowser.Classes.Containers;
010. 
011.namespace NuGetPackageBrowser.Classes
012.{
013. 
014.    public class Operations 
015.    {
016.        public delegate void DisplayInformation(string sender, bool bold);
017.        public static event DisplayInformation DisplayInformationHandler;
018.        public static Solution Solution;
019. 
020.        /// <summary>
021.        /// List of distinct package names in solution
022.        /// </summary>
023.        /// <returns></returns>
024.        public static List<string> DistinctPackageNames()
025.        {
026.            var sorted = new SortedSet<string>();
027. 
028.            foreach (var package in Solution.Packages)
029.            {
030.                foreach (var packageItem in package.PackageItems)
031.                {
032.                    sorted.Add(packageItem.Name);
033.                }
034. 
035.            }
036.             
037.            return sorted.ToList();
038.        }
039. 
040.        /// <summary>
041.        /// File name for exporting results to a web page
042.        /// </summary>
043.        public static string ExportWebPageFileName { get; set; }
044. 
045.        /// <summary>
046.        /// 
047.        /// </summary>
048.        /// <param name="solutionFolder">Folder to get packages</param>
049.        /// <param name="projectType">language e.g. C#, VB.NET</param>
050.        public static void BuilderPackageTable(string solutionFolder, string projectType)
051.        {
052.            string[] exclude = {".git",".vs", "packages"};
053. 
054.            string[] solutionName = Directory.GetFiles(solutionFolder, "*.sln");
055.            Solution = new Solution()
056.            {
057.                Folder = solutionFolder,
058.                SolutionName = solutionName.Length == 1 ? Path.GetFileName(solutionName[0]) : "???"
059.            };
060. 
061.            var folders = Directory.GetDirectories(solutionFolder).
062.                Where(path => !exclude.Contains(path.Split('\\').Last()));
063. 
064.            foreach (var folder in folders)
065.            {
066.                var fileName = (Path.Combine(folder, "packages.config"));
067.                var package = new Package(); // {SolutionFolder = solutionFolder };
068. 
069.                if (File.Exists(fileName))
070.                {
071.                     
072.                    var projectFiles = Directory.GetFiles(folder, projectType);
073. 
074.                    if (projectFiles.Length == 0)
075.                    {
076.                        continue;
077.                    }
078. 
079.                    var projectNameWithoutExtension = Path.GetFileNameWithoutExtension(projectFiles[0]);
080. 
081.                    DisplayInformationHandler?.Invoke(projectNameWithoutExtension, true);
082.                    package.ProjectName = projectNameWithoutExtension;
083. 
084.                    var doc = XDocument.Load(fileName);
085. 
086.                    foreach (var packageNode in doc.XPathSelectElements("/packages/package"))
087.                    {
088.                        var packageName = packageNode.Attribute("id").Value;
089.                        //var version = packageNode.Attribute("version").Value;
090.                        Version version = new Version(packageNode.Attribute("version").Value);
091.                         
092.                        DisplayInformationHandler?.Invoke($"    {packageName}, {version}", false);
093.                        package.PackageItems.Add(new PackageItem() {Name = packageName, Version = version});
094. 
095.                    }
096. 
097.                    Solution.Packages.Add(package);
098. 
099.                    DisplayInformationHandler?.Invoke("", false);
100.                }
101.                else
102.                {
103.                    package = new Package(); 
104.                    var projectFiles = Directory.GetFiles(folder, projectType);
105.                    if (projectFiles.Length == 0)
106.                    {
107.                        continue;
108.                    }
109. 
110.                    GetCorePackages(projectFiles[0],package);
111.                }
112.              
113.            }
114.        }
115.        public static void GetCorePackages(string projectFileName, Package package)
116.        {
117.            if (!File.Exists(projectFileName))
118.            {
119.                return;
120.            }
121. 
122.            var doc = XDocument.Load(projectFileName);
123.            var packageReferences = doc.XPathSelectElements("//PackageReference")
124.                .Select(pr => new PackageReference
125.                {
126.                    Include = pr.Attribute("Include").Value,
127.                    Version = new Version(pr.Attribute("Version").Value)
128.                });
129. 
130.            if (!packageReferences.Any())
131.            {
132.                return;
133.            }
134. 
135.            package.ProjectName = Path.GetFileNameWithoutExtension(projectFileName);
136.            DisplayInformationHandler?.Invoke(Path.GetFileNameWithoutExtension(projectFileName), true);
137. 
138.            foreach (PackageReference packageReference in packageReferences)
139.            {
140. 
141.                package.PackageItems.Add(new PackageItem()
142.                {
143.                    Name = packageReference.Include,
144.                    Version = packageReference.Version
145.                });
146.                 
147.                 
148.                DisplayInformationHandler?
149.                    .Invoke($"    {packageReference.Include}, {packageReference.Version}", false);
150. 
151.            }
152. 
153.            Solution.Packages.Add(package);
154. 
155.        }
156.        /// <summary>
157.        /// Export current solution packages to a HTML page.
158.        /// Note that the styles can be changed to suit a developer's
159.        /// personal choices.
160.        /// </summary>
161.        public static void ExportAsWebPage()
162.        {
163.            var sb = new StringBuilder();
164. 
165.            sb.AppendLine("<!DOCTYPE html>");
166.            sb.AppendLine("<html>");
167.            sb.AppendLine("<head>");
168.            sb.AppendLine("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
169.            sb.AppendLine("<style>");
170.            sb.AppendLine("table {border-collapse: collapse;border-spacing: 0;width: 100%;border: 1px solid #ddd;}");
171.            sb.AppendLine("table.center {width:400px;margin-left: auto; margin-right: auto;}");
172.            sb.AppendLine("th, td {text-align: left;padding: 16px;}");
173.            sb.AppendLine("tr:nth-child(even) {background-color: #f2f2f2;}");
174.            sb.AppendLine("h1 {text-align: center;color:DodgerBlue;}");
175.            sb.AppendLine("</style>");
176.            sb.AppendLine("</head>");
177.            sb.AppendLine("<body>");
178. 
179. 
180.            sb.AppendLine("<h1>Nuget packages</h1>");
181.            sb.AppendLine("<table  class=\"center\">");
182.            sb.AppendLine($"<tr><td><strong>Solution folder</strong></td><td>{Solution.Folder}</td></tr>");
183.            sb.AppendLine($"<tr><td><strong>Solution name</strong></td><td>{Solution.SolutionName}</td></tr>");
184.            sb.AppendLine("<tr><td style='height: 10px !important;'></td></tr>");
185. 
186. 
187.            foreach (var package in Solution.Packages)
188.            {
189.                Console.WriteLine(package.ProjectName);
190.                sb.AppendLine(
191.                    $"<tr><td style='background-color:#FFFF66 !important;'><strong>{package.ProjectName}</strong></td>" + 
192.                    "<td style='white-space: pre;background-color:#FFFF66 !important;'></td></tr>");
193. 
194.                foreach (var packageItem in package.PackageItems)
195.                {
196.                    sb.AppendLine($"<tr><td>{packageItem.Name}</td><td>{packageItem.Version}</td></tr>");
197.                }
198. 
199.            }
200. 
201.            sb.AppendLine("</table>");
202. 
203.            sb.AppendLine("</body>");
204.            sb.AppendLine("</html>");
205.             
206.            File.WriteAllText(ExportWebPageFileName, sb.ToString());
207. 
208.            if (File.Exists(ExportWebPageFileName))
209.            {
210.                Process.Start(ExportWebPageFileName);
211.            }
212. 
213.        }
214.        /// <summary>
215.        /// Provides packages grouped by version
216.        /// </summary>
217.        /// <param name="packageName">Package name</param>
218.        /// <param name="packageItems">List of PackageItem</param>
219.        /// <returns></returns>
220.        public static List<PackageResult> VersionGroup(string packageName, List<PackageItem> packageItems)
221.        {
222.            return packageItems.GroupBy(packageItem => packageItem.Version)
223.                .Select(iGroup => new PackageResult
224.                {
225.                    Version = iGroup.Key,
226.                    List = iGroup.ToList()
227.                }).ToList();
228.        }
229.        /// <summary>
230.        /// Create a comma-delimited string
231.        /// </summary>
232.        /// <param name="packageName">Name of package</param>
233.        /// <param name="packageItems"></param>
234.        /// <returns></returns>
235.        public static string VersionReport(string packageName,List<PackageItem> packageItems)
236.        {
237.            var sb = new StringBuilder();
238. 
239.            sb.AppendLine(packageName).Replace("'","");
240.            sb.AppendLine(new string('-', 30));
241. 
242.            var results = packageItems.GroupBy(packageItem => packageItem.Version)
243.                .Select(iGroup => new PackageResult
244.                {
245.                    Version = iGroup.Key,
246.                    List = iGroup.ToList()
247.                }).ToList();
248. 
249.            foreach (var result in results)
250.            {
251.                sb.AppendLine(result.Version.ToString());
252.                foreach (var packageItem in result.List)
253.                {
254.                    sb.AppendLine($"{packageItem.Name}");
255.                }
256. 
257.                sb.AppendLine(new string('-', 30));
258.            }
259. 
260.            return sb.ToString();
261.        }
262.    }
263.}

Summary

By having this utility if the time comes when there are conflicts with NuGet packages this utility can greatly reduce time it takes to learn where the conflicts are in any Visual Studio solution targeting .NET Framework, .NET Core and .NET 5.  In addition, there is a great deal for all levels of C# developers to learn from.

For VB.NET developers without any C# knowledge simple follow the directions above as there is no need to change the source.

See also

Source code

Github

Source code is located in the following GitHub repository which has 50 projects Since there is no way to download only one or two projects place the following in a temp folder e.g. C:\DotNet into a bath file and run it which will download only the projects needed to compile the NuGet package browser.

Note

The batch file below was generated by a tool in the same repository in the following project.

mkdir code
cd code
git init
git remote add -f origin https://github.com/karenpayneoregon/code-samples-csharp
git sparse-checkout init --cone
git sparse-checkout add ExceptionHandling
git sparse-checkout add NuGetPackageBrowser
git pull origin master
:clean-up
del .gitattributes
del .gitignore
del .yml
del .editorconfig
del *.md
del *.sln

Copy the two projects to a Visual Studio solution then remove the temp folder above. At this point the last step prior to building these projects is to do a restore NuGet packages command by right clicking on solution explorer top node.

If this utility is appealing the following GitHub browser is coming in December of 2020. This utility provide the ability to traverse GitHub repositories with the ability to save favorites to a json file,