SignalR.Core Reference inside csproj fails in Azure Pipline - c#

I am probably being a bit stupid ;) but I have a Problem in my Azure Pipline.
I used the the SignalR Library from AspNetCore to build a Hub. Since .NetCore3.0, the SignalR is automatically devliverd with AspNetCore.
That my Hub Class public class UrgentAcknowledgedHub : Hub {} did know where the Library (dll) is, i had to add following code to my .csproj File.
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.SignalR.Core">
<HintPath>..\..\..\..\..\Program Files\dotnet\shared\Microsoft.AspNetCore.App\6.0.8\Microsoft.AspNetCore.SignalR.Core.dll</HintPath>
</Reference>
</ItemGroup>
I can run this code local without any problem, but when the Azure pipline run this code, I got following Error and it crashes:
Warning MSB3245: Could not resolve this reference. Could not locate the assembly "Microsoft.AspNetCore.SignalR.Core". Check to make sure the assembly exists on disk. If this reference is required by your code, you may get compilation errors.
I totally understanding that it can't find the SignalR Referenc anymore because it is hardcoded. Is there any way to not hardcode it?

I think i got the Answer. To use the SignalR from the .Net6 Framework, I have to add the following FrameworkReference in my .csproj File.
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App"/>
</ItemGroup>

Related

Built Azure Function for Linux doesn't include dependency

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.

Is it possible to reference a project that exists in the solution and use NuGet package reference as fall-back if not in .NET Core?

I have an .NET Standard project where I implemented a module for an ASP.NET Core CMS framework. Currently, it uses the CMS framework libraries coming from NuGet packages. If I grab the source code of the CMS framework from GitHub and add my module to its solution and replace the package references to the actual project references it will work fine.
My goal is to make it work without updating the references in the csproj file so if the project is added to the full source code solution then use the project references, otherwise use the NuGet package references.
So let's say the .NET Standard project is called 'ModuleA'. It has a package reference to 'ModuleB':
<ItemGroup>
<PackageReference Include="ModuleB" Version="1.0.0" />
</ItemGroup>
When I want to use ModuleA in a solution where ModuleB is accessible then I use project reference to it:
<ItemGroup>
<ProjectReference Include="..\..\ModuleB\ModuleB.csproj" />
</ItemGroup>
I'd like to include both of them in the .csproj file somehow and make it to use the proper references on build (e.g. based on some conditions like project exists?).
If both are added to the csproj then the build will fail (e.g. 'Unable to find project ...ModuleB.csproj. Check that the project reference is valid and that project file exists.').
You could do that probably dynamically, but I think it's not really transparant how that would work.
I would recommend to add a configuration, e.g. in my example "Local-Debug" and use conditions in your csproj.
Example
Creating the configuration:
And in your csproj you could do this:
<ItemGroup>
<ProjectReference Condition="'$(Configuration)' == 'Local-Debug'" Include="otherProject.csproj" />
<PackageReference Condition="'$(Configuration)' != 'Local-Debug'" Include="otherProjectPackage" Version="2.4.1" />
</ItemGroup>
Your dependencies must be statically known and resolve-able at build-time. See #Julian's answer for a good config-driven build-time solution.
As a run-time solution: You can dynamically load your references at run-time. This way, you can search for the DLL you need in the working directory of your app and if you don't find it there, then download it (from Nuget or elsewhere), either as a binary that you can directly load, or as a source that you can build; and then load that library dynamically.
Here's how you can load an assembly dynamically (at run-time): https://learn.microsoft.com/en-us/dotnet/api/system.reflection.assembly.loadfrom?view=netframework-4.8
And this question: Loading library dynamically
Coding against a dynamically loaded assembly has its own quirks; you will need clear interface definitions for the referenced library, or else, you'll find yourself dealing with a lot of reflection and dynamics.

.NET Core - Nuget Pack of UniTest Project shows warning "The assembly is not inside the 'lib' folder"

