I currently have an issue with loading assemblies at runtime using Assembly.LoadFrom(String).
While the specified assembly is loaded just fine, referenced third-party assemblies (e.g. nuget packages) are not loaded when the targeted framework is either netcoreapp or netstandard.
To figure out the problem i have created a simple solution consisting of three projects.
Each project contains exactly one class.
I'm using Newtonsoft.Json as a nuget example here but it could be any other assembly.
ClassLibrary0.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net20;netstandard1.0</TargetFrameworks>
</PropertyGroup>
</Project>
namespace ClassLibrary0 {
public class Class0 {
public System.String SomeValue { get; set; }
}
}
ClassLibrary1.csproj
Has a package reference to Newtonsoft.Json via nuget.
Has a reference to additional assembly ClassLibrary0 depending on TargetFramework (shitty conditional ItemGroups).
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net20;net35;net40;net45;net451;net452;net46;net461;net462;net47;net471;net472;netstandard1.0;netstandard1.1;netstandard1.2;netstandard1.3;netstandard1.4;netstandard1.5;netstandard1.6;netstandard2.0;netcoreapp1.0;netcoreapp1.1;netcoreapp2.0;netcoreapp2.1</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net20' OR '$(TargetFramework)'=='net35' OR '$(TargetFramework)'=='net40' OR '$(TargetFramework)'=='net45' OR '$(TargetFramework)'=='net451' OR '$(TargetFramework)'=='net452' OR '$(TargetFramework)'=='net46' OR '$(TargetFramework)'=='net461' OR '$(TargetFramework)'=='net462' OR '$(TargetFramework)'=='net47' OR '$(TargetFramework)'=='net471' OR '$(TargetFramework)'=='net472'">
<Reference Include="ClassLibrary0">
<HintPath>..\net20\ClassLibrary0.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='netstandard1.0' OR '$(TargetFramework)'=='netstandard1.1' OR '$(TargetFramework)'=='netstandard1.2' OR '$(TargetFramework)'=='netstandard1.3' OR '$(TargetFramework)'=='netstandard1.4' OR '$(TargetFramework)'=='netstandard1.5' OR '$(TargetFramework)'=='netstandard1.6' OR '$(TargetFramework)'=='netstandard2.0'">
<Reference Include="ClassLibrary0">
<HintPath>..\netstandard1.0\ClassLibrary0.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='netcoreapp1.0' OR '$(TargetFramework)'=='netcoreapp1.1' OR '$(TargetFramework)'=='netcoreapp2.0' OR '$(TargetFramework)'=='netcoreapp2.1'">
<Reference Include="ClassLibrary0">
<HintPath>..\netstandard1.0\ClassLibrary0.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
namespace ClassLibrary1 {
public class Class1 {
public System.String SomeValue { get; set; }
public Class1() {
var tmp = new ClassLibrary0.Class0();
var tmp2 = new Newtonsoft.Json.DefaultJsonNameTable();
}
}
}
ClassLibrary2.csproj
Has a project reference to ClassLibrary1.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net20;net35;net40;net45;net451;net452;net46;net461;net462;net47;net471;net472;netstandard1.0;netstandard1.1;netstandard1.2;netstandard1.3;netstandard1.4;netstandard1.5;netstandard1.6;netstandard2.0;netcoreapp1.0;netcoreapp1.1;netcoreapp2.0;netcoreapp2.1</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ClassLibrary1\ClassLibrary1.csproj" />
</ItemGroup>
</Project>
namespace ClassLibrary2 {
public class Class2 {
public System.String SomeValue { get; set; }
public Class2() {
var tmp = new ClassLibrary1.Class1();
}
}
}
After running dotnet restore and rebuilding the solution the root problem can be observed in the output directories:
The Problem:
Copies of ClassLibrary0.dll are present in all output directories (=> references to third-party are good).
Copies of ClassLibrary1.dll are present in all output directories of ClassLibrary2 (=> project references are good too).
Copies of Newtonsoft.Json are only present in net output directories but are missing in all netcoreapp and netstandard.
All netcoreapp and netstandard output directories contain a *.deps.json file that correctly mentions the Newtonsoft.Json package as a dependency.
A call to Assembly.LoadFrom(String) however won't load these dependencies to Newtonsoft.Json in case of netcoreapp and netstandard.
This results in FileNotFoundException at runtime after running code from the specified loaded assemblies.
What i've tried:
I am trying to resolve those by attaching to the AppDomain.AssemblyResolve event but so far i'm out of luck.
Those *.deps.json don't contain a location path of the dependency.
I've tried looking for the assembly in all the locations within the Path environment variable but the nuget package location doesn't seem to be listed there.
The location on all my machines seems to be %userprofile%\.nuget\packages\package-name\version\.
However i'm not 100% positive that this will always be the correct location for nuget packages on all machines that might execute my code.
The actual question:
Is there a solid way to resolve nuget dependencies at runtime when manually loading assemblies?
Restrictions:
This needs to be an offline solution, no downloading of package versions on the fly.
Cannot rely on <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> in the original projects.
Cannot rely on me having references to the dependencies in question.
The whole point of doing this is to be able to dynamically load assemblies that i can't know about at compile time.
I have solved the problem by writing my own NuGet package resolver, which looks for the appropriate package at runtime. I haven't had the time for a proper documentation yet but it's already on my plate. Resolving at runtime requires to attach to AppDomain.AssemblyResolve with something like that:
private Assembly OnAssemblyResolve(Object sender, ResolveEventArgs args) {
if(AssemblyResolver.Nuget.TryResolve(args, out IEnumerable<FileInfo> files)) {
foreach(FileInfo file in files) {
if(AssemblyHelper.TryLoadFrom(file, out Assembly assembly)) {
return assembly;
}
}
}
return null;
}
This requires the use of my NuGet package which contains the resolver and some helpers. There is also an article that goes into the details and design decisions of the resolver.
I realize that dotnet publish will also copy any dependencies but this is a special edge case.
Related
I have a Visual Studio 2022 solution, with multiple projects, but four in particular are interesting here.
Provider1 which is based on .NET Framework 4.
Provider2 which is based on .NET 6.
Provider1Test which is based on .NET Framework 4.
Provider2Test which is based on .NET 6.
The Provider1 project has a number of classes, all in the Provider.Data namespace, one of them being Class1. This is my source code. The Provider1.csproj file:
<ItemGroup>
<Compile Include="Class1.cs">
<SubType>Code</SubType>
</Compile>
...
</ItemGroup>
The Class1.cs file:
namespace Provider.Data
{
public class Class1
{
...
}
}
The Provider2 project has links to these source files, i.e. "Add"->"Existing item"->"As link". It compiles with different conditional compilation symbols, so the output is not the same as for the Provider1 project.
The Provider2.csproj file:
<ItemGroup>
<Compile Include="..\Provider1\Class1.cs" Link="Class1.cs" />
</ItemGroup>
The Provider1Test project is an NUnit test project, that tests Provider1. It has multiple test classes, one of them is TestClass1.
The Provider2Test project is also a NUnit test project, with a ProjectReference to Provider2. It links to the test classes in Provider1Test in the same way as the source code does. The Provider2Test.csproj file:
<ItemGroup>
<ProjectReference Include="..\Provider2\Provider2.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\Provider1Test\TestClass1.cs" Link="TestClass1.cs" />
</ItemGroup>
The TestClass1.cs file:
using Provider.Data;
namespace ProviderTests
{
public class TestClass1
{
...
}
}
Now, this builds and runs just fine inside Visual Studio, but if I navigate to the Provider2Test folder and try to build with the dotnet build command, it doesn't find the source code.
C:\dev\DataProvider\Provider2Test>dotnet build
MSBuild version 17.3.1+2badb37d1 for .NET
Determining projects to restore...
All projects are up-to-date for restore.
Provider2 -> C:\dev\DataProvider\Provider2\bin\x64\Debug\net6.0\Provider.Data.dll
1 file(s) copied.
C:\dev\DataProvider\Provider1Test\TestClass1.cs(14,7): error CS0246: The type or namespace name 'Provider' could not be found (are you missing a using directive or an assembly reference?) [C:\dev\DataProvider\Provider2Test\Provider2Test.csproj]
Build FAILED.
What is the issue here, why doesn't dotnet build follow the reference path here, and how do I solve it?
I tried to create a TestClass2.cs file directly in Provider2Test, that is not a link but a standard compile include, and also using the Provider.Data namespace. It produces the same error.
I found a workaround, so I'm posting it here and I'm going with it for now, but I don't think it's a good solution, and it doesn't explain the original issue, so I'm not going to mark this as the accepted answer.
In Provider2.csproj, I added that if it is built with dotnet build, it has a post-build event that copies its source code dll to Provider2Test. This is not run if the project is build within Visual Studio ("$(MSBuildRuntimeType)" == "Full").
if "$(MSBuildRuntimeType)" == "Core" XCOPY "$(OutDir)Provider.Data.dll" "$(ProjectDir)..\Provider2Test\$(OutDir)" /Y /F
In Provider2Test.csproj I added a conditional assembly reference.
<ItemGroup>
<Reference Include="Provider.Data" Condition="$(MSBuildRuntimeType) == 'Core'">
<HintPath>$(OutDir)Provider.Data.dll</HintPath>
</Reference>
</ItemGroup>
I kept the ProjectReference in all cases (both "Full" and "Core"), in order to trigger a Provider2 build whenever Provider2Test is built.
I am developing a .net5.0 web api and i am getting the following error while using DinkToPdf:
DllNotFoundException: Unable to load DLL 'libwkhtmltox' or one of its dependencies: The specified module could not be found. (0x8007007E)
I have followed this tutorial, with a few exceptions:
added the service added the service, which was not done in the tutorial
services.AddSingleton(typeof(IConverter),
new SynchronizedConverter(new PdfTools()));
...
services.AddScoped<IPdfService, PdfService>();
named the services differently, but that shouldn't matter
installed it via NuGet instead of Install-Package DinkToPdf
my project is just an API, frontend is not in C#, shouln't matter sincer the error is here:
return this._converter.Convert(htmlToPdfDocument);
Did everything else like in the tutorial.
For me adding this into the csproj file resolved the issue -
<ItemGroup>
<None Remove="libwkhtmltox.dll" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="libwkhtmltox.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
What is happening there is you are missing the dlls under your project directory.
you can get them here DinkToPdf dlls and include them in your project.
You can run below and it will install dlls into bin. Make sure copy those dlls into root directory
Install-Package DinkToPdfCopyDependencies -Version 1.0.8
You can also try using this package to include the dependencies:
https://www.nuget.org/packages/DinkToPdfCopyDependencies
I have an "old style" .NET framework project which includes nuget references and other project references. Now I switched to the PackageReference format (removed the packages.config). I want to create a NuGet package for my project. So I added a reference to the "NuGet.Build.Tasks.Pack" package and used the MSBUILD pack target. In the first place it looked everything as expected, the resulting package contains all my references and the corresponding NuGet references. Now I have the problem, that I use also a project to project reference:
<ProjectReference Include="..\Wrapper\MyWrapper\MyWrapper.csproj">
<Project>{6b9a7dd0-b93f-3a5e-8fdf-99d0bf811652}</Project>
<Name>MyWrapper</Name>
</ProjectReference>
Based on the nuget documentation - for this reference:
Project to project references are considered by default as nuget
package references
But I want that this project reference is packaged into my package instead of a "nuget package reference". I found postings that using
PrivateAssets="all"
for the project reference could fix the problem, but adding this attribute to the project reference node does not change anything. Any help would be great!
I think you have missed something. It is not enough that you set PrivateAssets="all" for the ProjectReference. And actually, nuget will not view the referenced project as a nuget dependency and also nuget will not pack its assembly dll into the nupkg. You need other nodes.
Try these guidances:
Assume all your lib projects are target to net framework 4.7.2.
1) add the PrivateAssets="all" on the xxx.csproj file of the main project.
<ProjectReference Include="..\Wrapper\MyWrapper\MyWrapper.csproj">
<Project>{6b9a7dd0-b93f-3a5e-8fdf-99d0bf811652}</Project>
<Name>MyWrapper</Name>
<PrivateAssets>All</PrivateAssets>
</ProjectReference>
2) also add these node on the xxx.csproj file of the main project to pack the assembly dll into the nupkg:
<PropertyGroup>
<TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage</TargetsForTfmSpecificBuildOutput>
</PropertyGroup>
<Target Name="CopyProjectReferencesToPackage" DependsOnTargets="ResolveReferences">
<ItemGroup>
<BuildOutputInPackage Include="#(ReferenceCopyLocalPaths->WithMetadataValue('ReferenceSourceTarget', 'ProjectReference'))" />
</ItemGroup>
</Target>
3) then use this command to pack the project:
msbuild -t:rebuild,pack -p:PackageOutputPath=xxx\xxx -p:Authors="xxx" -p:Version="x.x.x"
Note: In my side, the main project called Mod and it references a project called Mod1. When I finish the pack process, you can see these in the nupkg.
It packs the refeneced dll as a lib rather than a nuget package.
In my solution I have a project "commons" and project has reference like SpecFlow.CustomPlugin. When I build the project Com.Org.Commons.dll will get generated.
But when I refer this .dll file to another project (please look into attached image solution structure
)
"SFP" which has class NUnitPropertyGenerator.cs also need reference of SpecFlow.CustomPlugin which is already included in commons project.
I build the project commons and Com.Org.Commons.dll will get generated. But when I include the Com.Org.Commons.dll into SFP project below code gives me error and it doesn't refer to Com.Org.Commons.dll.
using TechTalk.SpecFlow.Generator.Plugins;
using TechTalk.SpecFlow.Generator.UnitTestProvider;
using TechTalk.SpecFlow.Infrastructure;
[assembly: GeneratorPlugin(typeof(Com.Org.SFP.CustomNUnitProperties.SpecFlow.NUnitPropertyGenerator))]
namespace Com.Org.SFP.CustomNUnitProperties.SpecFlow
{
public class NUnitPropertyGenerator : IGeneratorPlugin
{
public void Initialize(GeneratorPluginEvents generatorPluginEvents, GeneratorPluginParameters generatorPluginParameters)
{
generatorPluginEvents.CustomizeDependencies += (sender, args) =>
{
args.ObjectContainer.RegisterTypeAs<MasterProvider, IUnitTestGeneratorProvider>();
};
}
}
}
I thought TechTalk.SpecFlow will get refered if I include Com.Org.Commons.dll in SFP project which internally referes SpecFlow.CustomPlugin package.
Expected result should be:
SFP project should successfully build after including the Com.Org.Commons.dll and should resolve the code error which are related to TechTalk.SpecFlow. Logically both the project required SpecFlow.CustomPlugin package but as I am seperated out the details implementation to commons project and considering common project has a reference package included in dependencies I should be able to resolve the error in SFP project after referencing the Com.Org.Commons.dll in SFP project.
Please find .csproj file content
commons.csproj (https://gist.github.com/gittadesushil/100df50d4de72d61a9d57aa08c82cada)
SFP.csproj (https://gist.github.com/gittadesushil/dda1af31b5351f6ef9c71e44e2ceccda)
If you need to use the namespaces/classes defined in SpecFlow.CustomPlugin DLL, you will need to add a reference to that DLL directly in all projects where you are using its classes directly in code. For e.g., TechTalk.SpecFlow.Infrastructure looks like a namespace from SpecFlow.CustomPlugin that's being used in SFP. In this case SFP needs to have a reference to SpecFlow.CustomPlugin
If you were not using TechTalk.SpecFlow.Infrastructure directly in SFP then all you would need to do was to ensure that the CopyLocal property of the SpecFlow.CustomPlugins reference in commons was set to true.
You are using a normal Reference (https://gist.github.com/gittadesushil/dda1af31b5351f6ef9c71e44e2ceccda#file-sfp-csproj-L25) and not a ProjectReference.
correct would be
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net45</TargetFramework>
<RootNamespace>Com.Org.SFP.CustomNUnitProperties.SpecFlow</RootNamespace>
<AssemblyName>Com.Org.SFP.CustomNUnitProperties.SpecFlow.2.4.SpecFlowPlugin</AssemblyName>
<PackageId>$(AssemblyName)</PackageId>
<Description>$(PackageId)</Description>
<OutputPath>$(SolutionDir)Output\</OutputPath>
<DocumentationFile></DocumentationFile>
<IsTool>true</IsTool>
<BuildOutputTargetFolder>tools\SpecFlowPlugin.2-4</BuildOutputTargetFolder>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>full</DebugType>
<DebugSymbols>true</DebugSymbols>
<GenerateSerializationAssemblies>Auto</GenerateSerializationAssemblies>
<WarningLevel>0</WarningLevel>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="Com.Org.Commons" />
</ItemGroup>
</Project>
Normal References don't look at the dependency graph.
I am trying to make distinction between these two concepts in .NET Core terminology. I will try to illustrate my confusion with an example.
When I create a new class library project (eg: dotnet new classlib -o myclasslib) the generated .csproj file looks like this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
</Project>
So far so good. Now, if I try to add WebApi controller class in this project (eg. for the purpose of dynamically loading plugins in my main WebApi application created as: dotnet new webapi -o mywebapi) I would need to use things like ControllerBase class and [ApiController] and [HttpGet] attributes. To keep things simple I just derive MyController from ControllerBase like this:
using System;
namespace myclasslib
{
public class MyController : Microsoft.AspNetCore.Mvc.ControllerBase
{
}
}
Trying to build this with dotnet build I get error:
error CS0234: The type or namespace name 'AspNetCore' does not exist
in the namespace 'Microsoft' (are you missing an assembly reference?)
That's kind of expected because I created classlib project, but if change SDK in .csproj to Sdk="Microsoft.NET.Sdk.Web" and also change TargetFramework to netcoreapp2.2 (hoping to resolve the reference to ControllerBase class) like this:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
</PropertyGroup>
</Project>
I get the same error when building the project. Isn't SDK supposed to include everything I need to build the project?
If I create usual webapi project (eg. dotnet new webapi -o mywebapi) the generated .csproj looks like this:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All" />
</ItemGroup>
</Project>
I notice the SDK is same as the one I set, but there is also a metapackage added: <PackageReference Include="Microsoft.AspNetCore.App" />
So why do we need to explicitly add metapackage if we already specified that we want to use Microsoft.NET.Sdk.Web?
One additional question: what version of metapackage is used if we don't specify one in PackageReference (like in this generated webapi .csproj)?
The SDK is just the build tooling and the .NET Core framework itself. ASP.NET Core is a set of NuGet packages. Essentially .NET Core framework != ASP.NET Core code. The concept of metapackages is only tangentially related. What could be called "ASP.NET Core" is actually dozens of individual NuGet packages. You could reference each individually, but as you can imagine, that would be both tiresome and error prone. Metapackages are essentially a NuGet package that depends on multiple other NuGet packages.
By pulling in just the metapackage, therefore, essentially all of that metapackage's dependencies are also pull in. As a result, you can simply add a package reference for Microsoft.AspNetCore.App and you're off to the races. However, the downside to that is that you're potentially getting dependencies that you don't actually need. That's not that big of an issue with something like a web app, because the dependencies can be trimmed, but a class library should not have excess dependencies. As such, you should only reference individual NuGet packages you actually need from the Microsoft.AspNetCore namespace.