diff --git a/src/CodingWithCalvin.ProjectRenamifier/Commands/RenamifyProjectCommand.cs b/src/CodingWithCalvin.ProjectRenamifier/Commands/RenamifyProjectCommand.cs index 094bda0..27a622c 100644 --- a/src/CodingWithCalvin.ProjectRenamifier/Commands/RenamifyProjectCommand.cs +++ b/src/CodingWithCalvin.ProjectRenamifier/Commands/RenamifyProjectCommand.cs @@ -8,6 +8,7 @@ using CodingWithCalvin.Otel4Vsix; using EnvDTE; using EnvDTE80; +using Microsoft.VisualStudio.Shell.Interop; namespace CodingWithCalvin.ProjectRenamifier { @@ -172,9 +173,11 @@ private void RenameProject(Project project, DTE2 dte) var stepIndex = 0; var projectRemovedFromSolution = false; var projectReaddedToSolution = false; - List referencingProjects = null; + List referencingProjects = null; Project parentSolutionFolder = null; + var vsSolution = ServiceProvider.GetService(typeof(SVsSolution)) as IVsSolution; + try { // Step 1: Collect projects that reference this project before removal @@ -230,7 +233,7 @@ private void RenameProject(Project project, DTE2 dte) // Step 9: Update references in projects that referenced this project ExecuteStep(progressDialog, stepIndex++, () => { - ProjectReferenceService.UpdateProjectReferences(referencingProjects, oldProjectFilePath, projectFilePath); + ProjectReferenceService.UpdateProjectReferences(vsSolution, referencingProjects, oldProjectFilePath, projectFilePath); }); // Step 10: Re-add project to solution, preserving solution folder location diff --git a/src/CodingWithCalvin.ProjectRenamifier/Services/ProjectReferenceService.cs b/src/CodingWithCalvin.ProjectRenamifier/Services/ProjectReferenceService.cs index 5c03ae5..0ac5747 100644 --- a/src/CodingWithCalvin.ProjectRenamifier/Services/ProjectReferenceService.cs +++ b/src/CodingWithCalvin.ProjectRenamifier/Services/ProjectReferenceService.cs @@ -2,6 +2,8 @@ using System.IO; using System.Xml; using EnvDTE; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell.Interop; namespace CodingWithCalvin.ProjectRenamifier.Services { @@ -10,17 +12,26 @@ namespace CodingWithCalvin.ProjectRenamifier.Services /// internal static class ProjectReferenceService { + /// + /// Metadata for a project that references the target project being renamed. + /// + public sealed class ReferencingProjectInfo + { + public string FullPath { get; set; } + public string UniqueName { get; set; } + } + /// /// Finds all projects in the solution that reference the specified project. /// /// The solution to search. /// The full path to the project being renamed. - /// A list of project paths that reference the target project. - public static List FindProjectsReferencingTarget(Solution solution, string targetProjectPath) + /// A list of referencing project descriptors (full path + unique name). + public static List FindProjectsReferencingTarget(Solution solution, string targetProjectPath) { ThreadHelper.ThrowIfNotOnUIThread(); - var referencingProjects = new List(); + var referencingProjects = new List(); var targetFileName = Path.GetFileName(targetProjectPath); foreach (Project project in solution.Projects) @@ -34,7 +45,7 @@ public static List FindProjectsReferencingTarget(Solution solution, stri /// /// Recursively searches a project (and solution folders) for references to the target. /// - private static void FindReferencesInProject(Project project, string targetProjectPath, string targetFileName, List referencingProjects) + private static void FindReferencesInProject(Project project, string targetProjectPath, string targetFileName, List referencingProjects) { ThreadHelper.ThrowIfNotOnUIThread(); @@ -67,7 +78,11 @@ private static void FindReferencesInProject(Project project, string targetProjec { if (ProjectReferencesTarget(project.FullName, targetFileName)) { - referencingProjects.Add(project.FullName); + referencingProjects.Add(new ReferencingProjectInfo + { + FullPath = project.FullName, + UniqueName = project.UniqueName, + }); } } } @@ -118,18 +133,77 @@ private static bool ProjectReferencesTarget(string projectFilePath, string targe /// /// Updates project references in all projects that referenced the old project path. + /// Each referencing project is temporarily unloaded via so Visual Studio + /// releases its file handle before we rewrite the .csproj on disk, then reloaded afterwards. /// - /// Projects that need their references updated. + /// The Visual Studio solution service used to unload and reload projects. + /// Projects that need their references updated. /// The old path to the renamed project. /// The new path to the renamed project. - public static void UpdateProjectReferences(List referencingProjectPaths, string oldProjectPath, string newProjectPath) + public static void UpdateProjectReferences(IVsSolution vsSolution, List referencingProjects, string oldProjectPath, string newProjectPath) { + ThreadHelper.ThrowIfNotOnUIThread(); + var oldFileName = Path.GetFileName(oldProjectPath); + var solution4 = vsSolution as IVsSolution4; + + foreach (var info in referencingProjects) + { + UpdateSingleProjectReference(vsSolution, solution4, info, oldFileName, oldProjectPath, newProjectPath); + } + } + + private static void UpdateSingleProjectReference( + IVsSolution vsSolution, + IVsSolution4 solution4, + ReferencingProjectInfo info, + string oldFileName, + string oldProjectPath, + string newProjectPath) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + var projectGuid = System.Guid.Empty; + var unloaded = false; + + if (solution4 != null && TryGetProjectGuid(vsSolution, info.UniqueName, out projectGuid)) + { + var hr = solution4.UnloadProject(ref projectGuid, (uint)_VSProjectUnloadStatus.UNLOADSTATUS_UnloadedByUser); + unloaded = ErrorHandler.Succeeded(hr); + } - foreach (var projectPath in referencingProjectPaths) + try { - UpdateReferencesInProject(projectPath, oldFileName, oldProjectPath, newProjectPath); + UpdateReferencesInProject(info.FullPath, oldFileName, oldProjectPath, newProjectPath); } + finally + { + if (unloaded) + { + solution4.ReloadProject(ref projectGuid); + } + } + } + + private static bool TryGetProjectGuid(IVsSolution vsSolution, string uniqueName, out System.Guid projectGuid) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + projectGuid = System.Guid.Empty; + + if (string.IsNullOrEmpty(uniqueName)) + { + return false; + } + + var hr = vsSolution.GetProjectOfUniqueName(uniqueName, out var hierarchy); + if (!ErrorHandler.Succeeded(hr) || hierarchy == null) + { + return false; + } + + hr = vsSolution.GetGuidOfProject(hierarchy, out projectGuid); + return ErrorHandler.Succeeded(hr) && projectGuid != System.Guid.Empty; } ///