Tuesday, November 24, 2020

Snapper - A snapshot does not exist

The first time I ran Snapper it failed with the following error message:

    Snapper.Exceptions.SnapshotDoesNotExistException : A snapshot does not exist.

    Apply the [UpdateSnapshots] attribute on the test method or class and then run the test again to create a snapshot.

  Stack Trace:

    SnapshotAsserter.AssertSnapshot(SnapResult snapResult)

    Snapper.MatchSnapshot(Object snapshot, SnapshotId snapshotId)

    Snapper.MatchSnapshot(Object snapshot, String childSnapshotName)

    Snapper.MatchSnapshot(Object snapshot)

    SnapperExtensions.ShouldMatchSnapshot(Object snapshot)

    GraphQLTests.Queries_Succeed(String queryName) line 57

    --- End of stack trace from previous location where exception was thrown ---

This error makes sense as Snapper expects that a snapshot exists when you run your tests. To fix this, you need to update your tests and add the [UpdateSnapshots] attribute above your test method or class.

When Snapper finds this attribute it will not search for an existing snapshot but create a new one(or replace an existing one). After you have run your tests for the first time you can remove the attribute.

Don’t forget to check in the generated files in the ‘_snapshots’ folder to your source control system:

Monday, November 23, 2020

Using XUnit Theory with Snapper

To test my GraphQL schema I first used SnapShooter.

From the documentation:

Snapshooter is a flexible .Net testing tool, which can be used to validate all your test results with one single assert. It creates simply a snapshot of your test result object and stores it on the file system. If the test is executed again, the test result will be compared with the stored snapshot. If the snapshot matches to the test result, the test will pass.

It is really a good fit to test your GraphQL API. Unfortunately I got into trouble when I tried to combine it with XUnit Theory. I had created 1 tests that loaded multiple graphql files and validates them:

This didn’t work as expected because the same snapshot is used for every test run. As a consequence it fails when the same test runs again with a different parameter value .

Probably there is a way to fix it, but I was lazy so I took a look online and found Snapper, which offers similar features as SnapShooter but supports the XUnit Theory functionality through a feature called child snapshots. Let’s update our example:

Friday, November 20, 2020

Enable .NET 5 in your Azure App Service

After deploying a .NET 5 application in an Azure App Service, I got the following error message when I tried to run the application:

HTTP Error 500.31 - ANCM Failed to Find Native Dependencies
Common solutions to this issue:
The specified version of Microsoft.NetCore.App or Microsoft.AspNetCore.App was not found.

This is because in Azure App Service .NET 5 is not enabled by default (yet).

Let’s see how to fix this:

  • Open the Azure portal
  • Go to the App Service you want to configure
  • Click on Configuration in the Settings section

  • Go to the Stack Settings and change the .NET Framework version to .NET 5(Early Access)

  • Click Save
  • Restart the App Service

Thursday, November 19, 2020

.NET 5–Source Generators–Lessons Learned–Part 3

One of the new features in .NET 5 that triggered my curiosity where source generators. I did some investigation on how to use them and want to share the lessons I learned along the way.

Yesterday I got my first source generator finally up and running. Now it is time to move on and do something more interesting. I found an example on Github that created a strongly typed config class based on your appsettings.json. I tried to duplicate the code but when I build my application the source generator didn’t run.

In the build output I noticed the following warning:

Severity

Code

Description

Project

File

Line

Suppression State

Detail Description

Warning

CS8034

Unable to load Analyzer assembly c:\lib\netstandard2.0\System.Text.Encodings.Web.dll: Could not find a part of the path 'c:\lib\netstandard2.0\System.Text.Encodings.Web.dll'.

SourceGeneratorsExample

1

Active

System.IO.FileNotFoundException: Could not find a part of the path 'c:\lib\netstandard2.0\System.Text.Encodings.Web.dll'.File name: 'c:\lib\netstandard2.0\System.Text.Encodings.Web.dll' ---> System.IO.DirectoryNotFoundException: Could not find a part of the path 'c:\lib\netstandard2.0\System.Text.Encodings.Web.dll'.   at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)   at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share)   at Roslyn.Utilities.FileUtilities.OpenFileStream(String path)   at Roslyn.Utilities.FileUtilities.OpenFileStream(String path)   at Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.GetAnalyzerTypeNameMap(String fullPath, AttributePredicate attributePredicate, AttributeLanguagesFunc languagesFunc)   at Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.Extensions`1.GetExtensionTypeNameMap()   at Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.Extensions`1.AddExtensions(Builder builder)-----System.IO.DirectoryNotFoundException: Could not find a part of the path 'c:\lib\netstandard2.0\System.Text.Encodings.Web.dll'.   at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)   at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share)   at Roslyn.Utilities.FileUtilities.OpenFileStream(String path)-----

