Issue with nested DLL referencing in a self-contained .Net Core web app running as a Windows service - asp.net-core

The Error
First off, lets start with the error message:
System.BadImageFormatException: 'Could not load file or assembly 'System.Management, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. Reference assemblies should not be loaded for execution. They can only be loaded in the Reflection-only loader context. (0x80131058)'
In my experience, this type of error usually occurs if the DLL is missing, or it's not in the expected format/version. However, I cannot work out what I need to do to resolve this error.
The Setup
The main project is an ASP.Net Core 3.1 application.
It is configured to be able to run as a Window service and support Web API and MVC frameworks. So my Program.cs looks something like this:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args)
{
var builder = Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
builder.UseWindowsService();
return builder;
}
}
That part all works great, I can run the app as a Windows service and serve razor views and web API endpoints, etc.
However, there are more projects in this solution. I have a couple of .Net 4.6 class libraries projects that are referenced by the service app. So my solution looks something like this:
Service App (NET CORE 3.1)
Lib 1 (NET 4.6)
Lib 2 (NET 4.6)
Service App references Lib 1, and Lib 1 references Lib 2.
Any code is either of these two libraries works fine and can be used by the service app.
The problem is that Lib 2 references System.Management DLL, and this is what is causing the error. Whenever I use a function that uses that DLL, I get the error above.
The Attempts So Far
My attempts so far have including:
Setting the DLL to "copy to output"
Adding the DLL reference directly to the web service app
Manually copying the DLL to the output folder. Both debug, and publish folder (note: publish is set to "self contained" application)
The Question
Basically, how can I resolve this issue? Or at least, how can I further debug the problem to potentially find a solution.

I can't repro this at the time, but I suspect this to be a bug in the way that the self-contained publication process using dotnet publish resolves transitive dependencies.
The point is, your .NET Core 3.1 application doesn't run on .NET Framework, while the library you reference, call it "Lib 2", does.
And your Lib 2 has a reference to the NuGet package System.Management, which, when installed on a project targeting .NET Framework ... does nothing, except making your project reference the .NET Framework reference assembly for System.Management, found in C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1. At runtime, the .NET Framework will load the appropriate assembly from the GAC, but the .NET Core runtime does not do this.
Now when publishing your .NET Core app, MSBuild walks through the dependencies to be copied, and (again, I suppose) looks at the reference assembly and goes "I want that one!" and copies it to your output directory.
But you can't run reference assemblies, you can just reference them, so at runtime, your application blows up with an exception stating exactly that:
Reference assemblies should not be loaded for execution
A workaround, but definitely not a solution, is to reference the same System.Management package from your .NET Core application, where the package will actually extract a DLL containing runnable code, and that one will be copied to your output directory. But this might cause other issues, such as the DLL being overwritten during build with the reference one.
I'd suggest looking at GitHub whether this is a known issue, or that this is something caused by your project setup.

Related

How do I expose a .netstandard2.0 library with COM for use in VB6?

