Multi-Targeted NuGet Package Gotchas!

Multi-Targeted NuGet Package Gotchas!

In order to migrate your application from .NET Framework to .NET Core, one part of the migration is making sure all of your dependencies via NuGet packages will work on .NET Core. Most packages nowadays are multi-targeted. Meaning they target various versions of .NET Framework, .NET Core, and .NET Standard. Here are a few of multi-targeted NuGet package gotchas that I’ve discovered in my own migration.

Migrating from .NET Framework to .NET Core

This post is in a blog series for migrating from .NET Framework to .NET Core. Here’ are some earlier post if you need to catch up:


Check out my YouTube channel where I also cover this topic


Before we get into the gotchas, I want to just clarify that in your migration from .NET Framework to .NET Core, you need to look at all of your NuGet Packages that you depend on, and confirm they are targeting .NET Core or .NET Standard.

The simplest way to do this is to go to and do a search for your package. Here’s an example of Newtonsoft.Json

Under the dependencies section, it lists each platform it targets as well as the dependencies it needs when running under that platform. The thing to be on the look out for is .NET Standard 2.0 (or lower) or .NET Core. It should list .NET Framework as you’re already using it under that if you’re attempting to migrate.

This is what multi-targeting. This package is built for various targets and the NuGet package you download contains the assemblies for each of these targets. When you build your project, the appropriate assembly is used depending on which platform you’re running under.

Gotcha #1: Behavior

It would be fair to assume that the package you use would behave the same under .NET Framework or .NET Core. However, that’s not always the case. Library authors will use #if directives to have certain code only exists when built when targeting a certain platform. Because of this, there may be differences in behavior when you run the exact same code on .NET Framework or .NET Core.

An example of this that I ran into was with NodaTime. When serializing a ZonedDateTime object to JSON via JSON.NET, the output was different.

.NET Framework Output

.NET Core Output

Thankfully this issue was resolved in the 3.0 release of NodaTime. It now consistently uses the same serialization regardless of target. However, this still proves that there may be differences in behavior that you may not expect.

Gotcha #2: API Surface

Just because you’re using the same version of a NuGet package against .NET Framework and .NET Core does not mean that each target has the same API surface. Just like gotcha #1, this is also because of #if directives. Meaning, library authors may leave out APIs for certain targets.

An example of this is with the AWS SDK. Specifically the AWS S3 Package.

If you’re using this package with .NET Framework, there are non-Async APIs such as the GetObject method on the AmazonS3Client. However, once you’re targeting .NET Core it uses the .NET Standard target which does not contain any sync methods. Instead, they are all marked as internal. This means that you will not be able to even build your project when you target .NET Core.

.NET Framework

.NET Standard

Gotcha #3: Serializing BCL Types

This last gotcha isn’t so much with NuGet packages, but more so with JSON.NET and serializing standard types that are in the Bass Class Library.

This is very specific when you’re using JSON.NET and serializing/deserializing with TypeNameHandling.All. This setting adds type information to the JSON output from SerializeObject so that you can use it later when deserializing.

The problem is that BCL types live in different assemblies. In .NET Framework this is referred to as mscorlib. In .NET Core it’s System.Private.CoreLib.

When you serialize a List<string> here is the output in .NET Framework:

Output from .NET Core:

If you try and deserialize the .NET Core output when running on .NET Framework it will throw a JsonSerializationException.

This is something to be aware of if during your migration you’re communicating between services/applications that are mixed between .NET Framework and .NET Core.

Multi-Targeted NuGet Package Gotchas

If you’ve run int other gotchas during a migration, let me know in the comments or on Twitter.

Enjoy this post? Subscribe!

Subscribe to our weekly Newsletter and stay tuned.

CAP: Event Bus & Outbox Pattern

Outbox Pattern

If you’re thinking of building or already are implementing a system using async messaging (SOA or Microservices) then you need to start thinking about what type of messaging library you want to use in front of a message broker. CAP is an Event Bus that implements the Outbox Pattern to deal with distributed transactions.


I did a live stream exploring CAP that is over on my YouTube Channel.

Distributed Transaction

When you’re using messages (events) to communicate between systems you will run into the situation where you need to save data to your database, then publish an event to a message broker. These events will then be received up by other systems for their own internal use.

The problem arises when two situations occur:

  • You save data to your database but there is a failure when publishing the event to your message broker
  • You publish an event to your message broker that something occurred in your system, but then when trying to save to your database, it fails.

