Built Azure Function for Linux doesn't include dependency - c#

We have an Azure Function running on a Linux host.
Our app is a netcoreapp3.1. It runs fine, except for one issue which I can't explain.
The csproj file has always been configured like this (only a snippet):
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AzureFunctionsVersion>v3</AzureFunctionsVersion>
<UserSecretsId>...</UserSecretsId>
<RunAnalyzersDuringBuild>false</RunAnalyzersDuringBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Cloud.Asset.V1" Version="2.6.0" />
</ItemGroup>
There are many other packages, but this one is the one with the issue. On Windows everything works fine, all good. On Linux (or WSL2) the app also builds fine, the Functions Host starts and all seems well, until we hit code that uses the Google.Cloud.Asset.V1 package. This package references Grpc.Core and the code then fails with
System.Private.CoreLib: Exception while executing function: inventory. Grpc.Core: Error loading native library. Not found in any of the possible locations: /mnt/c/development/app/App.Functions/bin/Debug/netcoreapp3.1/bin/libgrpc_csharp_ext.x64.so,/mnt/c/development/app/App.Functions/bin/Debug/netcoreapp3.1/bin/runtimes/linux/native/libgrpc_csharp_ext.x64.so,/mnt/c/development/app/App.Functions/bin/Debug/netcoreapp3.1/bin/../../runtimes/linux/native/libgrpc_csharp_ext.x64.so.
It doesn't seem to make munch sense to me because this used to work, but nothing in the csproj changed recently, apart from other dependencies that were added, but are unrelated to this.
Checking in bin/Debug/netcoreapp3.1/bin/runtimes there's no linux only Windows.
I do however see this directory here, which doesn't seem to be in the search path in the error message though. This is bin/Debug/netcoreapp3.1/runtimes.
Does anybody know how I can get this to work again?
I tried adding <RuntimeIdentifier> or <RuntimeIdentifiers> into the csproj, but that didn't change anything.

It looks like this is a problem that was fixed in Grpc.Core 2.34.0 (by this commit, I believe). If you add an explicit dependency on Grpc.Core 2.34.0, like this:
<PackageReference Include="Grpc.Core" Version="2.34.0" />
... that seems to fix it. I still don't know why the runtime was copied into the "old" expected place for Windows but not for Linux - that feels like it's an Azure Functions SDK issue somehow. But with Grpc.Core 2.34.0, the native extension loader "knows" where to find it in the parent bin/runtimes directory instead.

Related

VS 2019 Winform Project and SQLite error: Unable to load DLL 'SQLite.Interop.dll': The specified module could not be found

Greetings people who are smarter than I.
After publishing a project which utilises SQLite, when the part of the program runs that accesses the DB commands, I get this error.
Unable to load DLL 'SQLite.Interop.dll': The specified module could
not be found
Now I have spent hours, going through the many similar threads, trying all the suggestions to see if I can fix this, however as of yet I have had no luck.
I have done the following.
Ensured the SQLite.Core is included on the main project and all sub project areas.
Ensured the .dll is available in the debug bin.
Ensured dependencies are set correctly.
Publish specifically to x64 platform.
Publish specifically to x86 platform.
Disabled "Prefer 32-bit".
Copied some specific references to csproj.
All to no avail. If anyone has any experience with this who might be able to suggest something new that I haven't tried in an attempt to solve this I would be most grateful.
So after much research i finally found the solution.
It seems there are a great many potential causes for this error, however this resolved the issue for me in this instance.
Revert everything back to how it was, ensure System.Data.SQLite.Core is referenced in your assemblies.
Close VS / solution and open the csproj file in the repo. Copy the following into the file.
<PropertyGroup>
<ContentSQLiteInteropFiles>true</ContentSQLiteInteropFiles>
<CopySQLiteInteropFiles>false</CopySQLiteInteropFiles>
<CleanSQLiteInteropFiles>false</CleanSQLiteInteropFiles>
<CollectSQLiteInteropFiles>false</CollectSQLiteInteropFiles>
</PropertyGroup>
Save the file, (probably would create a copy of it beforehand to ensure you can replace it if it doesnt work for you).
Ensure that interop.dll is not anywhere in your repo except bin/debug/x86 and x64.
Then proceed to test your project, then publish it.
Reason:
Because the Interop is included in the nuget installation, but not copied down, this bit of code ensures that its copied through during the publishing.
Thank you internet.

Can a COMReference be fully migrated to PackageReference?

