
Maintaining .Net Framework projects present an interesting dilemma. Let 'em rot, as vulnerabilities in the supporting assemblies appear inevitably? Tempting, but I think the right answer has this logic:
- It's hard to make an update to an old system which has dependencies that have not been maintained and maybe disappeared. If you keep it as current as possible, when the day comes that you need to make a change, it will be less hard. Even if a piece of software gets stuck in time, if you at least tried to keep it up to date, you know the limitations and can plan.
In the .Net world, your dependencies are usually an operating system or serverless platform and NuGet packages. It is obvious to me that .Net Framework is in maintenance mode. Many NuGet packages are not being maintained or tested as fully as they once were. Security updates are happening and some interoperability updates happen but they are cascading unevenly. More often than not, it is not possible to just update everything because one package or another is way behind in its dependencies and many packages are just depending on the latest. The .net (was core) world, is better, but it moves fast. Recently, he latest NuGet everything Microsoft did not work on a .net8 project, I had to upgrade to .net9 even though .net8 is still in Long Term Support.
Lately I have been seeing something crazy happen when I publish Web Site model solutions with Visual Studio. When I run locally everything is fine. But when I publish, 4 assemblies are replaced with older versions.
- System.Memory.dll
- System.Buffers.dll
- System.Text.Encodings.Web.dll
- System.Numerics.Vectors.dll
If no binding redirects were needed for these, there would be no problem. But in order to use latest version, binding redirects for all of these are required, so when lower versions replace the correct versions, these assemblies are not found. I believe, System.Text.Json.dll needs the newest. But here is the rub, System.Web which is always a system provided assembly, not a Nuget package, is the one that wants the older assemblies. But that doesn't explain what mechanism is replacing the existing assemblies with old versions, the binding redirects would work fine if that was not happening. I tried installing these 4 assemblies into the GAC. That solved ASPNETCOMPILER pre-compilation errors. And by the way this problem happens whether I precompile or not, it happens on publish from Visual Studio. ASPNETCOMPILER was just letting me know in advance that this was going to fail. So where were the assemblies coming from? Based on date stamp I figured out they were coming from the Visual Studio installation folder IDE\PublicAssemblies. Here is how I solved my problem. You probably do not want to do this and the main reason I am documenting this is so I won't forget what I did.
- I moved the 4 files from the Visual Studio installation folder IDE\PublicAssemblies to IDE\PrivateAssemblies (the folder wasn't there and so I created it and when I renamed 'New Folder' to PrivateAssemblies, it magically appeared full of files...)
- I copied the NuGet versions of these 4 files that I want to be used to the Visual Studio installation folder IDE\PublicAssemblies
- I edited the IDE\devenv.exe.conf to have these redirects:
<dependentAssembly>
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
<bindingRedirect oldVersion="4.0.0.0-4.0.2.0" newVersion="4.0.1.2"/>
<codeBase version="4.0.1.2" href="PrivateAssemblies\System.Memory.dll"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
<bindingRedirect oldVersion="4.0.0.0-4.0.3.0" newVersion="4.0.3.0"/>
<codeBase version="4.0.3.0" href="PrivateAssemblies\System.Buffers.dll"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Text.Encodings.Web" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
<bindingRedirect oldVersion="4.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
<codeBase version="8.0.0.0" href="PrivateAssemblies\System.Text.Encodings.Web.dll" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Numerics.Vectors" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-4.1.4.0" newVersion="4.1.4.0" />
<codeBase version="4.1.4.0" href="PrivateAssemblies\System.Numerics.Vectors.dll"/>
</dependentAssembly>
This lets the IDE load, but the mysterious process takes the PublicAssemblies versions.
I think a lot of people may be struggling with this and are told it is other things happening. I think this only happens with the asp.net webforms Web Site model. The advice I see people get, is use Web Application, Install transitive NuGet explicitly and use correct binding redirects. The .NET Framework 4.7.2 - 4.8.1 is starting to show neglect but I think I am stuck with that for a while for a handful of useful software projects.
Here is my favorite PowerShell way to find what version of a file I should put in a binding redirect:
[System.Reflection.Assembly]::LoadFile("$pwd\System.Buffers.dll").GetName().Version