This is a challenge and the solution is quite complicated. It is also dependent on how your source control and development environment is set up. But there is a potential solution which I can explain.
In brief, you need a custom tool, which can just be a command line exe project, that will update the references in the csproj files without the need for all the manual changes.
Dev Environment set-up:
You will find that developers, as individuals, may all clone source to different locations on their machine. So each person has their code in a different place. This is ok as long as everyone has the same structure within a main folder so that the references can be added using a relative location.
EG, if everyone uses:
C:\Source\Common
C:\Source\App1\
Or:
C:\Code\Common
C:\Code\App1\
That's fine, but if the Common folder is called 'cmn' on someones machine, it will be a problem for them.
So now, in VS you can have a project reference in App1 to your Common project using a relative location, EG:
..\Common\Common.csproj
Project Type Issues:
You'll need to consider what type of projects you have. If you create a new project in Visual Studio and use .Net Framework 4.xx for example, you'll have what is now known as the 'old' project type. If you create with .Net Standard or .Net Core, you'll get the new project type (which is much nicer especially for doing all this nuget stuff).
The old project type has all nuget packages referenced in a packages.config for each project. It also has a reference to the actual project including exact path and version in the csproj. This is what makes the process more cumbersome.
The new project type has only a reference to the nuget packages in the csproj file (super duper).
But it doesn't matter if you still have the old project type, it is still possible for a custom tool to handle this.
The Tool:
This is just the basics, so not a full solution but hopefully you can get enough from this to implement.
Ensure you test this on a copy, or start a new branch first to ensure nothing is permanently broken. This will only cater for the old style project which I assume you're using.
Create a new Console Application project and add some basic classes for handling files:
PackageConfig.cs
[Serializable]
[DesignerCategory("code")]
[XmlType(AnonymousType = true, IncludeInSchema = true)]
[XmlRoot(ElementName = "packages", IsNullable = false)]
public class PackageConfig
{
[XmlElement("package")]
public Package[] Packages { get; set; }
}
[Serializable]
[DesignerCategory("code")]
public class Package
{
[XmlAttribute("id")]
public string Id { get; set; }
[XmlAttribute("version")]
public string Version { get; set; }
[XmlAttribute("targetFramework")]
public string TargetFramework { get; set; }
}
XmlSerializer.cx
class XmlSerializer
{
private System.Xml.Serialization.XmlSerializer GetSerializer<T>()
{
return new System.Xml.Serialization.XmlSerializer(typeof(T));
}
public string Serialize<T>(T instance, bool omitXmlDeclaration = false)
{
System.Xml.Serialization.XmlSerializer xmlSerializer = GetSerializer<T>();
StringBuilder stringBuilder = new StringBuilder();
XmlWriterSettings settings = new XmlWriterSettings
{
OmitXmlDeclaration = omitXmlDeclaration,
Encoding = Encoding.UTF8
};
using (XmlWriter writer = XmlWriter.Create(new StringWriter(stringBuilder), settings))
{
xmlSerializer.Serialize(writer, instance);
}
return stringBuilder.ToString();
}
public T Deserialize<T>(string xml)
{
System.Xml.Serialization.XmlSerializer xmlSerializer = GetSerializer<T>();
using (XmlTextReader reader = new XmlTextReader(new StringReader(xml)))
return (T)xmlSerializer.Deserialize(reader);
}
}
Update your program.cs as:
class Program
{
static void Main(string[] args)
{
try
{
string packageVersion = null;
if (args != null && args.length == 1)
packageVersion = args[0];
UpdateProjects(packageVersion);
}
catch (Exception ex)
{
Console.Error.WriteLine(ex.ToString());
}
}
const string _packageId = "Common";
delegate string UpdateContent(string content);
static void UpdateProjects(string packageVersion)
{
if (packageVersion == null)
UseLocalReferences();
else
UsePackageReferences(packageVersion);
}
static void UseLocalReferences()
{
RemoveNugetPackage();
ProcessProjects(content =>
{
content = RemoveReference(content);
content = AddReference(content, null);
return content;
});
}
static void UsePackageReferences(string packageVersion)
{
AddNugetPackage(packageVersion);
ProcessProjects(content =>
{
content = RemoveReference(content);
content = AddReference(content, packageVersion);
return content;
});
}
static void ProcessProjects(UpdateContent update)
{
string folder = Environment.CurrentDirectory;
string[] files = Directory.GetFiles(folder, "*.csproj", SearchOption.AllDirectories);
foreach (string fileName in files)
{
try
{
string content = File.ReadAllText(fileName);
content = update(content);
using (StreamWriter writer = new StreamWriter(fileName))
{
writer.Write(content);
}
Console.WriteLine("Updated: " + fileName);
}
catch (Exception ex)
{
Console.Error.WriteLine($"Error updating {fileName}:");
Console.Error.WriteLine(ex.ToString());
}
}
}
static void RemoveNugetPackage()
{
string folder = Environment.CurrentDirectory;
Console.WriteLine($"Updating all packages.config files under {folder}");
XmlSerializer xmlSerializer = new XmlSerializer();
string[] packageConfigFiles = Directory.GetFiles(folder, "packages.config", SearchOption.AllDirectories);
foreach (string packageConfigFile in packageConfigFiles)
{
PackageConfig packageConfig = xmlSerializer.Deserialize<PackageConfig>(File.ReadAllText(packageConfigFile));
List<Package> packages = new List<Package>(packageConfig.Packages);
if (packages.Any(x => x.Id == _packageId))
packages.First(x => x.Id == _packageId).Version = packageVersion;
else
packages.Add(new Package { Id = _packageId, Version = packageVersion, TargetFramework = "net462" }); // may need to change the framework here
packages.Sort((x, y) => string.Compare(x.Id, y.Id));
packageConfig.Packages = packages.ToArray();
File.WriteAllText(packageConfigFile, xmlSerializer.Serialize(packageConfig));
Console.WriteLine($"{packageConfigFile} updated");
}
Console.WriteLine("Update of packages.config files complete");
}
static void AddNugetPackage(string packageVersion)
{
string folder = Environment.CurrentDirectory;
Console.WriteLine($"Updating all packages.config files under {folder}");
XmlSerializer xmlSerializer = new XmlSerializer();
string[] packageConfigFiles = Directory.GetFiles(folder, "packages.config", SearchOption.AllDirectories);
foreach (string packageConfigFile in packageConfigFiles)
{
PackageConfig packageConfig = xmlSerializer.Deserialize<PackageConfig>(File.ReadAllText(packageConfigFile));
List<Package> packages = new List<Package>(packageConfig.Packages);
Package package = packages.FirstOrDefault(x => x.Id == _packageId);
if (package != null)
packages.Remove(package);
packageConfig.Packages = packages.ToArray();
File.WriteAllText(packageConfigFile, xmlSerializer.Serialize(packageConfig));
Console.WriteLine($"{packageConfigFile} updated");
}
Console.WriteLine("Update of packages.config files complete");
}
static string RemoveReference(string content)
{
string[] lines = content.Split(new string[] { "\r\n" }, StringSplitOptions.None);
StringBuilder sb = new StringBuilder();
bool removing = false;
foreach (string line in lines)
{
if (!removing && line.Trim().StartsWith($"<Reference Include=\"{_packageId}\""))
removing = true;
else if (removing)
{
if (line.Trim() == "</Reference>")
removing = false;
}
else
sb.AppendLine(line);
}
return sb.ToString();
}
static string AddReference(string content, string packageVersion)
{
string[] lines = content.Split(new string[] { "\r\n" }, StringSplitOptions.None);
StringBuilder sb = new StringBuilder();
foreach (string line in lines)
{
if (line.Trim() == "<Reference Include=\"System\" />") // find where the references need to be inserted
{
if (packageVersion == null)
{
sb.AppendLine($" <Reference Include=\"{_packageId}\">");
sb.AppendLine($" <SpecificVersion>False</SpecificVersion>");
sb.AppendLine($" <HintPath>..\\Common\\bin\\$(Configuration)\\{_packageId}.dll</HintPath>");
sb.AppendLine($" </Reference>");
}
else
{
sb.AppendLine($" <Reference Include=\"{_packageId}, Version = {packageVersion}, Culture = neutral, processorArchitecture = MSIL\">");
sb.AppendLine($" <HintPath>..\\packages\\{_packageId}.{packageVersion}\\lib\\net462\\{_packageId}.dll</HintPath>"); // again, framework may need updating
sb.AppendLine($" </Reference>");
}
}
sb.AppendLine(line);
}
return sb.ToString();
}
}
Using the tool:
When you compile the tool, copy the exe to your main project folder, EG C:\Source\App1 - then you can run it using:
updatetool
(to set the local references)
or
updatetool 1.0.1
(to set to the nuget package and references)
This is all untested but based on a solution I use for the same!