Whilst we may look forward to the new ‘syntatical sugar’ we get with updates in the C# language versions, I think one of the most pertinent new features in C#8.0 is the option to enable nullable checks on your code. Most devs will bear the battle scars of null reference bugs and, as is their nature, these often present themselves at runtime, in production, on a Friday at 4pm!
It’s not an all encompassing solution but, coupled with some code analyzers, a suitable ruleset and a little discipline to stop ignoring the warnings on compile (we’ve all done it!), it’s a good start to any new solution in setting out best practice for your code.
So how might this improve web api validation? First off, let’s take a new dotnet core web api solution and create a dotnet standard class library. Nullable reference types are only supported by dotnet core 3+ and dotnet standard 2.1, however, due to the lack of support for 2.1 (dotnet 4.6.2+ only supports dotnet standard 2.0), it’s still common for class libraries to target 2.0. To get around this lack of support, you can include dotnet core 3.0/3.1 as a target framework in your project to enabled support for nullable reference types. I’ve also added in a couple of analyzers – StyleCop and SonarQube are my preferred choices:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFrameworks>netcoreapp3.1;netstandard2.0</TargetFrameworks> <LangVersion>latest</LangVersion> <CodeAnalysisRuleSet>..\..\CodeAnalysisRuleSet.ruleset</CodeAnalysisRuleSet> <Nullable>enable</Nullable> <IsPackable>false</IsPackable> </PropertyGroup> <ItemGroup> <PackageReference Include="SonarAnalyzer.CSharp" Version="8.2.0.14119"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.113"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> </ItemGroup> </Project>
Let’s create a simple domain entity, Client, with a few properties:
public class Client { public string FirstNames { get; set; } public string LastName { get; set; } public string Title { get; set; } public string Salutation { get; set; } }
After building I immediately get a number of warnings and the Intellisense ‘squiggles’ kick in:
Putting aside the xml commenting warnings (which I think a vital warning as, at the very least, it makes you consider what has to be public if you want to minimise your time commenting), let’s pick up on the CS8618 description:
Non-nullable property 'FirstNames' is uninitialized. Consider declaring the property as nullable.
What this is saying, in layman terms, is that if you are happy for this property to ever be null, you must declare this. This ensures that devs, designers, architects and whoever else is involved in the requirements needs to consider, up-front, what a valid model entails. That’s good enforcement in my opinion.
Back to the example and let’s consider a possible set of requirements:
- LastName is an absolute must
- FirstNames a little more uncertain but let’s decide it’s mandatory
- Title is not so commonly used these days so optoinal
- Salutation is a nice-to-have. Optional.
Let’s plug this back into our code:
0 warnings and, as you can see the first two properties are declared as default! which enforces not null/empty; The last two use a nullable string type to demonstrate null/empty is allowed.
Web Api Use
Let’s see how this impacts on a Web Api operation referencing this entity. For the purpose of this demo, I’ll have a single, stubbed POST operation:
using AgileTea.Learning.Domain.Entities; using Microsoft.AspNetCore.Mvc; namespace AgileTea.Learning.WebApi.Controllers { [ApiController] [Route("[controller]")] public class ClientController : ControllerBase { [HttpPost] public ActionResult Post([FromBody] Client client) { return Created("/Client/1", client); } } }
So let’s test this out in Postman – I’m going fire the following json to my api:
{ }
(That’s not a typo – I’m literally sending an empty object)
Here’s the response I get:
{ "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1", "title": "One or more validation errors occurred.", "status": 400, "traceId": "|b63323fa-49feb72ee95bb74a.", "errors": { "LastName": [ "The LastName field is required." ], "FirstNames": [ "The FirstNames field is required." ] } }
A 400 response (Bad Request), plus details of what is missing. I think, for the cost of enabling code analysis and nullable type references, that’s a decent return!
1 Comment
Improving Code Quality with SonarCloud – jones.busy · 23rd February 2020 at 2:45 pm
[…] mentioned in a previous post how recent versions of C#8, along with some reputable code analysers have helped developers improve […]