I have a dotnet core library, a framework 4.7.2 library and a vb6 application.
I want to write a common library for them all to access and so choose .netstandard2.0
I tried a the 4.7.2 framework wrapper library between .netstandard2.0 library and vb6.
However I ran into assembly binding problems
Looking at the docs I see
In .NET Core, the process for exposing your .NET objects to COM has been significantly streamlined in comparison to .NET Framework.
However no mention .netstandard2.0
I decided to try following the docs anyway even though my project is using .netstandard2.0
I got up to the instructions on Generating the COM Host in which case the output files ProjectName.dll, ProjectName.deps.json, ProjectName.runtimeconfig.json and ProjectName.comhost.dll should build.
However the ProjectName.comhost.dll and ProjectName.runtimeconfig.json do not create.
I see in this dotnet standard issue that Microsoft plans on having tooling support in "Preview 4"
I am running VS 16.4.5
[Update]
I decided to try making a .net core wrapper library and enabling it for com.
I was able to add my .netstandard to the wrapper library via a nuget package (I build the .netstandard library using azure devops)
When I build my wrapper library the .dll, .deps.json, .pdb, .runtimeconfig.dev.json and .runtimeconfig.json files are created in a bin\Debug\netcoreapp3.1 folder.
However none of the .netstandard library files appear in the bin\debug folder.
I copied the .netstandard library and the .netcore wrapper libraries to the same folder and ran
regsvr32 MyCoreComWrapper.comhost.dll
However no .tlb file is created which I need to be able to use from VB6
I note the following in the docs
Unlike in .NET Framework, there is no support in .NET Core for
generating a COM Type Library (TLB) from a .NET Core assembly. The
guidance is to either manually write an IDL file or a C/C++ header for
the native declarations of the COM interfaces.
I found some information on github but would love a step by step guide to making the .tlb
I thought about using latebinding instead but am unsure of how to use it with a com library.
[Update]
I put a sample project on GitHub including some VB6 files.
With VB6 referencing the .tlb referenced with the framework library.
When I try to run that I get
Could not load file or assembly 'Microsoft.EntityFrameworkCore, Version=3.1.2.0,
Culture=neutral, PublicKeyToken=adb9793829ddae60' or one of its dependencies. The system cannot find the file specified.
So I copied all the files from my framework test project to my vb6 folder, rebuilt and ran.
Then I got the error
Could not load file or assembly 'Microsoft.Extensions.DependencyInjection.Abstractions, Version=3.1.0.0,
Culture=neutral, PublicKeyToken=adb9793829ddae60' or one of its dependencies. The system cannot find the file specified.
I see the file Microsoft.Extensions.DependencyInjection.dll is present with File version 3.100.220.6706
Regarding the .NET standard, I may be wrong but I think this is not applicable here because the COM interop stuff are at a higher level than the one .NET standard is targeting; we can only talk about either .NET Core or .NET Framework for COM interop.
If you want to generate a type library, you have few options.
By far, the easiest method is just to use .NET Framework. The fact that you are wanting to create a type library negates the advantages of .NET Core already because several COM, especially the "Automation" features are Windows-only. Using framework will be fine at least until .NET Core 5 comes out.
That said, if you have a business reason for using .NET Core but still need COM support, including the type library, then based on this GitHub comment, you should be able to compile your own IDL. Note that requires you to install C++ build tools because the MIDL compiler is not really a standalone thing that you can get without the rest of the C++ build tools.
It is strongly suggested to have had read the documentation on how .NET Core handles COM activation.
Assuming having the C++ build tools is not a barrier for you, the steps would be the following:
1) Create a .idl file that defines all your COM interfaces in the IDL format. That requires some translation between the .NET interface and the COM interface. Here's a partial example of how you'd need to translate between your C# interface and COM interface as defined in IDL:
[
Guid("<some gooey>"),
InterfaceType(ComInterfaceType.InterfaceIsDual)
]
public interface IFoo
{
string Bar { get; }
string Baz(int Fizz);
}
Would be translated into IDL:
[
uuid(<assembly gooey>),
version(1.0)
]
library myFoo
{
[
uuid(<some gooey>),
object,
dual
]
interface IFoo : IDispatch {
[propget] HRESULT Bar([out, retval] BSTR* retVal);
HRESULT Baz([in] long Fizz, [out, retval] BSTR* retVal);
}
}
Once you've defined the .idl file and it is an accurate representation, you can then use MIDL to compile the .idl file into a .tlb file. Usually something like midl foo.idl /tlb: foo.tlb. You should make use of the MIDL language reference to help you write the .idl file. As a quick way to get started, you could copy your C# interfaces to a .NET framework project, use tlbexp, then use oleview (available via Visual Studio Developer Command Prompt) or olewoo to view the resulting IDL file to get you started.
The next step is to then create registry keys so that your CLSID can reference the type library. You will need to have your assembly's GUID handy and it must be used as the library's uuid in the .idl file as well.
Using IFoo interface example, you would need to create the registry similar to below (using .reg format for easy sharing/comprehension and assuming per-user installation, rather than per-machine):
Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\Software\Classes\Interface\{<some gooey>}]
#="IFoo"
[HKEY_CURRENT_USER\Software\Classes\Interface\{<some gooey>}\ProxyStubClsid32]
#="{00020424-0000-0000-C000-000000000046}"
[HKEY_CURRENT_USER\Software\Classes\Interface\{<some gooey>}\TypeLib]
#="{assembly gooey}"
"Version"="1.0"
You will also need to create the registry in the CLSID, Interface, TypeLib, and Record as needed. This article provides a good overview of all registry keys but keep in mind it's assuming .NET framework, not .NET Core, so not all keys are applicable, especially under the CLSID branch.
Note that when you run the regsvr32, it will normally create the keys in the CLSID and Interface branches but you will need to add the TypeLib keys under the Interface's branch and also an entry to the TypeLib branch. You also will need to create the ProgId keys, too if you want to support CreateObject functionality.
Initially, you can start with just a .reg file that you can manually update & maintain but if you have several objects, then it becomes desirable to automate this. This can be also managed via the DllRegisterServer call so that when you execute regsvr32, it will take care of registering the keys. On the other hand, you're now polluting your codebase with registration code. Some elect to use installers to do the registry keys write instead.
I hope that helps you get started!
The issue is due to assembly binding resolution that fails when ran from VB6 (IDE or compiled .exe file).
Here are the steps to solve it:
Compile the VB project, for example, let's assume the compiled file is Project1.exe.
Copy all .NET assemblies (including x86 and x64 directories, and languages directory if localized version is important) aside the compiled VB6 file
Now run Project1.exe, you will get an error like this:
The error is clearly a mismatch between the version of your assemblies aside the Project1.exe file and the version of referenced assemblies (not references you've created yourself but reference embedded in these assemblies... ). You don't see that when you start a .NET program because resolution is a very complex process that depends on a lot of parameters (and it's not getting any better with .NET Core, Framework, Standard, nugets, etc.).
To futher check it's a mismatch error, you can also use the Fuslogvw.exe (Assembly Binding Log Viewer) tool from the SDK.
Now we know it's an assembly version mismatch issue, what you can do is create a file named Project1.exe.config aside Project1.exe and add assembly binding redirects to it.
The easiest way to configure it is to redirect all possible versions to the ones present in the directory that contains your program, so in your case (and as of today, as all these can evolve...), it would be something like this, possibly for every assembly you reference directly or indirectly:
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
...
<dependentAssembly>
<assemblyIdentity name="Microsoft.Extensions.DependencyInjection.Abstractions" publicKeyToken="adb9793829ddae60" />
<!-- 3.1.2.0 is the version of the assembly you ship -->
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="3.1.2.0" />
</dependentAssembly>
...
</assemblyBinding>
</runtime>
</configuration>
Unfortunately, there are many satellite assemblies, and it's a bit tedious to create all redirects with correct information, so I've created a tool that creates a .config file with the redirects configured automatically for all .NET assemblies in a given directory: https://github.com/smourier/BindingRedirectGenerator.
If you want it to work for the VB6 IDE too, you'll have to use the same procedure in a VB6.exe.config file aside VB6.exe.
A reminder to myself
Use a demo UI to access the original DLL to confirm the call works. ( if you can't get it to work skip to making the App.Config for the unit test project using BindingRedirectGenerator )
Add a unit test in the com visible project to confirm the
call works.
Copy all the dlls created by both projects to the
release folder
For each com visible dll run as Administrator
c:\windows\microsoft.net\framework\v4.0.30319\regasm /verbose /codebase /tlb:MyLibrary.tlb c:\myproject\releasedlls\MyLibrary.dll
Install BindingRedirectGenerator to c:\brg say
At the command prompt change directory to c:\brg
BindingRedirectGenerator c:\myproject\releasedlls App.config
Rename App.config to MyVB6Project.exe.config and copy it to the same folder as MyVB6Project.exe
Remember to set up the files for the vb6.exe folder if you want to run it in the vb6 ide
Put the whole process in a script for future use ( I used a .bat)
Keep an eye on what nuget has put in app.config
Pay attention to the yellow warnings at build time!

(System.IO.FileNotFoundException) .Net core Web API cannot find child dependency (Dependencies of dependency) when added DLL by "Add Reference"

I am getting System.IO.FileNotFoundException in my .Net Core Web API. So I've set up the below project to demonstrate the problem.
I created a.Net Standard library named DemoLibrary and added QRCoder dependency via NuGet.
Disclaimer: Reason for choosing the QRCoder is that the Web API doesn't use it by default. I don't use it in my project. In fact, I'm getting this exception for EntityFrameworkCore.
I created a new .Net Core Web API DemoWebAPI which has no other dependencies.
Then added the DemoLibrary to DemoWebAPI via Add Reference -> Browse -> DemoLibrary.dll.
This is my solution:
The DemoMethod method in Calculate class just creates the object of QRCodeGenerator.
public class Calculate
{
public static string DemoMethod()
{
QRCodeGenerator qrGenerator = new QRCodeGenerator();
return "";
}
}
And my ValuesController in DemoWebAPI just calls the method:
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2", DemoLibrary.Calculate.DemoMethod() };
}
Now, when I run the DemoWebAPI project I get below exception upon the call to the DemoMethod:
System.IO.FileNotFoundException: 'Could not load file or assembly 'QRCoder, Version=1.3.5.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.'
I understand the fact that I have to copy the QRCoder.dll file somewhere. But I fail to understand where to put it. I've already tried putting it in "bin/debug/netcoreapp2.2" of the DemoWebAPI and "bin/debug/netstandard2.0" of the DemoLibrary.
But I couldn't get it working.
Request: Please post your answer as descriptive as you can because I am new to .Net Core.
Edit:
I am aware of the NuGet servers. I have read topics like hosting a NuGet server in IIS and Azure. The reason behind DLL reference is I want to use my DLLs in two projects one of them is a .net core API and the other is .net framework class library which is compiled by NMAKE. I couldn't find any way to restore NuGet packages in the .MAK files.
It looks like you've merely added the DLL for DemoLibrary to your DemoWebApi project. That's not how you should be adding references. Since these are in the same solution, you should add a project reference. That will fix your issue.
Now, let me explain what's actually going on here. Your DemoLibrary has a dependency on QRCoder. It's a NuGet reference, which means that package will be restored (i.e. downloaded) and included in your DemoLibrary build output. However, it will be included as one or more DLLs along side your DemoLibrary.dll. When you then just reference DemoLibrary.dll, you're missing all these other DLLs that are part of DemoLibrary and thus, things don't work properly.
Now, when it comes to a project reference, things are little more complex. A project reference essentially wraps the referenced project into your other project. You can think of it as sort of a sub project. For all intents and purposes, it's like any dependency of the sub project becomes a dependency of the main project. That means that DemoWebAPI now technically has a NuGet package reference to QRCoder even though there's no explicit package reference in its project file. The dependency comes from your DemoLibary project. As such, with a project reference, all the necessary dependencies will be included, because it's as if the main project included those itself, by way of the sub project.
For what it's worth, you should virtually never include a DLL as a reference directly. That used to be required, but the concept of NuGet packages has all but eliminated the practice. Even if DemoLibrary was not in the same solution as DemoWebAPI (meaning you could no longer do a project reference), the correct way to use it would be to turn DemoLibary into a NuGet package, and then reference it in DemoWebAPI via a package reference, like any other NuGet package. You do not simply add the DLL.

Referencing DLL's between solutions

I've found an interesting issue with using DLL's in .NET Core (.NET Framework works fine). MVCE as follows:
Create an F# (language is irrelevant) .NET Core class library with the following single file (specific code irrelevant as well):
namespace FSharpClassLibrary
module Say =
let hello name =
printfn "Hello %s" name
Create a C# .NET Core class library in a separate solution (very important--it completely works if they are all in the same solution) with the following code:
namespace CSharpClassLibrary
{
public class HelloClass
{
public void Hello(string name)
{
FSharpClassLibrary.Say.hello(name);
}
}
}
Also, add a folder dlls, copy/paste FSharpClassLibrary.dll into it, and add it as a reference to the project (otherwise, the file above won't compile). It's actually not important to move the .dll into the project, but since I'm distributing this second project independently of the first, I need to guarantee the DLL is available.
Finally, add a console app to the same solution as CSharpClassLibrary with the following file:
using System;
using CSharpClassLibrary;
namespace ConsoleApp1
{
public class Main1
{
public static void Main()
{
new HelloClass().Hello("test");
#if DEBUG
Console.Write("Press any key");
Console.ReadKey();
#endif
}
}
}
Finally, go into NuGet and add FSharp.Core version 4.3.4 to both C# projects (just to make sure everything has access to the F# language stuff; not necessary if you create the first class library with C#). Also, I am using VS 15.6; this may or may not be relevant as well.
Set the console app as the startup project and run it. You will get the following error:
System.IO.FileNotFoundException: Could not load file or assembly 'FSharpClassLibrary, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
File name: 'FSharpClassLibrary, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'
at ClassLibrary1.Facade.Hello1.Hello(String name)
at ConsoleApp1.Main1.Main() in C:\Users\***\source\ConsoleApp1\ConsoleApp1\Main.cs:line 11
The most curious part about this is that if I bypass the CSharpClassLibrary library and host FSharpClassLibrary.dll and HelloClass.cs in the ConsoleApp1 project, everything works as expected. The same issue happens if I use two C# DLL's, I used F# because that's what I was working with already.
What I already tried:
Cleaning and rebuilding all projects, making sure each project was referencing the latest build.
Referencing the child (FSharpClassLibrary.dll) from ConsoleApp1 as well as CSharpClassLibrary.dll.
Using the .dll from the obj folder instead of the bin folder (shouldn't matter as far as I'm aware).
Verify that FSharpClassLibrary.dll exists in ConsoleApp1's bin folder.
Double-check that all projects compile to .NET Core, no .NET Standard projects.

DNX core application version information

In earlier .NET based applications I implemented a central method for returning application information, usually product name, version and legal copyright. This method was implemented via System.Reflection.Assembly or in newer applications via System.Diagnostics.FileVersionInfo. This allowed me keep the version number in a central place, namely the main assembly file, and edit it easily in Visual Studio project properties.
Now in DNX core all this doesn't seem to be available, neither System.Reflection nor System.Diagnostics.
How would you suggest to manage version information in a DNX core based ASP.NET 5 application in a platform neutral way?
You can implement something like dnx's IRuntimeEnvironment:
IRuntimeEnvironment
RuntimeEnvironment (the implementation of IRuntimeEnvironment)
Now in DNX core all this doesn't seem to be available, neither System.Reflection nor System.Diagnostics.
Not sure what you mean by that. System.Reflection and System.Diagnostics are available: https://github.com/aspnet/dnx/blob/219871c6063d00f8297eeafe93266f1048f59a45/src/Microsoft.Dnx.Host/project.json#L21-L23
If you cannot find a particular type, use the PackageSearch website to see in which NuGet package it is
The project.json file has a version number, name, description, copyright, authors etc. These are the same properties used to build a NuGet package. In fact, if you create a 'Class Library (Package)' project, you can compile it directly to a NuGet .nupkg file by checking the option in project properties.
I believe you can read this file using Configuration.GetConfigurationSection. ApplicationSettings is just a class with the properties from the project.json that you want to read.
var configurationSection = configuration.GetConfigurationSection(nameof(ApplicationSettings));
var applicationSettings = ConfigurationBinder.Bind<ApplicationSettings>(configurationSection);

NHibernate - missing dll's

This call
// this._cfg is an NHibernate Configuration instance
this._sessionFactory = this._cfg.BuildSessionFactory();
Gives me this exception at runtime (NOT at compile time).
Could not load file or assembly 'NHibernate.ByteCode.Castle' or one of its dependencies. The system cannot find the file specified.":"NHibernate.ByteCode.Castle
OK so far. But the thing is, this code is running in a class library project, and I have referenced NHibernate.ByteCode.Castle (along with all the other NHibernate dll's) in that project.
Wierder: I can fix the exception by additionally referencing the NHibernate dll's in the Windows WPF executable project that calls my class library. But the Windows WPF executable contains no code that directly uses NHibernate (as evidenced by: It compiles fine without any NHibernate references). So what's going on? Apparently it's insufficient to reference NHibernate.ByteCode.Castle in the project that actually uses the NHibernate stuff. Anyone know why?
I know this is old, but what I've done to fix the dependency problem is simple:
In my UnitOfWork I added one static method:
private static void bringCastleDamnit()
{
var pf = new NHibernate.ByteCode.Castle.ProxyFactoryFactory();
}
Then, and only then, would MSBuild see that it was needed and copy it to my output directory for my (asp.net and console) apps that references my Data project.
I wouldn't reference the castle byte code factory at all; just ensure it (and all other needed dependancies) are copied to the output directory using a post-build step.