Friday, October 29, 2021

GraphQL–Strawberry Shake GraphQL client

Until recently I always used the GraphQL.Client as the GraphQL client of my choice. This client is straightforward and easy-to-use.

For a new project I decided to give Strawberry Shake a try. Strawberry Shake was created by Chilicream, the creators of the HotChocolate GraphQL backend for .NET.

Strawberry Shake is using a different approach as the GraphQL.Client as it heavily relies on code generation and looks similar to the Apollo GraphQL client from a design point of view.

I mostly followed the “Get started” documentation to get the Strawberry Shake client up and running, but I didn’t get everything up and running immediatelly so I’ll add some extra detail on the points where I got into trouble.

Add the CLI tools

  • We start by adding the Strawberry Shake CLI tools
  • Open the folder that contains the project where you want to add the Strawberry Shake GraphQL client.
  • Now wee need to first create a dotnet tool-manifest.

dotnet new tool-manifest

Getting ready...

The template "Dotnet local tool manifest file" was created successfully.

  • After doing that we can install the Strawberry Shake tools locally.

dotnet tool install StrawberryShake.Tools –local

You can invoke the tool from this directory using the following commands: 'dotnet tool run dotnet-graphql' or 'dotnet dotnet-graphql'.

Tool 'strawberryshake.tools' (version '12.0.1') was successfully installed. Entry is added to the manifest file C:\projects\graphqlclientexample\.config\dotnet-tools.json.

Add the NuGet packages

  • Now we need to add some NuGet packages:

dotnet add package StrawberryShake.Transport.Http

dotnet add package StrawberryShake.CodeGeneration.CSharp.Analyzers

dotnet add package Microsoft.Extensions.DependencyInjection

dotnet add package Microsoft.Extensions.Http

Add the GraphQL client

  • Next step is to add a client using the following command  dotnet graphql init {{ServerUrl}} -n {{ClientName}}.

dotnet graphql init https://example.graphql.be/graphql/ -n ExampleClient

Download schema started.

Download schema completed in 399 ms

Client configuration started.