I am trying to rid myself of a lib-Folder I have in my solution, migrating dlls to nuget and also switching from Packages.config to Packagereference.
That worked well for most dlls.
But now I have some COM-References I am unsure if what I am trying to do is correct.
It looks like this in .csproj:
<COMReference Include="MyLib">
<Guid>{ABCDEFA1-AD1F-AFBE-ACED-AFDF123AADEE}</Guid>
<VersionMajor>X</VersionMajor>
<VersionMinor>Y</VersionMinor>
<Lcid>0</Lcid>
<WrapperTool>tlbimp</WrapperTool>
<Isolated>False</Isolated>
<EmbedInteropTypes>True</EmbedInteropTypes>
</COMReference>
And as a prebuild-event I have
<PreBuildEvent>
regsvr32 /s $(SolutionDir)Lib\MyLib.dll
</PreBuildEvent>
Now I am irritated, because I read this article where it told me to add ReferencePath and EmbedInteropTypes via .targets from the nuget-package.
However I did not get this to work (or it seems it did not suffice in my case (looking at the GUID being referenced in the COMReference))
So this got me thinking, maybe I do not need to transform this into a Packagereference at all, but just add one (that includes "MyLib"), have the prebuild-event go against that dll from my packages-folder and leave the COMReference as is.
I now just added the libaries I needed to the nuget package I am pulling under content\x86 and modified the prebuild-event to point to $([MSBuild]::EnsureTrailingSlash('$(NugetPackageRoot)')) instead.
This seems to work, it builds, and I have no runtime errors (so far), but I am not sure if this is best practice or if I am missing something, any pointers are greatly appreciated.

Using Selenium WebDriver in library csproj with .NET Core