I'm trying to pack a UnitTest project as a Nuget package and I always get the following warning(s) if I build my project:
The assembly
'content\SpecFlow.MSDependencyInjection.SpecFlowPlugin.dll' is not
inside the 'lib' folder and hence it won't be added as a reference
when the package is installed into a project. Move it into the 'lib'
folder if it needs to be referenced.
My csproj file looks like this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>0.1.0</Version>
<IsPackable>true</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http" Version="2.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" />
<PackageReference Include="MSTest.TestAdapter" Version="1.4.0" />
<PackageReference Include="MSTest.TestFramework" Version="1.4.0" />
<PackageReference Include="SpecFlow" Version="3.0.188" />
<PackageReference Include="SpecFlow.MSDependencyInjection.SpecFlowPlugin" Version="1.0.2" />
<PackageReference Include="SpecFlow.MsTest" Version="3.0.188" />
<PackageReference Include="SpecFlow.Tools.MsBuild.Generation" Version="3.0.188" />
</ItemGroup>
</Project>
The error still appears if I copy the files into the lib folder of the Nuget package. I'm at a loss what I need to change for this warning to disappear. To be frank I'm not even sure why it appears in the first place because I have a different project that works fine without this error.
Update 1:
After the detailed answer from #zivkan I changed my project structure so it is not a UnitTest project anymore.
Sadly the errors still appear if my project is a class library...
Screenshot with all Nuget-Packages that I need for my project to work
If I only add my own Nuget-Package that consists of two dependencies (Microsoft.Extensions.DependencyInjection and SpecFlow) it still produces this error but the two dependencies in this Nuget-Package don't. To me this seems to be a problem with the Nuget-Packages...
I'm not 100% sure, but my guess is that since with SDK style csproj files, when you build, only your assembly's dll is normally written to the output directory. When you run a non-test netcoreapp, the dotnet cli looks at what project references and nuget references you have, and configures the assembly loader to load from their "original" locations, rather than having all the assemblies copied to your app's bin folder. Perhaps the unit test framework doesn't support loading assemblies in this way and creates Content items out of each dll, which tells the build step to copy the content (in this case dlls) into the output directory (bin\$(Configuration)\$(TargetFramework)). Therefore, when you run unit tests, the unit test framework has all the required assemblies in the single directory, whereas that's normally not true for non-test projects.
Next you need to understand that when NuGet packs a project, it looks for MSBuild items of the type Content, and puts copies of them in the nupkg's content and contentFiles directories. Due to how NuGet works, only dlls in the lib\ or ref\ directories within the nupkg are given to the compiler, therefore any dlls you have a content directory will not be passed to the compiler, so your project that references this nupkg cannot use classes in those dlls. This is not how people usually intend to use NuGet packages and therefore NuGet generates a warning.
So, I believe the reason you're getting this warning is because you're packing a project type that is not intended to be packable. The project type does some uncommon things in order to work, which triggers NuGet warnings because typically when this uncommon thing is done, it's a mistake.
I believe this to be a case of a XY problem. I assume you're packing a unit test project because you want to share some utility code useful for tests, maybe some mocks or object initialisation code. In this case, I recommend you create a new classlib project, put your shared code in there, leaving all your test cases in your netcoreapp test project, even if it's nothing more than a single method call into the classlib. This way you can pack and share the classlib without warnings. Packing a unit test seems unusual and it would be interesting to discuss why you want to do this, what problem do you intend to solve and if packing a test is really the best way to achieve it. Unfortunately Stack Overflow isn't a good place to have discussions and is often actively discouraged.
perhaps you have missed a file, please follow this link for full details : https://learn.microsoft.com/en-us/nuget/create-packages/creating-a-package#Package_Conventions

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.

Reference dll in my precompiled C# Azure Function

In reference to Reference c# class library in my Azure Function I would like to ask the same question how to achieve this
- with a precompiled C# Azure function
- with any kind of .dll (that is provided as such, thus not a NuGet)
- within VS Code
AFAIK this is not documented
Add a folder like MyAssemblies containing our dlls under function app folder.
Then add references in functionappname.csproj.
<ItemGroup>
<Reference Include="MyCustomDll">
<HintPath>./MyAssemblies/MyCustomDll.dll</HintPath>
</Reference>
</ItemGroup>

Categories