The source generator is using System.Text.Json and System.Text.Encodings.Web to parse the config files. But it seems that the source generator cannot find and use these references.

How to use external references in source generators?

There is some extra work that needs to be done before external libraries can be loaded successfully.

For every reference that your source generator needs you need to do 2 things:

  1. Take a private dependency on the package(PrivateAssets=all). By doing this consumers of the generator will not reference it.
  2. Set the dependency to generate a path property by adding GeneratePathProperty="true". This will create a new MSBuild property of the format PKG<PackageName> where <PackageName> is the package name with . replaced by _.

Now we can then use the generated path property to add the binaries to the resulting NuGet package as we do with the generator itself:

More information in the source generators cookbook:  https://github.com/dotnet/roslyn/blob/master/docs/features/source-generators.cookbook.md#use-functionality-from-nuget-packages

Wednesday, November 18, 2020

.NET 5–Source generators–Lessons learned–Part 2

One of the new features in .NET 5 that triggered my curiosity where source generators. I did some investigation on how to use them and want to share the lessons I learned along the way.

Yesterday I wrote my first generator. Unfortunately nothing happened and no new code was generated. I tried to attach the debugger but when I did a rebuild still nothing happened. It seems that the code itself was never called.

It took me some time to figure out why it didn’t work; it turns out that you have to add your source generator to a separate assembly(which makes sense). In my first try I just added the source generator logic to the same project as the other code but this doesn’t work.

How to package a source generator?

To get source generators up and running, you need to create a separate project add the source generator logic there.

So create a new .NET Standard project and add a reference to the following package:

Generators can be packaged using the same method as an Analyzer would. Therefore the generator should be placed in the analyzers\dotnet\cs folder of the package for it to be automatically added to the users project on install. To enable this we need to add the following to your project file:

Now we can add our source generators here.

There is one last step you also need to take into account. If you just create a reference to your SourceGenerator project it will not work. You have to change the project reference and include `OutputItemType="Analyzer" ReferenceOutputAssembly="false"`:

Now the source generator is finally invoked when I build my code.

Tuesday, November 17, 2020

.NET 5–Source generators–Lessons learned - Part 1

One of the new features in .NET 5 that triggered my curiosity where source generators. I did some investigation on how to use them and want to share the lessons I learned along the way.

But let’s first start with an explanation what “a source generator” actually is:

A Source Generator is a new kind of component that C# developers can write that lets you do two major things:

  1. Retrieve a Compilation object that represents all user code that is being compiled. This object can be inspected and you can write code that works with the syntax and semantic models for the code being compiled, just like with analyzers today.
  2. Generate C# source files that can be added to a Compilation object during the course of compilation. In other words, you can provide additional source code as input to a compilation while the code is being compiled.

So in short, source generators are a new compiler feature that allow you to inspect existing code and generate new code(remark: you cannot change existing code).

I started with the example mentioned in the blog post above:

But when I build my project, nothing happened and it looked like no code was generated. What was I doing wrong?

How to debug a source generator?

So the first question became how can I debug a source generator? Now I had no clue how to investigate what was (not) happening.

The trick is to add a `Debugger.Launch()` statement inside your code:

Now when you build your code a popup should appear that asks you how you what debugger you want to launch.

Monday, November 16, 2020

GraphQL Hot Chocolate–Enable global authentication

There are multiple ways to enable authentication in Hot Chocolate. Here is simple approach:

Step 1 – Enable ASP.NET Core authentication

First step is to enable authentication at ASP.NET Core level. Let’s use JWT token for authentication:

Step 2- Enable authentication at the root GraphQL query

The second(and already the last step) is to enable authentication on the root query type. By providing no role or policy names we’re simply saying the user must be authenticated.