How to generate API clients using msbuild from a remote Swagger Url? - msbuild

Here's an example URL: https://petstore.swagger.io/
I'm looking for a code sample that solves the problem and industry standard answers to the questions below:
How can I generate a C# API Client using NSwag against this swagger.json endpoint / at build time with MSBuild / dotnet build. Please note that I'm not interested in copying this as a local file and generate it; I'm looking to point to this endpoint specifically.
Why / Why not commit the generated API Client to the repository? Should we let the build generate a new client through the CI/CD build machines on every build?
Is generating the API Clients against live endpoints a good idea in general? What problems did arise for people?

About how generate client:
I know two methods
The first is to use visual studio function "Connected Services":
Right click one thee project -> Add -> Connected Services. And setup new service(see screenshot for example).
I not found any documentation about how user can setup generated code. but you can try, and maybe this will be enough for you task.
The second is to use Nswag directly:
Csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NSwag.AspNetCore" Version="13.9.4" />
<PackageReference Include="NSwag.MSBuild" Version="13.9.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<Target Name="NSwag" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<!--You can change this if you you want keep generated code in repository-->
<ClientFileOutDir>$(IntermediateOutputPath)</ClientFileOutDir>
<ClientFile>$(ClientFileOutDir)/Client.cs</ClientFile>
</PropertyGroup>
<Exec Command="$(NSwagExe_Core31) run nswag.json /variables:OutFilename=$(ClientFile)" />
<ItemGroup>
<Compile Include="$(ClientFile)" />
</ItemGroup>
</Target>
</Project>
nswag.json
{
"runtime": "NetCore31",
"defaultVariables": null,
"documentGenerator": {
"fromDocument": {
"url": "https://petstore.swagger.io/v2/swagger.json",
"output": null,
}
},
"codeGenerators": {
"openApiToCSharpClient": {
"output": "$(OutFilename)",
"className": "PetstoreApi",
"namespace": "MyNamespace",
/*
Other properties
*/
}
}
}
About generate client or not and live endpoints:
I don't think there is a industry standard for this questions. Rather, you need to proceed from the requirements of the task and your convenience. For example, if in the future an offline build may be needed, then "live endpoints" will not work. Or if the api are constant and do not change, then the generation for each build looks unnecessary and it may be worth generating the client once and placing it in the repository.

Related

Can't figure out how to include the source code into the nuget package that gets generated

