With the introduction of nullable reference types in C# 8.0, developers were promised a safer and more reliable way to handle null values.
In real world API development, nullable reference types often fall short, leaving developers with a false sense of security.
By the end of this article, you’ll understand how nullable reference types can lead to unexpected null pointer exceptions and how to fix it. If you just want the github repo.
This article is talking mostly about an AspNetCore WebApi that serves requests from a client (most often a SPA frontend) and uses EntityFramework Core to store data.
The Code
One single endpoint which takes in a name and greets you in all caps.
The Three Problems
Nullable references are enabled.
My GreetingRequest.Name
is a string
not a string?
, so I’m fine calling ToUpper()
I have no hints, no warnings, no squiggly lines.
In fact if I do this $"HELLO {request.Name?.ToUpper()}"
I’m getting a little hint that the question mark operator here is unnecessary, because I’m safe.
1) Deserialization of http requests into GreetingRequest
If you post {}
or { name: null }
AspNetCore will just accept your request and crash as soon as it hits ToUpper()
.
2) The generated openAPI schema.
It’s just plain wrong. It will mark GreetingRequest.name
as optional and nullable.
If you try to generate clients from this schema, they will reflect that and all the properties will be optional and nullable.
3) The EF Core entity class when loaded from the DbContext
This depends on whether NullableReferenceTypes are enabled in your EF Core project or not.
If it’s disabled, the Person.Name property will map to a nullable field in the database.
If you call var person = await dbContext.Persons.FirstAsync()
and the name is null in the database field,
it will be null in your Person
instance too.
The Solutions
1) Deserialization of http requests into GreetingRequest
The first step to fixing this is the required
keyword. Let’s adjust our GreetingsRequest
to use it.
This will prevent any clients from posting {}
. You can not omit required
properties.
The standard AspNetCore request validation will catch this and respond with a 400 BadRequest.
Sadly, this does not prevent direct null posts. { name: null }
gets past the request validation just fine.
I have no idea why. To me this looks like a bug.
I have not found any attribute or setting in System.Text.JsonSerialization
to fix this.
So I wrote my own middleware
to validate that properties marked with required
are not null and respond with a 400 BadRequest if so.
Great! That fixes it!
Now I can actually trust the static analysis. Now I’m actually safe.
2) The generated OpenAPI Schema
The generated schema for our GreetingRequest
looks like this.
I’m not an expert on the openAPI specification,
but from what I understand this schema says name
is optional and nullable.
A correct, meaning name
is required and not nullable, looks like this.
Notice the new required
array and the missing "nullable": true
.
Again, I don’t really understand why. This looks like another bug to me.
It feels like the openAPI schema generator for AspNetCore should respect AspNetCore features such as nullable reference types and especially the required
keyword.
This can be fixed by adjusting the schema generation.
Great! Another one fixed.
As you can see, I’m just checking for the required keyword and then adjust the schema however I please.
You could just check for nullability instead and skip the required keyword altogether.
I like the required
keyword, because it forces you on the API side to adhere to your own contract.
You can never forget to set it. You can’t set it to null.
3) Loading EF Core Entity Classes
This one is less egregious and definitely not a bug.
But I think it makes sense to mention it, while talking about lying nullable reference analysis.
If you have your ef core entities in a project with nullable references disabled,
string
will be nullable on the database level by default.
These are easy fixes. Turn on nullable reference types or simply mark the property as required explicitly.
Now you wont ever be able to write null
into your database, so you will never be able to get null
out either.
Initialize your strings for good measure too.
Conclusion
This makes nullable reference types actually real to me.
Now I can actually trust the types that are coming into my API from either the client or ef core.
The type of applications I’m working on are mostly
SPA frontend => AspNetCore Api => PostgresDB via EF Core
Without these fixes taking the static analysis serious, made no sense to me.
90%+ of the code I write is either getting data from a request or from ef core
and neither of them respected nullable reference types.
You can check out the working code in this github repo.