In both situations, there is no consistency between what your database has saved and what you have published to the message broker.

What you want in this situation is one atomic transaction that can save data to your database and publish the event. If either fails, the other is rolled back. Basically, a distributed transaction.

Outbox Pattern

The Outbox pattern solves this issue by using a single transaction to perform both actions. What this involves is rather than publishing the event directly to the message broker, it serializes the event and saves it to a table in the same database using the same transaction for persisting your application data.

This is a diagram of the outbox pattern described in Microsoft’s eShopOnContainers reference application

Outbox Pattern

Once the event is persisted to the table within the database, it will then be published to the message broker. If the message broker is unavailable or there is a failure publish to the message broker, the library you’re using will retry to publish. It provides reliability that you are not losing messages that you need to publish.


I stumbled upon the CAP project as I was looking for references for how other libraries have implemented the outbox pattern.

The following just illustrates the simplicity of using the CAP library and it’s API. My example uses MySQL as the database and RabbitMQ as the message broker.

First step is to include the relevant packages to your project

Next is to configure the Startup. In the ConfigureServices we need to use the AppCap() with various options to configure our database and message broker connections. Also in the Configure I’ve added the UseCapDashboard() which provides a little web-based UI for showing the messages and event subscriptions.

Now that we have CAP configured, the first step is going to be publishing an event. You do this by using the ICapPublisher. The parameters are a name and the contents of the event.

The key thing to point out here is that BeginTransaction() is an extension method from CAP. This extension method starts the MySQL transaction but also passes it along to the CapPublisher so they are using the same transaction. CapPublisher needs this transaction because it is going to write the published events to a table within your database.

CAP automatically creates this table. There is no setup required on your end.

Finally, we can create a subscriber/receiver for this event. To do so is simply a matter of implementing ICapSubscribe and add the CapSubscribe attribute to a method with the appropriate arguments that match what was published.

That’s it!

That’s the simplest example. And it’s pretty simple and hopefully gives you an idea of how CAP works with implementing the outbox pattern. There are various message brokers it supports such as Kafka, RabbitMQ, and Azure Service Bus. On the database side it supports SQL Server, MySQL, PostgreSQL, and MongoDB.

My entire sample application is available on GitHub.

Enjoy this post? Subscribe!

Subscribe to our weekly Newsletter and stay tuned.

Configuring Errors and Warnings in C#

Configuring Errors and Warnings in C#

I recently stumbled upon some code that was making an awaitable method call but was not being awaited. I noticed the issue because it was a compiler warning CS4014 and was highlighted in code. I immediately fixed the issue, however, my second action was configuring errors and warnings in C#.


Make sure to check out my YouTube channel where I often post related content that accompanies my blog posts.


You can have a .NET build fail by having the compitler producing errors for normally what are considered warnings. Such as as CS4014

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the ‘await’ operator to the result of the call.

The code in question looked something similar to this as I seen within Rider

There are very few cases where I would not want to await. Because of that, I wanted this to cause a compiler error so going forward it was impossible to do this by accident and not notice.

The fix is really simple, in your SDK style csproj file, specify an element called WarningAsError inside a PropertyGroup. The value is a comma-separated list of warning numbers to treat as errors.

Now when you look or build it produces an error instead of a warning.

The great thing about Roslyn analyzers is they are also helpful in finding possible code issues. One of my favorites is the Microsoft.VisualStudio.Threading.Analyzers that is available as a NuGet package. Simply add it to your project and you’ll immediately get over a dozen analyzer rules applied to your codebase. The package is best for detecting Sync over Async which I’ve written about before.

One of them is VSTHRD103 that finds any code that synchronously blocks. using Wait() on Task is a good example.

We can also turn this into an error by adding VSTHRD103 in our WarningAsErrors list.


However, when you start adding some Roslyn analyzers you may not like some of the warnings they provide. One for me, which is highly debated, is the use of the Async suffix. Since our entire codebase is Async, we do not follow this convention. This is rule VSTHRD200 which I did not want to be warned about. This is also pretty simple by adding a NoWarn element.


The last tip is instead of specifying all of these in each project file (csproj) definition, you can create a file called Directory.Build.props and place it at the root of where your solution is located. This file will then apply to every project.

Configuring Errors and Warnings in C#

Do you have any preferred Roslyn analyzers or warnings you like to treat as errors? Let me know in the comments or on Twitter.


Enjoy this post? Subscribe!

Subscribe to our weekly Newsletter and stay tuned.