I have several projects in a solution that i want to be packaged to be used as libraries in other solutions. The goal is to make development and debugging seamless, as if it was all in the same solution.
Specifically, I want to be able to ctrl + click on something from the library and be able to view the original source code and not the decompiled code.
I am using PackageReference to include the libraries to the application. What I have noticed is that when I unzip either the nupkg or snupkg, there is no source files anywhere. On that note, I have searched all over the internet and found conflicting things about where the source files go in the nupkg. I have seen mentions of the following folders in the nupkg: lib, src, content, and contentFiles. Which one should actually contain the source code?
When I unzip the nupkg (or snupkg) the only things I have in it are _rels, lib, package, [Content_Types].xml, and PROJECTNAME.nuspec.
I see that the lib folder contains the dll and the pdb file but no source code.
Furthermore, I noticed that the snupkg file is considerably smaller than the nupkg file which I find to be counter intuitive.
I have tried packaging using
msbuild -t:pack
msbuild -t:pack -IncludeSource=true
nuget pack
nuget pack -IncludeSource=true
and also building with visual studio but to no avail.
Here is my vbproj file
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{C64FB67B-64D0-4607-AE35-A21888FE79A2}</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>ROOTNAMESPACE_HERE</RootNamespace>
<AssemblyName>PACKAGE_NAME_HERE.ROOTNAMESPACE_HERE</AssemblyName>
<FileAlignment>512</FileAlignment>
<MyType>Windows</MyType>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<Deterministic>true</Deterministic>
<SccProjectName>SAK</SccProjectName>
<SccLocalPath>SAK</SccLocalPath>
<SccAuxPath>SAK</SccAuxPath>
<SccProvider>SAK</SccProvider>
<TargetFrameworkProfile />
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<version>1.0.0</version>
<RepositoryType>git</RepositoryType>
<Authors>COMPANY_HERE</Authors>
<BuildInParallel>false</BuildInParallel>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<IncludeSource>true</IncludeSource>
<PackageId>PACKAGE_NAME_HERE.ROOTNAMESPACE_HERE</PackageId>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
</PropertyGroup>
...
...
...
<ItemGroup>
<PackageReference Include="NuGet.Build.Tasks.Pack" Version="6.4.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
</ItemGroup>
I have also tried using a nuspec file as well but still no success.
I am using .net framework 4.8
I have spent the last 3 days banging my head against the wall over this and haven't been able to figure it out. I have googling non-stop and have even been using ChatGPT to help me try and trouble shoot and no matter what I try I cant get it to work.
Any help would be greatly appreciated!
It sounds like you want to publish nuget packages with SourceLink activated.
SourceLink will add metadata to the packaged assemblies that contains hints about where the original code from which the package was build can be found, e. g. a GIT repository URL and the particular commit SHA. The Visual Studio Debugger during debugging will read the metadata and thus be able to download the source from the repository and show it to you.
This will be the original source like you wanted.
See the docs at https://learn.microsoft.com/en-us/dotnet/standard/library-guidance/sourcelink.
What you have to do is add SourceLink as a package dependency to the project from which the package will be built.
For github and an SDK-style project it looks like this:
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All"/>
<!-- alternatively, using the new GlobalPackageReference element -->
<GlobalPackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" />
Since you seem to be using the old project format, you may need to do it differently. Visual Studio will most likely do the right thing for you when adding the package through the package manager UI.
Note that this is a build-time dependency only and will not add any libraries.
SourceLink by default will only do its job when some MSBuild properties are set. More on that below.
This is a snippet I use (again, SDK-style) to have SourceLink active on every release build:
<PropertyGroup>
<!-- ugly workaround because MSBuild apparently cannot set a bool property from the result of an evaluated expression -->
<TreatAsOfficialBuild>false</TreatAsOfficialBuild>
<!-- Abuse "Release" config as trigger for SourceLink, because I don't want to type -p:ContinuousIntegrationBuild every time
This should work as long as we do not locally debug release builds from commits that have not been pushed to github yet -->
<TreatAsOfficialBuild Condition="'$(Configuration)' == 'Release'">true</TreatAsOfficialBuild>
<PublishRepositoryUrl>$(TreatAsOfficialBuild)</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<ContinuousIntegrationBuild>$(TreatAsOfficialBuild)</ContinuousIntegrationBuild>
<DeterministicSourcePaths>$(TreatAsOfficialBuild)</DeterministicSourcePaths>
</PropertyGroup>
For the debugger to be able to download the source, the package must have been built from a commit that is available on the remote repo.
I trust you already know that you may not be able to debug into that source with breakpoints if the build you are debugging is optimized like in a "Release" build.
If you want to be able to debug through every line of the package's original source, you would have to build that package without optimization (like in a "Debug" build). For SourceLink to be active on debug builds too, you would need to adapt the criteria for the TreatAsOfficialBuild property accordingly.

Change NuGet Package Referenced Based on Profile

