如何在解决方案中找到未使用的NuGet包?
我有很多解决方案,其中安装了很多包,其中很多都被标记为有更新。
但是,我担心可能会有破坏性的更改,因此我首先想通过删除任何未使用的包来进行清理。
如何在解决方案中找到未使用的NuGet包?
我有很多解决方案,其中安装了很多包,其中很多都被标记为有更新。
但是,我担心可能会有破坏性的更改,因此我首先想通过删除任何未使用的包来进行清理。
我不认为有一个默认的方法来发现这一点。主要原因是这些包可以做各种各样的事情,从引用程序集到向项目注入源代码。你可能想检查一下Nuget。扩展。codeplex上的以下线程讨论了nuget包的审计报告。
http://nuget.codeplex.com/discussions/429694
(NuGet已从Codeplex移动到GitHub。以上档案链接:) https://web.archive.org/web/20171212202557/http://nuget.codeplex.com:80/discussions/429694
这是体力劳动,但很有效。
使用ReSharper或类似的代码分析工具来识别项目中任何未使用的引用,并卸载相应项目中的nuget。 有时卸载的NuGet仍然停留在“管理NuGet包”对话框中的“已安装包”和“更新”列表中。关闭Visual Studio,然后删除packages文件夹,然后重新打开解决方案并恢复您的块。
你可以使用Visual Studio扩展ResolveUR -解析未使用的引用。
通过解决方案和项目节点的菜单项解决方案资源管理器工具窗口解决Visual Studio 2012/2013/2015项目中未使用的引用,包括nuget引用
这不是一项容易的任务,所以我建议在此之前进行备份和/或提交,以便在出现错误时进行回滚。
ReSharper 2016.1有一个功能,删除未使用的NuGet。
它可以在解决方案上运行,也可以在解决方案中的每个项目上运行,它做以下事情:
分析代码并收集对程序集的引用。 基于程序集的使用构建NuGet使用图。 没有内容文件、未使用本身和未使用依赖项的包被假定为未使用并建议删除。
不幸的是,这对项目不起作用。json项目(RSRP-454515)和ASP。NET核心项目(RSRP-459076)
你可以使用ReSharper 2019.1.1来实现。
右键单击项目>重构>删除未使用的引用。
如果你的项目很小,你也可以使用:项目>优化使用的引用…
弹出一个窗口。选择所有引用并删除它们。然后返回并重新添加那些导致编译器错误的代码。
下面是一个小PowerShell脚本,它为。net Core / . net 5+项目查找多余的NuGet包。对于每个项目文件,它删除每个引用一次,并检查它是否编译。这要花很多时间。在此之后,您将得到可能被排除的每个引用的摘要。最后,应该删除哪些内容是由你决定的。大多数情况下,您不可能删除它建议的所有内容(由于依赖关系),但它应该为您提供一个良好的起点。
将下面的脚本保存为ps1文件,并将第89行中的字符串C:\MySolutionDirectory替换为您想要扫描的目录,然后运行ps1文件。先做备份,以防出现问题。
function Get-PackageReferences {
param($FileName, $IncludeReferences, $IncludeChildReferences)
$xml = [xml] (Get-Content $FileName)
$references = @()
if($IncludeReferences) {
$packageReferences = $xml | Select-Xml -XPath "Project/ItemGroup/PackageReference"
foreach($node in $packageReferences)
{
if($node.Node.Include)
{
if($node.Node.Version)
{
$references += [PSCustomObject]@{
File = (Split-Path $FileName -Leaf);
Name = $node.Node.Include;
Version = $node.Node.Version;
}
}
}
}
}
if($IncludeChildReferences)
{
$projectReferences = $xml | Select-Xml -XPath "Project/ItemGroup/ProjectReference"
foreach($node in $projectReferences)
{
if($node.Node.Include)
{
$childPath = Join-Path -Path (Split-Path $FileName -Parent) -ChildPath $node.Node.Include
$childPackageReferences = Get-PackageReferences $childPath $true $true
$references += $childPackageReferences
}
}
}
return $references
}
function Get-ProjectReferences {
param($FileName, $IncludeReferences, $IncludeChildReferences)
$xml = [xml] (Get-Content $FileName)
$references = @()
if($IncludeReferences) {
$projectReferences = $xml | Select-Xml -XPath "Project/ItemGroup/ProjectReference"
foreach($node in $projectReferences)
{
if($node.Node.Include)
{
$references += [PSCustomObject]@{
File = (Split-Path $FileName -Leaf);
Name = $node.Node.Include;
}
}
}
}
if($IncludeChildReferences)
{
$projectReferences = $xml | Select-Xml -XPath "Project/ItemGroup/ProjectReference"
foreach($node in $projectReferences)
{
if($node.Node.Include)
{
$childPath = Join-Path -Path (Split-Path $FileName -Parent) -ChildPath $node.Node.Include
$childProjectReferences = Get-ProjectReferences $childPath $true $true
$references += $childProjectReferences
}
}
}
return $references
}
$files = Get-ChildItem -Path C:\MySolutionDirectory -Filter *.csproj -Recurse
Write-Output "Number of projects: $($files.Length)"
$stopWatch = [System.Diagnostics.Stopwatch]::startNew()
$obseletes = @()
foreach($file in $files) {
Write-Output ""
Write-Output "Testing project: $($file.Name)"
$rawFileContent = [System.IO.File]::ReadAllBytes($file.FullName)
$childPackageReferences = Get-PackageReferences $file.FullName $false $true
$childProjectReferences = Get-ProjectReferences $file.FullName $false $true
$xml = [xml] (Get-Content $file.FullName)
$packageReferences = $xml | Select-Xml -XPath "Project/ItemGroup/PackageReference"
$projectReferences = $xml | Select-Xml -XPath "Project/ItemGroup/ProjectReference"
$nodes = @($packageReferences) + @($projectReferences)
foreach($node in $nodes)
{
$previousNode = $node.Node.PreviousSibling
$parentNode = $node.Node.ParentNode
$parentNode.RemoveChild($node.Node) > $null
if($node.Node.Include)
{
$xml.Save($file.FullName)
if($node.Node.Version)
{
$existingChildInclude = $childPackageReferences | Where-Object { $_.Name -eq $node.Node.Include -and $_.Version -eq $node.Node.Version } | Select-Object -First 1
if($existingChildInclude)
{
Write-Output "$($file.Name) references package $($node.Node.Include) ($($node.Node.Version)) that is also referenced in child project $($existingChildInclude.File)."
continue
}
else
{
Write-Host -NoNewline "Building $($file.Name) without package $($node.Node.Include) ($($node.Node.Version))... "
}
}
else
{
$existingChildInclude = $childProjectReferences | Where-Object { $_.Name -eq $node.Node.Include } | Select-Object -First 1
if($existingChildInclude)
{
Write-Output "$($file.Name) references project $($node.Node.Include) that is also referenced in child project $($existingChildInclude.File)."
continue
}
else
{
Write-Host -NoNewline "Building $($file.Name) without project $($node.Node.Include)... "
}
}
}
else
{
continue
}
dotnet build $file.FullName > $null
if($LastExitCode -eq 0)
{
Write-Output "Building succeeded."
if($node.Node.Version)
{
$obseletes += [PSCustomObject]@{
File = $file;
Type = 'Package';
Name = $node.Node.Include;
Version = $node.Node.Version;
}
}
else
{
$obseletes += [PSCustomObject]@{
File = $file;
Type = 'Project';
Name = $node.Node.Include;
}
}
}
else
{
Write-Output "Building failed."
}
if($null -eq $previousNode)
{
$parentNode.PrependChild($node.Node) > $null
}
else
{
$parentNode.InsertAfter($node.Node, $previousNode.Node) > $null
}
# $xml.OuterXml
$xml.Save($file.FullName)
}
[System.IO.File]::WriteAllBytes($file.FullName, $rawFileContent)
dotnet build $file.FullName > $null
if($LastExitCode -ne 0)
{
Write-Error "Failed to build $($file.FullName) after project file restore. Was project broken before?"
return
}
}
Write-Output ""
Write-Output "-------------------------------------------------------------------------"
Write-Output "Analyse completed in $($stopWatch.Elapsed.TotalSeconds) seconds"
Write-Output "$($obseletes.Length) reference(s) could potentially be removed."
$previousFile = $null
foreach($obselete in $obseletes)
{
if($previousFile -ne $obselete.File)
{
Write-Output ""
Write-Output "Project: $($obselete.File.Name)"
}
if($obselete.Type -eq 'Package')
{
Write-Output "Package reference: $($obselete.Name) ($($obselete.Version))"
}
else
{
Write-Output "Project refence: $($obselete.Name)"
}
$previousFile = $obselete.File
}
你可以在这里找到更多信息:https://devblog.pekspro.com/posts/finding-redundant-project-references
Visual Studio 2019(版本16.9)内置了删除未使用的包功能,我们现在需要手动启用它。
进入工具>选项>文本编辑器> c# >高级>(在分析部分下)勾选显示“删除未使用的引用”命令
Visual Studio 16.10版本提供了删除未使用的引用特性。右键单击项目>删除未使用的引用。
在Visual Studio 2019中,从最新版本和Visual Studio 2022开始,您可以删除之前评论中报道的未使用的包,但仅限于SDK风格的项目。 如果你尝试旧的项目,比如。net Framework,你不会看到这个选项。 为了验证,你可以创建两个简单的控制台应用:一个使用。net Core或更高版本,另一个使用。net Framework 4.7或4.8。
请参考:删除未使用的引用