Friday, September 23, 2022

Generate C# client for API with Swagger (OpenAPI) support

If you develop web API it worth to consider to add support for Swagger specification (or OpenAPI specification) to it. As documentation says:

OAS defines an API’s contract, allowing all the API’s stakeholders, be it your development team, or your end consumers, to understand what the API does and interact with its various resources, without having to integrate it into their own application.

One of the practical advantage is possibility to easily create documentation for web API which supports Swagger. Another big advantage is possibility to generate SDK (client) for web API using preferred language. There are Swagger Codegen tools for that. One of them is online web service https://generator3.swagger.io/api/generate which can be used for generating SDK online.

Let's see how to use that and generate C# client for sample web API https://petstore.swagger.io/v2/swagger.json with Swagger support. We need to send HTTP POST request tohttps://generator3.swagger.io/api/generate with the following body:

{
  "specURL" : "https://petstore.swagger.io/v2/swagger.json",
  "lang" : "csharp",
  "type" : "CLIENT",
  "codegenVersion" : "V3"
}

and add header "Content-Type": "application/json". In "lang" parameter we specified which language should be used for generating SDK. After that web service will generate C# client for us and will return generated .cs classes in zip archive. If you use Postman then instead of Send button need to use Send and Download:


Zip archive will contain Visual Studio solution with generated SDK for web API:

As you can see it contains generated model classes and API for working with them so no need to code it manually. It may save a lot of time when you work with external web API.

Update 2023-03-29: see also another blog post Generate strongly-typed C# client for ASP.Net Core Web API with OpenAPI (swagger) support running on localhost which describes how to generate C# client for web api running on localhost.

Wednesday, September 7, 2022

Fix problem with PnP.PowerShell log file locked for writing by Set-PnPTraceLog

If you use PnP.PowerShell then most probably you are familiar with Set-PnPTraceLog cmdlet which allows to enable logging from PnP cmdlets. This is useful feature which simplifies troubleshooting especially if error got reproduced only on customer's environment. However there is one unpleasant side effect: Set-PnPTraceLog locks log file for writing. I.e. it is not possible to reuse the same file for other purposes and write there from anoter sources. Let's see why it happens.

Internally Set-PnPTraceLog uses TextWriterTraceListener (thanks Gautam Sheth for sharing it):

If we will decompile code of TextWriterTraceListener (which is quite easy in VS2022) we will find that it opens FileStream with FileShare.Read option:


And that's exactly the reason why it is not possible to write anything else to this log file until PowerShell session where Set-PnPTraceLog was called will be closed.

In order to solve this problem we need to use our own FileStream and inject it to PnP logging system. It can be done by the following PowerShell code:

# enable logging
Set-PnPTraceLog -On -LogFile $logFilePath -Level Debug
# close default file stream
[System.Diagnostics.Trace]::Listeners[1].Writer.Close()
# open new file stream with FileShare.ReadWrite
$fileStream = New-Object System.IO.FileStream($logFilePath, [System.IO.FileMode]::Append, [System.IO.FileAccess]::Write, [System.IO.FileShare]::ReadWrite, 4096)
# inject new file stream to PnP
[System.Diagnostics.Trace]::Listeners[1].Writer = New-Object System.IO.StreamWriter($fileStream, [System.Text.Encoding]::UTF8, 4096, $false)

Here we first enable PnP logging Set-PnPTraceLog. Every process by default already has trace listener called DefaultTraceListener which is accessible from Trace.Listeners[0]. In order to access listener added by Set-PnPTraceLog we need access Trace.Listeners[1]. So after that we open new FileStream with FileShare.ReadWrite and then use it for StreamWriter which we inject to PnP. As result PnP will use our own FileStream and it will be possible to write to the same log from another sources.