We have a NuGet package we created locally. We have a version that includes non-production environments and one that includes the production environment. Let's use Connection and ConnectionProd for reference.
Is there a way that I can set Debug or my non Prod publish profile that I have set up to use the Connection package, but have the Production profile use ConnectionProd? I know the PackageReference has conditions, but I wasn't sure if there was a way to tie that to the selected publish profile?
For security reasons, most of our developers don't have access to the ConnectionProd NuGet source location and I'd like to not have to create another TFS branch solely to handle the production NuGet reference.
You specify different package name and version depending on the envirounment:
<ItemGroup Condition="'$(Configuration)'=='Debug'">
<PackageReference Include="Connection" Version="1.0.0-dev" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)'=='Release'">
<PackageReference Include="ConnectionProd" Version="1.0.0" />
</ItemGroup>
UPDATE
Theorically '$(ASPNETCORE_ENVIRONMENT)'=='Development' or using any environment variable for the condition should do it, but somehow I couldn't make it work on my test project.
However, I found another way to make it run with any custom variable, just define your variable inside a <PropertyGroup> then use it to define the condition:
<PropertyGroup>
<MyVariable Condition="'$(MyVariable)'==''">MyValue</MyVariable>
</PropertyGroup>
<ItemGroup Condition="'$(MyVariable)'=='MyValue'">
...
</ItemGroup>
Reference: https://learn.microsoft.com/en-us/visualstudio/msbuild/how-to-build-the-same-source-files-with-different-options?view=vs-2019#example

How to use SatelliteResourceLanguages to filter out resource files when publishing .NET Core API services

When publishing .NET Core API services, the output includes with localized resources (cs, de, es, fr, etc.)
Searching for a solution to prevent .NET Core from publishing these localized resource files, I came across this commit on Github to implement SatelliteResourceLanguages for that purpose.
But how can I implement it?
According to this answer, you should just add it to the project file:
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
Here's how you can use the above line in a project config:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<SatelliteResourceLanguages>en;de;pt</SatelliteResourceLanguages>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FooBar" Version="2.0.1" />
</ItemGroup>
</Project>
Note that I couldn't find SatelliteResourceLanguages documented officially anywhere as of today.
Also note that you need to have a recent version of the SDK, as this bug report mentions that a typo prevented this to work properly in prior releases.

Pass parameter/property from publish profile to csproj file

I exclude some folders while building for development in .csproj file.
<ItemGroup>
<Content Remove="Production\**" />
<Compile Remove="Production\**" />
</ItemGroup>
I want to include those folders back in a "publish profile" but it does not work.
<ItemGroup>
<Content Include="Production\**" />
<Compile Include="Production\**" />
</ItemGroup>
So, how could i pass parameters from "publish profile" to build(.csproj) and prevent exclusion of those folders or include them back.
<ItemGroup Condition="'$(SOMEPARAM)'!='Production'">
<Content Remove="Production\**" />
<Compile Remove="Production\**" />
</ItemGroup>
So, i need to determine when build runs with "publish profile" in .csproj file and take according action.
I know it is possible with command line parameters but i want to use Visual Studio, not command line.
dotnet build /p:DeployOnBuild=true /p:PublishProfile=FolderProfile;SOMEPARAM=Production
Update:(Solution)
Check my answer below, it works fine when CopyToPublishDirectory used.
Update:(Another Solution)
Alternatively, when targets specified with same names, a target from publish file will override the target from project file so we could define what to include/exclude separately in project and publish files.
You can define any property inside a <PropertyGroup> in the publish profile and use it in a condition in the csproj's <ItemGroup>s.
This works beause the publish profile is imported into the project and msbuild evaluates all static property groups before all item groups, which means that even a file that is imported at the end of it can affect item groups logically above it.
Sometimes my projects have "similar" code but hard-coded limitations like features supported by the operating system or license features.
I have solved it using 2 "configurations", let me walk you through it
Step 1. define your compiler directive in a central location where you need it in your code like so:
Console.WriteLine("Hello, World!");
#if IDPSH1
Console.WriteLine("IDPS H 1");
#elif IDPSH3
Console.WriteLine("IDPS H 2");
#elif IDPSH5
Console.WriteLine("IDPS H 3");
#endif
Console.ReadKey();
Step 2, in your build process, generate a build that would tell the compiler to generate what you need where you need it
dotnet publish ConsoleDefineConstants.csproj --output bin\release\IDPS-H-1 --configuration Release /p:DefineConstants=IDPSH1
dotnet publish ConsoleDefineConstants.csproj --output bin\release\IDPS-H-2 --configuration Release /p:DefineConstants=IDPSH2
dotnet publish ConsoleDefineConstants.csproj --output bin\release\IDPS-H-3 --configuration Release /p:DefineConstants=IDPSH3
In the sample, I instruct dotnet to build and publish
the relative project ConsoleDefineConstants.csproj
in release mode
using a compiler constant using the /p:DefineConstants=
in a location where my deployment packaging expects it
if I start the sample, I get
I added all the code you need in the sample, let me know if you like me to clarify something
So, i determined that those includes need CopyToPublishDirectory. So following works fine too.
In .csproj file;
<ItemGroup>
<Content Remove="Production\**" />
<Compile Remove="Production\**" />
</ItemGroup>
In publish profile;
<ItemGroup>
<Content Include="Production\**" CopyToPublishDirectory="PreserveNewest" />
<Compile Include="Production\**" CopyToPublishDirectory="PreserveNewest" />
</ItemGroup>

