Build a project for .NET Core 3. Add some nuget package.
Create a "lib" subdirectory in the output folder.
Move some/all of the .dll files into "lib".
Program will fail to run, as it is unable to locate the moved assemblies.
When building a program using the standard framework, which used app.exe.config
for its runtime configuration, you could specify private subdirectories that would be searched when resolving assembly loading:
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="lib" />
</assemblyBinding>
</runtime>
I have been unable to find any directives that would reproduce that behavior in the new app.runtimeconfig.json
configuration. The runtime spec documentation only mentions searching the root application directory, and doesn't indicate any means of getting the runtime to search alternate directories (even with the presumed restriction of only being allowed to search private subdirectories).
Use of additionalProbingPaths
in app.runtimeconfig.json
and app.runtimeconfig.dev.json
does not seem to work.
dotnet --info
output:
.NET Core SDK (reflecting any global.json):
Version: 3.0.100-preview6-012264
Commit: be3f0c1a03
Runtime Environment:
OS Name: Windows
OS Version: 6.1.7601
OS Platform: Windows
RID: win7-x64
Base Path: C:\Program Files\dotnet\sdk\3.0.100-preview6-012264\
Host (useful for support):
Version: 3.0.0-preview6-27804-01
Commit: fdf81c6faf
.NET Core SDKs installed:
1.1.14 [C:\Program Files\dotnet\sdk]
2.1.602 [C:\Program Files\dotnet\sdk]
2.1.604 [C:\Program Files\dotnet\sdk]
2.1.700 [C:\Program Files\dotnet\sdk]
2.1.800-preview-009696 [C:\Program Files\dotnet\sdk]
2.2.202 [C:\Program Files\dotnet\sdk]
2.2.204 [C:\Program Files\dotnet\sdk]
2.2.300 [C:\Program Files\dotnet\sdk]
2.2.400-preview-010219 [C:\Program Files\dotnet\sdk]
3.0.100-preview6-012264 [C:\Program Files\dotnet\sdk]
.NET Core runtimes installed:
Microsoft.AspNetCore.All 2.1.9 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.1.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.2.3 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.All 2.2.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.App 2.1.9 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.1.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.2.3 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 2.2.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.0.0-preview6.19307.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 1.0.16 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 1.1.13 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.9 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.11 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.2.3 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.2.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.0.0-preview6-27804-01 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 3.0.0-preview6-27804-01 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
I do not believe this is possible at in .Net Core.
@peterhuene to confirm or see if he has an idea here for this.
If you are trying to simplify the output of your application, have you looked at the single executable option?
If you are trying to simplify the output of your application, have you looked at the single executable option?
I've considered it, but a 60 MB release isn't acceptable compared to something that should be less than 1 MB. The issue is the noise as more nuGet packages are added. They're tiny in size, but add huge numbers of extra files that really don't need to be polluting the root directory.
As far as I'm aware, this is currently not a supported feature of the host.
There is a runtimeOptions.additionalProbingPaths
option for runtimeconfig.json
, but the probing is done by the library (e.g. package) path and then asset's path within the library.
Perhaps the host could support an additional set of probing paths to consider when doing "local" dependency resolution, in addition to the application's root directory.
@vitek-karas would this be something that the host might support for the future?
They're tiny in size, but add huge numbers of extra files that really don't need to be polluting the root directory.
Sounds like you want the exe in the root folder and everything else in the bin folder, similar issue: https://github.com/dotnet/core-setup/issues/5120
The apphost supports loading a DLL file located in another folder, but the msbuild files don't allow you to change the name. it defaults to <asm>.dll
with apphost renamed to <asm>.exe
.
In general .NET Core has a different model for the apps - the runtime/host is following the .deps.json
file to learn where to look for files, it does very little probing.
I'm wondering what is the root problem here and what is the scenario:
The ideal solution would be to modify the .deps.json
to point to the files in the subfolder, unfortunately this is currently broken in the host (for searches done in the app folder, the relative path specified is ignored). I don't know the exact reason why the code works that way unfortunately (it's been like that for a long time). If there's enough interest I'm not opposed to somehow tweaking the host to make this work - but it would still require either manual modification of the .deps.json
or SDK support (ideally SDK support).
Just to list other options, not that I would recommend either:
.deps.json
(not sure if SDK has a good way to do this though) and implementing a handler for the AssemblyLoadContext.Default.Resolving
event where you find them manually.APP_PATHS
runtime property.So in short, currently I'm not aware of a good way to support this...
I would still be interested in the overall scenario and why you're trying to do this - as it will help us decide what to do about this going forward.
Why is it important to have small file size in the root folder, but it's OK to have the rest in a subfolder?
It's not. I'm saying that the individual files are already tiny in size, so I'm not wanting to go a route that negates that (ie: a single exe or self-contained), but that it's not desirable to have them all in the root folder.
The deps.json
file wouldn't actually need to be modified. The relative paths in that file (eg: "lib/netstandard2.0/HtmlAgilityPack.dll"
) are fine as a subdirectory of the root folder. I'd just need to create one additional subdirectory level and move files into that, as well as handle files that go into different subdirectories (eg: "lib/netcoreapp3.0/Microsoft.Extensions.DependencyInjection.dll"
). It would be a mild annoyance to make sure everything is in the right place, but the deployment would be clean. If those files could be placed there automatically on build or publish, that would be a nice convenience, but it can be done as a post-build event on my end, so is not necessary for the time being. It's only the loading part on program run that's out of my hands.
Alternatively, the option for a property analogous to the original .config <probing privatePath="">
element seems like a relatively trivial change, but would require the addition of a new property, which is probably less desirable than making use of the existing information in deps.json
.
I would still be interested in the overall scenario and why you're trying to do this - as it will help us decide what to do about this going forward.
Primarily it's about the end-user experience for an xcopy deployment. The less noise to sort through in the root folder, the less stress there is on the end user to find the executable, and the less in-your-face complexity is shown for what is (or should be) viewed as a simple program. Dependency injection and configuration abstractions and logging extensions and whatever else may make things easier on the programming side, but they're big scary words to the average end-user who thinks he's getting something simple. They're an aggravation that doesn't need to be there. Sort of like code formatting guidelines — not strictly necessary for functionality, but a way to help reduce stress of the users.
It's likely to only start becoming noticeable now, because of .NET Core 3's addition of WPF and WinForms. Since those necessarily had to be built using the standard Framework prior to this, and that already had the convenience of the <probing>
element, there wasn't much need to consider it in Core.
@Kinematics thanks for the response.
I'm looking into some idea which might be a possible work around... meanwhile I'm curious about your single-file statement.
As noted, a single-exe deployment is not desirable because of size issues.
I don't really understand this. Single-file can also be framework dependent in which case its size is nearly identical to your current build output.
OK - I might have a workaround, but it's not pretty.
I tried a simple app created like this:
dotnet new console
dotnet add package Newtonsoft.Json
dotnet build
Then I went into the bin/Debug/netcoreapp3.0
folder (the build output) and I moved the Newtonsoft.Json.dll into a subfolder bin\newtonsoft.json\12.0.2\lib\netstandard2.0
(all under the netcoreapp3.0
folder. Then I modified the .runtimeconfig.dev.json
by removing all the paths in it (as that would make it find the library in nuget caches) and just added one path bin
.
Then I ran the app with current directory set int he netcorreapp3.0
folder - and it works.
This relies on several things:
additionalProbingPaths
, so that you can run the app from anywhere, but it means the current directory must be the folder with the app itself. If that is not possible, you would have to specify a full path there, but that might be different on different machines, so you would need some kind of installer to set it up. Without the above bug fix I'm not aware of a way to specify an application relative path.bin
myself - it can be really anything. Alternatively it's possible to not have a subdirectory, but then the root would contain multiple subdirectories (one for each nuget package basically)newtonsoft.json\12.0.2\lib\netstandard2.0\Newtonsoft.Json.dll
) is dictated by the .deps.json
. The first part is the path of the library
(near the bottom of the file), in this case it's the newtonsoft.json/12.0.2
and typically it will be the name of the NuGet package and the version of it (the NuGet version!). The second part is the relative path in the runtime
section which is basically path inside the NuGet package. This may differ package to package, but typically it's something like lib/<tfm>/assemblyname.dll
where tfm stands for Target Framework Moniker - so typically netstandard2.0
or netcoreapp2.0
or similar.Alternatively you can specify the additionalProbingPath
on the command line app.exe --additionalprobingpath <path>
but it is basically the same thing, has the same limitations.
It's definitely not pretty, but if you need this and can't use single-file it should work (a bit tedious to setup the folder structure though).
It's likely to only start becoming noticeable now, because of .NET Core 3's addition of WPF and WinForms. Since those necessarily had to be built using the standard Framework prior to this, and that already had the convenience of the
<probing>
element, there wasn't much need to consider it in Core.
I totally agree that before UI apps this problem is much less likely to occur. That said .NET Core doesn't have a simple counterpart to "probing paths":
.deps.json
and the SDK to produce these artifacts which are consumed at runtime - trying to reduce the complexity of the assembly resolution logic in the runtime..deps.json
is that it can express things which normal probing paths really can't. It's the one thing which enables portable apps - that is applications which can run on any platform - the same binaries. For platform specific components, it can express the dependency, so for example it can say that a native library A
is in subfolder win64
when running on Windows and is in subfolder linux
when running on Linux..deps.json
is necessary..deps.json
for that as well.I'm not saying this solution is perfect, but it's at least consistent across all application types. That said if the "probing path" paradigm is something common for UI apps, we will definitely look into providing an easier way to achieve the desired outcome. That's why I'm trying to understand the scenario you're facing, because the "probing path" itself is just a technical solution - I want to understand the problem first, before we commit to a solution.
I don't really understand this. Single-file can also be framework dependent in which case its size is nearly identical to your current build output.
OK, attempts that I've made had all generated the self-contained ~60 MB output. I'll take another look at this to see if I can get it done properly as framework dependent.
... OK, took a bit to figure out how (documentation seems a bit scarce), but I managed to get it to compile. Unfortunately it has two problems:
hostfxr.dll
file, which isn't part of the build.Then I went into the bin/Debug/netcoreapp3.0 folder (the build output) and I moved the Newtonsoft.Json.dll into a subfolder bin\newtonsoft.json\12.0.2\libnetstandard2.0 (all under the netcoreapp3.0 folder. Then I modified the .runtimeconfig.dev.json by removing all the paths in it (as that would make it find the library in nuget caches) and just added one path bin.
OK. A bit ugly, but yes, it works. I can also put the "bin" path in the runtimeconfig.template.json
file to put it in the normal runtimeconfig.json
file on publish, and that works fine. It will require manual maintenance when package versions are updated, and more complicated work in post-build, but it's doable.
And, while it might be a nuisance for me, I can see wanting to keep using the version directories from deps.json
for the future possibility of improving versioning resolution (eg: being able to load two different versions of an assembly because of different versions used between an app and one of the app's packages). That fits in with the other reasons to want to keep using the deps.json
data.
For a future consideration, maybe have an option to specify a 'local' package repository directory (ie: "bin" subdirectory) that the build process can store the dependent assemblies in, in the same format as the common nuget directories. That would also assist in the potential versioning issues, where you may need access to two (or more) different versions of the same package, which would be a complete mess if you just dumped everything into the root directory. Though that gets into an entirely different mess of issues. (The versioning issue is on my mind because of Jon Skeet's recent blog post on the topic.)
Overall, that would solve the messy root directory problem, while also opening up more flexibility in assembly resolution.
Thanks - could you please file a new issue on the problem with single-file failing to run? That sounds like a bug.
Could you please file a new issue on the problem with single-file failing to run? That sounds like a bug.
While trying to create a testcase, I realized I'd specified the win-x86
RID, since it errored out when trying just win
(which is what the RID Catalog docs seem to imply would be generically usable), but it failed because I have the 64 bit version of .NET Core installed, not the 32 bit version. So that's where the error message was coming from. When I use win-x64
it runs fine.
I'm not sure if there's a known issue for not being able to specify a generic Windows build.
That workaround is ugly and requires too much work.
@Kinematics I've successfully removed all files from the root folder and put them in the bin folder. The only thing remaining are the apphost exe files. Nothing else. I already mentioned it above but here's some more info.
The apphost exe (eg. myapp.exe
if your entry point assembly is myapp.dll
) has the path to your main dll file. You can patch the apphost exe so instead of having the path myapp.dll
in it, it's bin\myapp.dll
. When the exe is started, it will load your file from the bin folder. This is the patcher I use. What's left is just moving the output to the bin folder and moving the apphost exe to the root and you're done.
Moving your files to a bin folder is a common request and the SDK should have support for this. All it needs to do is move all files to the bin folder and make sure the apphost exe loads the file from the bin folder.
@Kinematics In .NET Core it's not possible to create an executable which would work on both x86 and x64 (and ran natively as 32bit or 64bit). The executable is just like any other native executable and thus has to be tied to a specific platform. The fact that in .NET Framework it was possible to have an .exe which ran on either x86 or x64 was achieved by tight integration with the Windows OS (the OS loader actually knows about managed apps and does special things). For various reasons we didn't want to introduce a similar component for .NET Core.
The error message in this case is not ideal as it doesn't provide guidance of what to do instead unfortunately. I filed https://github.com/dotnet/sdk/issues/3401 for this.
@0xd4d That is definitely another approach although it has its own issues as well. The main problem is that it's somewhat confusing what the application base path going to be (is it the folder with the .exe or the one with the main .dll?). But I agree that in terms of work required it's relatively simpler.
The bug in the host where it ignores the relative path for app directory lookups is tracked here: https://github.com/dotnet/core-setup/issues/5645.
See more customer scenarios described in dotnet/sdk#3405
try this tool NetCoreBeauty
@vitek-karas
Hello, I've been browsing through several of these similar issues and don't see any recent updates. Has this issue been addressed at all in .Net 5?
I am also in a scenario where I have a large number of dll references but single-file is not an option. Essentially, I have a project with a large amount of shared business logic and multiple front-ends. If I use the standard verbose deployment, the separate interfaces can all share the same dlls (which saves space). If I use single-file, then each interface gets its own embedded copy of the shared code, which balloons the install size
Sadly, it has not. But the tools mentioned in this topic are still working.
There's no nice solution in .NET 5. I think that if this should happen it should be an SDK based solution. Basically some way for the SDK to specify that parts of the build output should go into a subdirectory. And then SDK would generate the right .deps.json
(and would require a fix in the hosting layer). Fixing just the hosting layer is desirable, but on its own it doesn't make the solution simpler - it would still require custom tool to process the .deps.json
.
Most helpful comment
That workaround is ugly and requires too much work.
@Kinematics I've successfully removed all files from the root folder and put them in the bin folder. The only thing remaining are the apphost exe files. Nothing else. I already mentioned it above but here's some more info.
The apphost exe (eg.
myapp.exe
if your entry point assembly ismyapp.dll
) has the path to your main dll file. You can patch the apphost exe so instead of having the pathmyapp.dll
in it, it'sbin\myapp.dll
. When the exe is started, it will load your file from the bin folder. This is the patcher I use. What's left is just moving the output to the bin folder and moving the apphost exe to the root and you're done.Moving your files to a bin folder is a common request and the SDK should have support for this. All it needs to do is move all files to the bin folder and make sure the apphost exe loads the file from the bin folder.