如何在解决方案中找到未使用的NuGet包?

时间:2013-11-13 10:27:59

标签: visual-studio nuget

如何在解决方案中找到未使用的NuGet包?

我有很多解决方案,其中有很多已安装的软件包,并且大量标记为已更新。

但是我担心可能会有重大变化,所以我首先要删除任何未使用的软件包。

8 个答案:

答案 0 :(得分:45)

ReSharper 2016.1有a feature to remove unused NuGet.

它可以在解决方案和解决方案中的每个项目上运行,它可以执行以下操作:

  1. 分析您的代码并收集对程序集的引用。
  2. 根据程序集的用法构建NuGet使用情况图。
  3. 没有内容文件,未使用且没有使用依赖项的软件包被假定为未使用并建议删除。
  4. 不幸的是,这不适用于project.json项目(RSRP-454515)和ASP.NET核心项目(RSRP-459076

答案 1 :(得分:19)

您可以使用Visual Studio扩展程序ResolveUR - Resolve Unused References

  

通过解决方案和项目节点解决方案资源管理器工具窗口中的菜单项,在Visual Studio 2012/2013项目中解析未使用的引用,包括nuget引用。

这不是一件容易的事,所以我建议在出现错误之前进行备份和/或提交。

答案 2 :(得分:7)

下面是一个小的 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

答案 3 :(得分:2)

您可以使用Resharper 2018.1完成此操作。

右键单击项目>重构>删除未使用的参考文献。

如果你的项目很小,你也可以>分析使用的参考文献。 。

会弹出一个窗口。选择所有引用并将其全部删除。然后返回并重新添加那些给你编译错误的那些。

答案 4 :(得分:2)

Visual Studio 2019(版本 16.9)内置了 remove-unused-packages 功能,我们现在需要手动启用它。

转到工具>选项>文本编辑器>C#>高级>(在分析部分下)勾选显示“已删除未使用的引用”命令

enter image description here

答案 5 :(得分:0)

我认为没有一种默认方式可以找到它。主要原因是这些包可以通过引用程序集到将源代码注入项目来实现各种功能。您可能想查看Nuget.Extensions。 codeplex上的以下主题讨论了nuget包的审计报告。

http://nuget.codeplex.com/discussions/429694

(NuGet已从Codeplex移至GitHub。上述链接存档:) https://web.archive.org/web/20171212202557/http://nuget.codeplex.com:80/discussions/429694

答案 6 :(得分:0)

这是手工劳动,但它有效。

  1. 使用ReSharper或类似的代码分析工具识别项目中任何未使用的引用,并在相应的项目中卸载nuget。

  2. 有时,卸载的nugets仍会停留在Manage NuGet Packages对话框的 Installed packages Updates 列表中。关闭Visual Studio,然后删除packages文件夹,然后重新打开解决方案并恢复您的nugets。

答案 7 :(得分:0)

在 Visual Studio 2019 中右键单击 Dotnet 核心项目,您将看到删除未使用引用的选项。 enter image description here