MSBuild appears to only use old output files for custom build tools

I have an ANTLR grammar file as part of a C# project file and followed the steps outlined in the User Manual.
<Project ...>
<PropertyGroup>
<Antlr3ToolPath>$(ProjectDir)tools\antlr-3.1.3\lib</Antlr3ToolPath>
<AntlrCleanupPath>$(ProjectDir)AntlrCleanup\$(OutputPath)</AntlrCleanupPath>
</PropertyGroup>
<ItemGroup>
<Antlr3 Include="Grammar\Foo.g">
<OutputFiles>FooLexer.cs;FooParser.cs</OutputFiles>
</Antlr3>
<Antlr3 Include="Grammar\Bar.g">
<OutputFiles>BarLexer.cs;BarParser.cs</OutputFiles>
</Antlr3>
</ItemGroup>
<Target Name="GenerateAntlrCode"
Inputs="#(Antlr3)"
Outputs="%(Antlr3.OutputFiles)">
<Exec Command="java -cp %22$(Antlr3ToolPath)\antlr-3.1.3.jar%22 org.antlr.Tool -message-format vs2005 #(Antlr3Input)" Outputs="%(Antlr3Input.OutputFiles)" />
<Exec Command="%22$(AntlrCleanupPath)\AntlrCleanup.exe%22 #(Antlr3Input) %(Antlr3Input.OutputFiles)" />
</Target>
<ItemGroup>
<!-- ...other files here... -->
<Compile Include="Grammar\FooLexer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Foo.g</DependentUpon>
</Compile>
<Compile Include="Grammar\FooParser.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Foo.g</DependentUpon>
</Compile>
<!-- ... -->
</ItemGroup>
</Project>
For whatever reason, the Compile steps only use old versions of the code, no amount of tweaking appears to help.
By "old versions," I mean that if I clean the solution, build the project, Foo.g will make FooLexer.cs and FooParser.cs. Should I then make an update to Foo.g and recompile, the new versions of the lexer and parser C# files are ignored and the old versions are used. I have to compile a second time...
There seems to be a bug in the IDE: Visual Studio only monitors changes in C# files that it modifies itself (e.g. designer generated code). For code modified/generated outside of the IDE (e.g. external tool like ANTLR) it will use the in-memory version of the file, without refreshing it from the disk.
The workaround is to not use the "hosted" cache, and instead spawn an external CSC process to compile the project. You do this by setting the "UseHostCompilerIfAvailable" project property to false like so in your .csproj:
<UseHostCompilerIfAvailable>FALSE</UseHostCompilerIfAvailable>
For more info, see this entry in the MS Connect website.
I had the exact same problem as you with ANTLR in Visual Studio, and this fixed it for me. However, some people report problems with project-to-project dependencies after setting that option to 'false' so watch out for side effects...