22using System . IO ;
33using System . Xml ;
44using EnvDTE ;
5+ using Microsoft . VisualStudio ;
6+ using Microsoft . VisualStudio . Shell . Interop ;
57
68namespace CodingWithCalvin . ProjectRenamifier . Services
79{
@@ -10,17 +12,26 @@ namespace CodingWithCalvin.ProjectRenamifier.Services
1012 /// </summary>
1113 internal static class ProjectReferenceService
1214 {
15+ /// <summary>
16+ /// Metadata for a project that references the target project being renamed.
17+ /// </summary>
18+ public sealed class ReferencingProjectInfo
19+ {
20+ public string FullPath { get ; set ; }
21+ public string UniqueName { get ; set ; }
22+ }
23+
1324 /// <summary>
1425 /// Finds all projects in the solution that reference the specified project.
1526 /// </summary>
1627 /// <param name="solution">The solution to search.</param>
1728 /// <param name="targetProjectPath">The full path to the project being renamed.</param>
18- /// <returns>A list of project paths that reference the target project .</returns>
19- public static List < string > FindProjectsReferencingTarget ( Solution solution , string targetProjectPath )
29+ /// <returns>A list of referencing project descriptors (full path + unique name) .</returns>
30+ public static List < ReferencingProjectInfo > FindProjectsReferencingTarget ( Solution solution , string targetProjectPath )
2031 {
2132 ThreadHelper . ThrowIfNotOnUIThread ( ) ;
2233
23- var referencingProjects = new List < string > ( ) ;
34+ var referencingProjects = new List < ReferencingProjectInfo > ( ) ;
2435 var targetFileName = Path . GetFileName ( targetProjectPath ) ;
2536
2637 foreach ( Project project in solution . Projects )
@@ -34,7 +45,7 @@ public static List<string> FindProjectsReferencingTarget(Solution solution, stri
3445 /// <summary>
3546 /// Recursively searches a project (and solution folders) for references to the target.
3647 /// </summary>
37- private static void FindReferencesInProject ( Project project , string targetProjectPath , string targetFileName , List < string > referencingProjects )
48+ private static void FindReferencesInProject ( Project project , string targetProjectPath , string targetFileName , List < ReferencingProjectInfo > referencingProjects )
3849 {
3950 ThreadHelper . ThrowIfNotOnUIThread ( ) ;
4051
@@ -67,7 +78,11 @@ private static void FindReferencesInProject(Project project, string targetProjec
6778 {
6879 if ( ProjectReferencesTarget ( project . FullName , targetFileName ) )
6980 {
70- referencingProjects . Add ( project . FullName ) ;
81+ referencingProjects . Add ( new ReferencingProjectInfo
82+ {
83+ FullPath = project . FullName ,
84+ UniqueName = project . UniqueName ,
85+ } ) ;
7186 }
7287 }
7388 }
@@ -118,18 +133,77 @@ private static bool ProjectReferencesTarget(string projectFilePath, string targe
118133
119134 /// <summary>
120135 /// Updates project references in all projects that referenced the old project path.
136+ /// Each referencing project is temporarily unloaded via <see cref="IVsSolution4"/> so Visual Studio
137+ /// releases its file handle before we rewrite the .csproj on disk, then reloaded afterwards.
121138 /// </summary>
122- /// <param name="referencingProjectPaths">Projects that need their references updated.</param>
139+ /// <param name="vsSolution">The Visual Studio solution service used to unload and reload projects.</param>
140+ /// <param name="referencingProjects">Projects that need their references updated.</param>
123141 /// <param name="oldProjectPath">The old path to the renamed project.</param>
124142 /// <param name="newProjectPath">The new path to the renamed project.</param>
125- public static void UpdateProjectReferences ( List < string > referencingProjectPaths , string oldProjectPath , string newProjectPath )
143+ public static void UpdateProjectReferences ( IVsSolution vsSolution , List < ReferencingProjectInfo > referencingProjects , string oldProjectPath , string newProjectPath )
126144 {
145+ ThreadHelper . ThrowIfNotOnUIThread ( ) ;
146+
127147 var oldFileName = Path . GetFileName ( oldProjectPath ) ;
148+ var solution4 = vsSolution as IVsSolution4 ;
149+
150+ foreach ( var info in referencingProjects )
151+ {
152+ UpdateSingleProjectReference ( vsSolution , solution4 , info , oldFileName , oldProjectPath , newProjectPath ) ;
153+ }
154+ }
155+
156+ private static void UpdateSingleProjectReference (
157+ IVsSolution vsSolution ,
158+ IVsSolution4 solution4 ,
159+ ReferencingProjectInfo info ,
160+ string oldFileName ,
161+ string oldProjectPath ,
162+ string newProjectPath )
163+ {
164+ ThreadHelper . ThrowIfNotOnUIThread ( ) ;
165+
166+ var projectGuid = System . Guid . Empty ;
167+ var unloaded = false ;
168+
169+ if ( solution4 != null && TryGetProjectGuid ( vsSolution , info . UniqueName , out projectGuid ) )
170+ {
171+ var hr = solution4 . UnloadProject ( ref projectGuid , ( uint ) _VSProjectUnloadStatus . UNLOADSTATUS_UnloadedByUser ) ;
172+ unloaded = ErrorHandler . Succeeded ( hr ) ;
173+ }
128174
129- foreach ( var projectPath in referencingProjectPaths )
175+ try
130176 {
131- UpdateReferencesInProject ( projectPath , oldFileName , oldProjectPath , newProjectPath ) ;
177+ UpdateReferencesInProject ( info . FullPath , oldFileName , oldProjectPath , newProjectPath ) ;
132178 }
179+ finally
180+ {
181+ if ( unloaded )
182+ {
183+ solution4 . ReloadProject ( ref projectGuid ) ;
184+ }
185+ }
186+ }
187+
188+ private static bool TryGetProjectGuid ( IVsSolution vsSolution , string uniqueName , out System . Guid projectGuid )
189+ {
190+ ThreadHelper . ThrowIfNotOnUIThread ( ) ;
191+
192+ projectGuid = System . Guid . Empty ;
193+
194+ if ( string . IsNullOrEmpty ( uniqueName ) )
195+ {
196+ return false ;
197+ }
198+
199+ var hr = vsSolution . GetProjectOfUniqueName ( uniqueName , out var hierarchy ) ;
200+ if ( ! ErrorHandler . Succeeded ( hr ) || hierarchy == null )
201+ {
202+ return false ;
203+ }
204+
205+ hr = vsSolution . GetGuidOfProject ( hierarchy , out projectGuid ) ;
206+ return ErrorHandler . Succeeded ( hr ) && projectGuid != System . Guid . Empty ;
133207 }
134208
135209 /// <summary>
0 commit comments