I'm trying to put together a web-scraping app, using Selenium and .NET Core, but I'm having trouble getting my WebDriver exes to be found.
I have one .csproj that will run the API for the project, which calls out to (amongst others) another .csproj that will handle the webscraping.
All are in a single .sln, and all are running .NET Core 2.1
In the scraping proj, I've nuget-installed Selenium.WebDriver and Selenium.WebDriver.ChromeDriver.
I've created an endpoint in the API, which calls out to the scraping project, and runs a method that attempts to invoke new ChromeDriver(). It doesn't work :( Specifically, I get:
The chromedriver.exe file does not exist in the current directory or in a directory on the PATH environment variable. The driver can be downloaded at ... <url>
Seems fairly clear (although it dissappointingly doesn't tell you what "current directory" means. I'll be submitting a PR for that imminently)
By observing changes during a rebuild, and other research online, I see that:
All the dlls and exes from the nuget packages are stored in the Global Nuget cache, rather than a nuget packages folder in the solution directory.
This appears to be expected behaviour: "Bug" raised in dotnet Std; MSDN migration docs.
The chromedriver.exe appears to get copied to <solutionFolder>\<ScrapingProjectFolder>\bin\Debug\chromeDriver.exe.
I assume that this is what the ChromeDriver Nuget package does; certainly I haven't configured it myself.
This superficially feels like a reasonable thing for that ChromeDriver package to be doing as an attempt at "install this to make new ChromeDriver() JustWork."
Digging into the WebDriver codebase, reveals that the "currentDirectory" that it's looking at is "the location of WebDriver.dll".
In my case, that's "<globalNugetPackagesCache>\selenium.webdriver\3.141.0\lib\netstandard2.0"
It doesn't seem like I should be trying to get the chromedriver.exe to end up in this folder - copying it into a different package's global cache seems wrong? (Do people agree?)
This article seems to have reached broadly the same conclusion and says that the solution is to invoke the driver as:
new ChromeDriver(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location))
Unfortunately, that path takes me to <solutionFolder>\<APIProjectFolder>\bin\Debug\<ScrapingProjectFolder>.dll, because the dll gets copied over the the API project's folder.
A couple of solutions occur to me, none of which really appeal:
I could install Selenium.WebDriver.ChromeDriver into the API project.
Eww... the API project doesn't know about WebDriver or Selenium, and now the Scraping project doesn't have the driver exe.
I could manually explictly copy the exe into the right place.
Doesn't really feel right, and feels fragile. I suspect this will make deployment painful.
I could manually point the ChromeDriver constructor to a hard-coded path, that I just happen to know contains the current exe.
Seems similar to the above; though not quite as bad.
??? Is there some way to make all the DLLs etc. of a project get compiled into a single common folder? ???
Is there a good, non-hacky way to solve this problem. Which will result in a git repo that JustWorks, and is going to be relatively painless to deploy to a server in the future?
Are any of the things I've described above wrong, or mis-configured?
From what I understand you have an API project that depends on a Scraping project.
Scraping.csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>7.2</LangVersion>
<PublishChromeDriver>true</PublishChromeDriver>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Selenium.WebDriver" Version="3.141.0" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="2.46.0" />
</ItemGroup>
</Project>
API.csproj:
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\Scraping\Scraping.csproj" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>7.2</LangVersion>
</PropertyGroup>
</Project>
The trick is adding <PublishChromeDriver>true</PublishChromeDriver> to the transitive project to make it publish the chromedriver when running dotnet publish API.csproj The ChromeDriver package has custom build targets in the NuGet package so it is custom.
You can now use
new ChromeDriver(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
and dotnet run API.csproj
Please correct me if I'm wrong. You have some kind of Class Library that has reference to Selenium and you would like to use ChromeDriver.exe but you are getting an error that it cannot be found under the following location. This is fairly simple.
Currently you are referencing Class Library lets say Foo to API. Your Assembly Location will point to API bin location, whereas chromedriver.exe is located under Class library bin.
If this is the case the only thing you would have to do is copy following chromedriver.exe to final bin directory which is API.
Add following Post Build Event to your API project to copy chromedriver:
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="copy $(SolutionDir)\ClassLibrary\bin\Debug\netstandard2.0\chromedriver.exe $(TargetDir)" />
</Target>
This will copy your chromedriver.exe to API bin. Later while initializing ChromeDriver use:
var options = new ChromeOptions();
var service = ChromeDriverService.CreateDefaultService(AppDomain.CurrentDomain.BaseDirectory);
WebDriver = new ChromeDriver(service, options);
While AppDomain.CurrentDomain.BaseDirectory will point to your API bin directory.

Clean solution does not clean FakesAssemblies?

I'm trying to understand the behavior of building fake assemblies. I'm able to add Fake Assembly in my unit test project, and it compiles into the FakesAssemblies folder locally.
However, I've been having issues lately where my dll's in FakesAssemblies-folder tries to reference an older version of the "QualityTools.Fakes"-reference. This happened after I've installed Update 5 (went from Update 3) of VS2013.
When cleaning the entire solution, the FakesAssemblies seems to be still there and not recompile.
The obvious workaround for this is to delete everything in the FakesAssemblies-folder whenever I need, but is this how it's meant to work?
You can add custom code to .csproj file (before the closing </Project> tag) to do it automatically:
<Target Name="AfterClean">
<RemoveDir Directories="$(ProjectDir)\FakesAssemblies" ContinueOnError="true" />
</Target>

Unable to load DLL 'sqlite3': The specified module could not be found

I'm having a heck of a time getting my tests to run against my SQLite Data Provider.
I've looked at the suggested links here on stackoverflow, but none of them seem to get me going down the right path.
I've downloaded the Windows Precompiled Binaries for sqlite3.dll
I've copied the sqlite3.dll into both my Sqlite\bin directory as well as my Tests\bin directory
Unfortunately when I run my tests, I get the following error
Is there a clear cut way to get this working both in my Windows dev environment (primary goal right now) as well as running in Android and IOS (required in the near future)?
Also, if it matters, here are my Sqlite project references.
So the answer for me was quite simple. I wired up a Pre-Build event that checks the architecture of the machine, and copies the appropriate dll into the output bin directory.
Now anyone on our team can simply run REBUILD, and the proper dll will be available to run against SQLite.
if '$(PROCESSOR_ARCHITECTURE)'=='AMD64' (xcopy /y "$(ProjectDir)x64\sqlite3.dll" ".\")
if '$(PROCESSOR_ARCHITECTURE)'=='x86' (xcopy /y "$(ProjectDir)x86\sqlite3.dll" ".\")
if '$(PROCESSOR_ARCHITEW6432)'=='AMD64' (xcopy /y "$(ProjectDir)x64\sqlite3.dll" ".\")
When I add the following libraries the error went away
Microsoft Visual C++ Runtime Package (V11.0)
SQLite for Windows Runtime (V3.8.7.1)
Can't put this in comments, so here's what I got:
Also, have a look at Similar problem.
Last, but not least, did you try to add a reference to the SQLite.Inetrop.dll (in the references)?
References -> add -> just browse to where your SQLite is, select view all, and add a reference to the Interop.dll as well)
I am using VS2017 and solved the issue by adding the following task to copy the assembly.
<Target Name="Ensure SQLite assemblies copied" AfterTargets="Build" Condition=" '$(Platform)' == 'x64' ">
<Copy SourceFiles="$(OutDir)x64\SQLite.Interop.dll" DestinationFolder="$(OutDir)" />
</Target>

Categories