Lately, I was working on a tool that verifies if a given binary could load the needed dependencies at runtime, for .NET Framework binaries the resolution of dependencies is pretty straightforward as mentioned here. For .NET Core i am still confused about the way CoreCLR locates and loads assemblies. As i know at the moment is that CoreCLR search in three different places :
- Application base directory
- Shared directory
- Nuget cache
Resolution process relies on the presence of three Json files:
- *.runtimeconfig.json file which specifies which version of .NET Core the application depends
{
"runtimeOptions": {
"tfm": "netcoreapp3.1",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "3.1.0"
}
}
}
- *.runtimeconfig.dev.json that contains additional probing paths
{
"runtimeOptions": {
"additionalProbingPaths": [
"C:\\Users\\yjirari\\.dotnet\\store\\|arch|\\|tfm|",
"C:\\Users\\yjirari\\.nuget\\packages"
]
}
}
- *.deps.json that lists dependencies of the application and their relative path to the Nuget cache
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v3.1",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v3.1": {
"ConsoleApp1/1.0.0": {
"dependencies": {
"Newtonsoft.Json": "12.0.1"
},
"runtime": {
"ConsoleApp1.dll": {}
}
},
"Newtonsoft.Json/12.0.1": {
"runtime": {
"lib/netstandard2.0/Newtonsoft.Json.dll": {
"assemblyVersion": "12.0.0.0",
"fileVersion": "12.0.1.25517"
}
}
}
}
},
"libraries": {
"ConsoleApp1/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Newtonsoft.Json/12.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==",
"path": "newtonsoft.json/12.0.1",
"hashPath": "newtonsoft.json.12.0.1.nupkg.sha512"
}
}
}
The *.runtimeconfig.json purpose is pretty obvious which is allowing the runtime to know which version of .NET Core the application was built against.
For *.runtimeconfig.dev.json is to add Nuget cache and other directories as additional probing directories But for .deps i am still confused about its goal.
To clarify the role of .deps, i did a little experiment using a console project that depends on Newtonsoft.Json v 12.0.1.
There are two main cases:
- When .deps is present in bin folder:
If Newtonsoft.Json with same or superior version (e.g. v12, v13) exists also in bin, the application executes normally. Else the Newtonsoft.Json gets loaded from Nuget cache.
- When the .deps is absent:
If Newtonsoft.Json with same or superior version exists also in bin, the application executes normally. Else a FileLoadException is raised
Conclusion :
I concluded from the result of my experiment that :
- if the dependency is present in the bin folder with the same version or superior the dependency gets loaded.
- The role of .deps appears only if the dependency doesn't exist in the bin folder, that helps loading dependency from the Nuget cache using the path of Nuget cache in *.runtimeconfig.dev.json concatenated to relative path specified in .deps.
My Questions are the following:
- Why CoreClr loads dependencies with superior version of that referenced by the project ?
- Is there any role of *.deps file while resolving dependencies other than locating assmeblies in Nuget cache ?
- Is the CoreCLR parses the .deps before searching for dependencies?
I saw this two documents on resolution of CoreCLR but i didn't get too much about the role of .deps.
https://github.com/dotnet/cli/blob/v2.0.0/Documentation/specs/corehost.md https://learn.microsoft.com/en-us/dotnet/core/dependency-loading/default-probing