Client configuration completed in 137 ms

  •  A .graphqlrc.json is generated together with a schema.graphql file and a schema.extensions.graphql file:

    Add a GraphQL query

    • At this moment no code is generated yet. Therefore we have to write our first graphql query.
    • Create a new .graphql file and write the query you want to execute
    • The next step mentioned in the documentation is that you only need to compile your code to let the code generation do its work. But nothing happened when I tried to do that.
    • I discovered that I had to take one extra step. I needed to set the build action for the graphql file to GraphQL compiler:

    • Now when I build my code a Generated folder appears containing an ExampleClient. StrawberryShake.cs file.
    • Register the generated client in your Startup.cs file:

    Use the generated query

    • To use the query I first need to inject the generated client:
    • Then I can invoke the query in a type safe manner:

    Thursday, October 28, 2021

    Azure Pipelines - Unable to determine the location of vstest.console.exe

    A colleague forwarded me a question about a failing build pipeline. When I took a look at the build results, I noticed that the Visual Studio Test task was failing.

    Inside the logs I found more details explaining what was going on:

    ##[warning]No results found to publish.

    ##[debug]Processed: ##vso[task.logissue type=warning]No results found to publish.

    ##[error]System.Management.Automation.CmdletInvocationException: Unable to determine the location of vstest.console.exe ---> System.IO.FileNotFoundException: Unable to determine the location of vstest.console.exe

    ##[debug]Processed: ##vso[task.logissue type=error;]System.Management.Automation.CmdletInvocationException: Unable to determine the location of vstest.console.exe ---> System.IO.FileNotFoundException: Unable to determine the location of vstest.console.exe

       at Microsoft.TeamFoundation.DistributedTask.Task.Internal.InvokeVSTestCmdlet.GetVsTestLocation()

       at Microsoft.TeamFoundation.DistributedTask.Task.Internal.InvokeVSTestCmdlet.ProcessRecord()

       at System.Management.Automation.CommandProcessor.ProcessRecord()

       --- End of inner exception stack trace ---

       at System.Management.Automation.Runspaces.PipelineBase.Invoke(IEnumerable input)

       at System.Management.Automation.PowerShell.Worker.ConstructPipelineAndDoWork(Runspace rs, Boolean performSyncInvoke)

       at System.Management.Automation.PowerShell.Worker.CreateRunspaceIfNeededAndDoWork(Runspace rsToUse, Boolean isSync)

       at System.Management.Automation.PowerShell.CoreInvokeHelper[TInput,TOutput](PSDataCollection`1 input, PSDataCollection`1 output, PSInvocationSettings settings)

       at System.Management.Automation.PowerShell.CoreInvoke[TInput,TOutput](PSDataCollection`1 input, PSDataCollection`1 output, PSInvocationSettings settings)

       at Microsoft.TeamFoundation.DistributedTask.Handlers.LegacyVSTSPowerShellHost.VSTSPowerShellHost.Main(String[] args)

    ##[error]LegacyVSTSPowerShellHost.exe completed with return code: -1.

    On the build server only the latest Visual Studio 2019 Build tools were installed. I noticed that he was still using an older Visual Studio Test Task version. This older version could only handle older Visual Studio version.

    I changed it to the latest version:

    Now I could select Visual Studio 2019(or latest)  as the Test platform version:

    I triggered the build again and this time it succeeded.

    Wednesday, October 27, 2021

    .NET Tools - Cannot find a manifest file

    A .NET tool is a special NuGet package that contains a console application. You can install a .NET tool as a global tool (using the --global argument) or as a local tool (using the  --local argument).

    However when I tried to install a specific tool locally, it failed with the following error message: “Cannot find a manifest file.”

    dotnet tool install StrawberryShake.Tools --local

    Cannot find a manifest file.

    For a list of locations searched, specify the "-d" option before the tool name.

    If you intended to install a global tool, add `--global` to the command.

    If you would like to create a manifest, use `dotnet new tool-manifest`, usually in the repo root directory.

    To install a tool for local access only, it has to be added to a tool manifest file. As I didn’t create such a file, I got the error message mentioned above.

    To fix this, we first need to create a tool manifest file by running the dotnet new tool-manifest command:

    dotnet new tool-manifest

    Getting ready...

    The template "Dotnet local tool manifest file" was created successfully.

    This command creates a manifest file named dotnet-tools.json under the .config directory.

    Now we can retry the install command and this time it will succeed:

    dotnet tool install StrawberryShake.Tools --local

    You can invoke the tool from this directory using the following commands: 'dotnet tool run dotnet-graphql' or 'dotnet dotnet-graphql'.

    Tool 'strawberryshake.tools' (version '12.0.1') was successfully installed. Entry is added to the manifest file C:\projects\IAMCore\.config\dotnet-tools.json.

    If we take a look at the manifest file, we can see the following info:

    More information: https://docs.microsoft.com/en-us/dotnet/core/tools/global-tools

    Tuesday, October 26, 2021

    vscode.dev : Bringing VS Code to the browser

    Although I’m still using Visual Studio (or Rider depending on the mood) for my day to day C# development(and F# occasionally), I use Visual Studio Code for all other languages and web development.

    With vscode.dev, your favorite code editor becomes available everywhere without the need to leave the browser and install anything.

    Thanks to the File System Access API support in modern browser, vscode.dev can access the local file system. This enables scenario’s like local file viewing end editing.

    Integration with Github and Azure DevOps is also available allowing you to sync your changes with repositories on both platforms.

    However don’t expect that vscode.dev is already on par with the desktop version in terms of functionality. For example, there's no internal debugging or terminal with vscode.dev.

    More information: https://code.visualstudio.com/blogs/2021/10/20/vscode-dev

    Monday, October 25, 2021

    MassTransit - Stop handling erroneous messages

    By default when a MassTransit consumer fails to handle a message (and throws an exception), the message is moved to an _error queue (prefixed by the receive endpoint queue name). This is OK for transient exceptions but probably not what you want when you have a bug in your system or there is another reason why none of the messages can be handled succesfully.

    In that case, another feature of MassTransit becomes handy; the kill switch.

    A Kill Switch is used to prevent failing consumers from moving all the messages from the input queue to the error queue. By monitoring message consumption and tracking message successes and failures, a Kill Switch stops the receive endpoint when a trip threshold has been reached.

    You can configure a kill switch for a specific endpoint or for all receiver endpoints on the bus.

    Here is a short example on how to configure the kill switch for all receiver endpoints:

     

    In the above example, the kill switch will activate after 10 messages have been consumed. If the ratio of failures/attempts exceeds 15%, the kill switch with trip and stop the receive endpoint. After 1 minute, the receive endpoint will be restarted. Once restarted, if exceptions are still observed, the receive endpoint will be stopped again for 1 minute.

    If you want to learn more about this feature, check out this video by Chris Patterson, the creator of Masstransit:

    Friday, October 22, 2021

    Service decomposition and service design

    Finding the boundaries of your system and decompose it into multiple services sounds easy, but it certainly isn’t.

    If you are interested in this topic, check out the blog series by Vadim Samokhin:

    Remark: After writing this post, I noticed that Vadim created a blog post linking to the series above and included also some other related posts.

    Thursday, October 21, 2021

    Azure AKS–Save some money using spot node pools

    One of the ways you can save some money using Azure is by using spot node pools for your Azure Kubernetes Service cluster.

    What’s a spot node pool?

    Using a spot node pool allows you to take advantage of unused Azure capacity at a significant cost savings. At any point in time when Azure needs the capacity back, the Azure infrastructure will evict spot nodes. Therefore, Spot nodes are great for workloads that can handle interruptions like batch processing jobs, dev/test environments, large compute workloads, and more.

    Remark: A spot node pool can't be the cluster's default node pool. A spot node pool can only be used for a secondary pool.

    Pricing for a spot node pool

    Pricing for spot instances is variable, based on region and SKU. For more information, see pricing for Linux and Windows. You do have the option to set a max price. In case the price is exceeded the spot node is evicted from your cluster.

    Schedule a deployment to use the spot node pool

    A spot node pool has the label kubernetes.azure.com/scalesetpriority:spot and the taint kubernetes.azure.com/scalesetpriority=spot:NoSchedule. We use this information to add a toleration in our deployment.yaml:

    In case you have multiple spot node pools, you can use a nodeselector to select a specific pool:

    More information

    Winget–A package manager for Windows

    I’ve been using Chocolatey for a long time as an easy way to get my Windows machine configured with all the software I need. With the release of version 1.1 of the Windows Package Manager(WinGet) I thought it was a good time to give it a try.

    Installation

    Chances are high that WinGet is already available on your machine. Open a terminal and type winget. If it is available you should see something like this:

    If not, the Windows Package Manager is distributed with the App Installer from the Microsoft Store. You can also download and install the Windows Package Manager from GitHub, or just directly install the latest available released version.

    Searching a package

    The list of available packages is quite large(more than 2,600 packages in the Windows Package Manager app repository). Just run winget search <SomePackage> to see if the package you are looking for has available there.

    For example let’s search for my favorite git client GitKraken:

    PS C:\Users\bawu> winget search gitkraken
    Naam      Id                Versie Bron
    ------------------------------------------
    GitKraken Axosoft.GitKraken 8.1.0  winget

    For packages inside the Microsoft store you don’t get  a readable id but a hash value instead:

    PS C:\Users\bawu> winget search git
    Name                                  Id                                         Version                    Source
    -------------------------------------------------------------------------------------------------------------------
    Learn Pro GIT                         9NHM1C45G44B                               Unknown                    msstore
    My Git                                9NLVK2SL2SSP                               Unknown                    msstore
    GitCup                                9NBLGGH4XFHP                               Unknown                    msstore
    GitVine                               9P3BLC2GW78W                               Unknown                    msstore
    GitFiend                              9NMNKLTSZNKC                               Unknown                    msstore
    GitIt                                 9NBLGGH40HV7                               Unknown                    msstore
    GitHub Zen                            9NBLGGH4RTK3                               Unknown                    msstore
    GitLooker                             9PK6TGX9T87P                               Unknown                    msstore
    Bhagavad Gita                         9WZDNCRFJCV5                               Unknown                    msstore
    Git                                   Git.Git                                    2.33.1                     winget
    GitNote                               zhaopengme.gitnote                         3.1.0         Tag: git     winget
    Agent Git                             Xidicone.AgentGit                          1.85          Tag: Git     winget
    TortoiseSVN                           TortoiseSVN.TortoiseSVN                    1.14.29085    Tag: git     winget
    TortoiseGit                           TortoiseGit.TortoiseGit                    2.12.0.0      Tag: git     winget

    Installing a package

    After you have found the package you want, installing it is as easy as invoking the following command:

    winget install --id <SomePackage>

    Of course the real fun starts when you create a script that contains all the packages you need for you day-to-day work. Here is the script I’m using:

    Tuesday, October 19, 2021

    ASP.NET Core–Running Swagger behind a reverse proxy

    Today I helped out a colleague who was struggling with the following use case:

    We have an ASP.NET Core Web API with OpenAPI(Swagger) integration enabled. This ASP.NET Core Web API was running behind a reverse proxy(Yarp in case you want to know) and isn’t directly accessibel.

    To explain the problem he had, let’s start from the following situation:

    When browsing to the Swagger endpoint on https://localhost/proxy/swagger, we got the following:

    Do you notice the application url in the address bar vs the url in the Servers dropdown?

    If we try to invoke a specific endpoint through the Swagger UI, Swagger tries to do the call directly to http://internalname:5000 which results in 404 error as the service is not available directly on this address.

    It seems that Swagger doesn’t respect the Forwarded headers as provided through Yarp.

    We fixed it by explicitly reading out the X-Forwarded-Host value and using it to fill up the servers dropdown:

    Monday, October 18, 2021

    Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException–The system cannot find the file specified

    After deploying an application to production, it failed with the following error message:

    The system cannot find the file specified

    Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: at Internal.Cryptography.Pal.CertificatePal.FilterPFXStore (System.Security.Cryptography.X509Certificates, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a) at Internal.Cryptography.Pal.CertificatePal.FromBlobOrFile (System.Security.Cryptography.X509Certificates, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a) at System.Security.Cryptography.X509Certificates.X509Certificate..ctor (System.Security.Cryptography.X509Certificates, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a) at System.Security.Cryptography.X509Certificates.X509Certificate2..ctor (System.Security.Cryptography.X509Certificates, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a)

    Let’s take a look at the code that caused this issue:

    Not much I could do wrong with this code. It tries to load a PFX file from disk and open it using the specified password.

    I checked if the file was there and that was indeed the case. So we have to search for the root cause somewhere else.

    I took a look at the Application Pool settings and noticed that the Load User Profile setting was set to False.

    Aha! After changing this back to True the error got away…

    Friday, October 15, 2021

    DaprCon is coming on October 19th-20th 2021

    What is Dapr?

    Dapr helps developers build event-driven, resilient distributed applications. Whether on-premises, in the cloud, or on an edge device, Dapr helps you tackle the challenges that come with building microservices and keeps your code platform agnostic.

    The Dapr ecosystem keeps growing and now they’ll have their first virtual conference ‘DaprCon’ next week. DaprCon will include a variety of content including a keynote, technical sessions, panel discussions and real-world experiences of adopters building with Dapr

    DaprCon is a fully virtual event that will be streamed on YouTube and attendance is free! To watch the live events just follow these two links:

    More information: https://blog.dapr.io/posts/2021/10/05/join-us-for-daprcon-october-19th-20th-2021/

    Thursday, October 14, 2021

    MassTransit–Message versioning

    MassTransit dedicated a whole documentation page to message versioning but it still wasn’t completely clear to me how it worked.

    Let’s use this blog post to see what’s going on…

    Publishing messages

    Let’s first focus on the sending side.

    Publishing a first version of our message contract

    We’ll start with a first version of our message contract:

    Let’s send this to RabbitMQ using:

    Time to open the RabbitMQ Management portal and take a look how the message payload is constructed:

    Creating a second version of our message contract

    Let’s introduce a v2 version of our message contract:

    If we send it to RabbitMQ in the same way:

    There isn’t such a big difference when comparing the payloads:

    The ‘messagetype’ value is different and of course the message itself. But that’s it.

    Send a backwards compatible version

    Let’s now construct a message that implements both contracts:

    And send that one:

    If we now check the payload, we see that 1 message is put on the queue with the following payload:

    Take a look at the ‘messagetype’. You can see that it contains both the 2 messagecontracts AND the concrete message type we created:

    "messageType": [
    
    "urn:message:Sender:Program+SubmitOrderCommand",
    "urn:message:Messages:SubmitOrder",
    "urn:message:Messages:SubmitOrderV2"
    ],

    Consuming messages

    Now we have a good understanding on what is going on at the sending side, let’s move on to the consuming side.

    Consuming v1 of our message contract

    Let’s create a consumer for that consumes v1 of our message contract:

    And subscribe this consumer:

    After publishing a ‘SubmitOrder’ message, our consumer is called as expected.

    > Old Order consumed

    Consuming v2 of our message contract

    Let’s create a consumer for that consumes v2 of our message contract:

    And subscribe this consumer:

    After publishing a ‘SubmitOrderV2’ message, our consumer is called as expected.

    > New Order consumed

    So far nothing special.

    Consuming the backwards compatible version

    The question is what happens when we send our ‘SubmitOrderCommand’ that implements both message contracts.

    If we have only one consumer subscribed the behavior is completely the same as before and either the old or the new consumer is called.

    But if we have both consumers subscribed:

    Each one will get a copy of the message and be called:

    > Old Order consumed
    > New Order consumed

    Ok, that is good to now. But what happens if one of the consumers now fail?

    Althought the first consumer is called succesfully, the message will still end up on the error queue:

    If we then move the message back to the original queue, both consumers will be called again.

    Wednesday, October 13, 2021

    .NET 6.0 - Migrate to ASP.NET Core 6

    .NET 6 introduces a new hosting model for ASP.NET Core applications. This model is streamlined and reduces the amount of boilerplate code required to get a basic ASP.NET Core application up and running.

    There are a lot of blog posts out there explaining this new hosting model, but I like to share the guide written by David Fowler, software architect on the ASP.NET team.

    He walks you through the building blocks, explains the differences in the hosting model, shares a list of frequently asked questions and provides you a cheatsheet.

     

    Tuesday, October 12, 2021

    ASP.NET Core - InvalidOperationException: 'VaryByQueryKeys' requires the response cache middleware.

    Yes, I’m still rewriting an existing ASP.NET Web API application to ASP.NET Core.

    I ported an existing action method where I was using response caching. I previously ported other action methods that used caching but this one was a little bit different because I had to take the querystring values into account for the caching.

    This is easily arranged by specifying the name of the query string parameter using the VaryByQueryKeys property on the ResponseCache attribute.

    Small tip: If you want to take all query string parameters into account, you can use “*” as a single value.

    When I tried to call this method I got a 500 error. Inside the logs I noticed the following error message:

    InvalidOperationException: 'VaryByQueryKeys' requires the response cache middleware.

    Although caching seemed to work for other scenario’s, when I used the VaryByQueryKeys property I had to add the response caching middleware.

    Here is my Startup.ConfigureServices():

    And my Startup.Configure()

    Monday, October 11, 2021

    ASP.NET Core–Route arguments are case sensitive

    I’m currently rewriting an existing ASP.NET Web API application to ASP.NET Core. Along the way I encountered some issues, here is one specific lesson I learned…

    After porting an ASP.NET Web API controller to .NET Core, a specific action method looked like this:

    Looked OK to me. But when I tried to invoke this action method, I got a 400 error back:

    {
      "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
      "title": "One or more validation errors occurred.",
      "status": 400,
      "traceId": "00-3c02d7d0541cbb49a1790b11a71d871a-885c9350d2c4864c-00",
      "errors": {
        "ID": [
          "The value '{id}' is not valid."
        ]
      }
    }
    It wasn’t immediatelly obvious to me what I did wrong. Maybe you spot my mistake?
    Let’s have a look at the route attribute: [HttpGet(“{id}”)]
    and at the method argument: GetAuthTypeById(int ID)
    As you can see the ‘id’ argument in the route is lowercase where the method argument is uppercase. Turns out that this is the cause of the error above.
    To make it work, I have to make sure that both the route attribute and method argument are using the exact same casing: 

    Friday, October 8, 2021

    ASP.NET Core - Swashbuckle.AspNetCore.SwaggerGen.SwaggerGeneratorException: Ambiguous HTTP method for action

    I’m currently rewriting an existing ASP.NET Web API application to ASP.NET Core.

    I ported a controller from ASP.NET Web API to ASP.NET Core. Here is the end result:

    Everything looked OK at first sight, but when I tried to run the application I got the following error message:

    Swashbuckle.AspNetCore.SwaggerGen.SwaggerGeneratorException: Ambiguous HTTP method for action - Controllers.AuthTypeController.GetAll (VLM.IAM.Services). Actions require an explicit HttpMethod binding for Swagger/OpenAPI 3.0

       at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateOperations(IEnumerable`1 apiDescriptions, SchemaRepository schemaRepository)

       at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GeneratePaths(IEnumerable`1 apiDescriptions, SchemaRepository schemaRepository)

       at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GetSwagger(String documentName, String host, String basePath)

       at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)

       at Microsoft.AspNetCore.Server.IIS.Core.IISHttpContextOfT`1.ProcessRequestAsync()

    The Swagger/OpenAPI middleware requires that you explicitly annotate all your action methods. So I had to update the GetAll action method and annotate it with a [HttpGet] attribute:

    Thursday, October 7, 2021

    ASP.NET Core - ActionResult doesn’t work with IList

    I’m currently rewriting an existing ASP.NET Web API application to ASP.NET Core. Yesterday I blogged about the use of the ActionResult<T> to combine the type safety of typed controllers with the list of out-of-the-box actionresults.

    While introducing the ActionResult<T> class everywhere, I stumbled over one use case where I got a compiler error. Here is the specific code:

    When you try to compile this code in Visual Studio it fails with the following error message:

    CS0029: Cannot implicitly convert type ‘System.Collection.Generic.IList<T> to Microsoft.AspNetCore.Mvc.ActionResult<System.Collection.Generic.IList<T>>.

    The reason is because C# doesn't support implicit cast operators on interfaces. Consequently, we need to convert the interface to a concrete type if we want to use it as our type argument for ActionResult<T>.

    An easy fix is to wrap the IList in a concrete type as I did in the example below:

    Wednesday, October 6, 2021

    ASP.NET Core - How to handle the ‘NotFound’ use case when using a typed controller

    I’m currently rewriting an existing ASP.NET Web API application to ASP.NET Core. While doing that, I (re)discovered some ASP.NET Core features I forgot they existed.

    One of the features that ASP.NET Core offers are ‘typed controllers’. I don’t think that is the official name but you’ll know what I mean when you take a look at the example below:

    In the example above I created an action method GetTodoItem that returned a typed object of type ‘TodoItem’ (or to be even more correct a Task<TodoItem>).  This makes your life as a developer easy as you don’t have to think about the ins and outs of ASP.NET. It almost feels like that this action method is exactly the same as any other method you’ll find on a class.

    But what if wanted to return a 404 message when no TodoItem could be found for the specified id. Should I throw a specific exception? In ASP.NET Web API this was possible through the use of the HttpResponseException.

    In ASP.NET Core there is a better alternative through the usage of ActionResult<T>. By wrapping the returned object in an ActionResult I can keep most of my code(thanks to the magic of implicit cast operators) and start using ActionResult types for specific use cases.

    Let’s rewrite our example:

    Tuesday, October 5, 2021

    GraphQL - Use @skip and @include on fragments

    GraphQL has multiple built-in directives like @deprecated, @include, @skip and some others like @stream and @defer that are not part of the official spec (yet).

    With the @include directive you can conditionally include fields based on a specified argument:

    The @skip directive does exactly the opposite and excludes fields based on a specified argument:

    But applying these directives for every field that should be included or excluded feels like a lot of repetition:

    Here is a trick for you, the @skip and @include directives can be used on fields, fragment spreads, and inline fragments. This can help us to make our GraphQL queries more readable.

    Here is the example rewritten using an inline fragment:

    And here is the same example with the fragment spread into the query:

    Monday, October 4, 2021

    Azure DevOps–Emoji’s

    Both in pull request comments and wiki pages in Azure DevOps, you can use emojis while documenting, adding comments or reviewing requests.

    If you want to find out what emoji’s are supported; you can find the full list here.

     

    Friday, October 1, 2021

    Application Insights Telemetry enricher for Kubernetes

    Quick tip if you are hosting your ASP.NET Core application in Kubernetes: have a look at the Microsoft Application Insights for Kubernetes nuget package.

    Through this package you can enrich your Application Insights telemetry data with Kubernetes specific information:

    After adding the nuget package you can register the enricher by adding the following code:

    More information: https://github.com/microsoft/ApplicationInsights-Kubernetes