DEV Community

loading...

How to shut the warning up when using the combination of Blazor code-behind, property injection, and C# nullable reference types

jsakamoto
Microsoft MVP for Visual Studio and Development Tech. (prefer C#, .NET Core, ASP.NET Core, Azure Web Apps, TypeScript, and Blazor WebAssembly App!)
Updated on ・5 min read

Introduction

C# nullable reference types

Since C# 8.0, we can use the "nullable reference types" feature on C# programming.

It helps us to build more robust applications on C#.

Adding the <Nullable>enable</Nullabl> MSBuild property value into the C# project file (.csproj ) is one of the ways to enable this feature.

<!-- This is .csproj file -->
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    ...
    <!-- 👇 Add this line -->
    <Nullable>enable</Nullable>
    ....
Enter fullscreen mode Exit fullscreen mode

Code-behind in Blazor programming

In Blazor programming, to implement a component, we usually write an HTML markup and C# code mixing in a Razor file (.razor), and place a C# code inside a @code { } block.

<!-- This is MyComponent.razor file -->
<p>Here is an HTML markup.</p>

@code {
  // Here is a C# code block.
}
Enter fullscreen mode Exit fullscreen mode

And another way we can use the "code-behind" way.
In this way, we write a C# code in a usual C# source file (.cs) as a partial class.

// This is MyComponent.razor.cs file
public class partial MyComponent
{
  // Here is a C# code separated from the .razor file
}
Enter fullscreen mode Exit fullscreen mode

Sometimes the "code-behind" style is convenient to make a Blazor component readable and maintainable.

Dependency Injection in a "code-behind" of a Blazor component

We have to use the @inject directive in a Blazor component (.razor) to retrieve any service from the DI container.

<!-- This is MyComponent.razor file -->

<!-- 👇 The "IJSRuntime" service instance will be injected 
          from DI container. -->
@inject IJSRuntime JS
Enter fullscreen mode Exit fullscreen mode

@inject directives work as property injection.

So if you want to inject a service in a "code-behind" C# source file, you can do it with a property that is annotated with [Inject] attribute instead of the @inject directive.

// This is MyComponent.razor.cs file
public class partial MyComponent
{
  // 👇 The "IJSRuntime" service instance will be injected 
  //    from DI container.
  [Inject]
  private IJSRuntime JS { get; set; }
  ...
Enter fullscreen mode Exit fullscreen mode

What happens if we mix these 3 programming styles?

If we mix these three programming factors - C# nullable reference type, code-behind style, property injection in code-behind - what will happen?

The answer is, it causes a terrible warning, "CS8618".

image

"Warning CS8618 Non-nullable property 'JS' must contain a non-null value when exiting constructor. Consider declaring the property as nullable."

However, in my understanding, the property that will be injected from the DI container might never be null where the code place that the application developer will write.

So I feel this CS8618 warning does not make sense.

What can we do to avoid this warning?

I have three ideas (but are not perfect solution) + [May 2, 2021, Updated] One more good idea I heard from others

I'm not sure about the perfect solution to this problem at this time.

But I came up with three ideas to mitigate the problem.

[May 2, 2021, Updated] I heard one more good idea from @taks.
So I added that method I heard into the list below.

1. Make the property nullable.

One of the ideas is to make the property nullable.
To do this, append the "?" symbol to the end of the type name.

[Inject]     // 👇 "?" means this property can be null.
public IJSRuntime? JS { get; set; }
Enter fullscreen mode Exit fullscreen mode

Instead, we have to do a null-check whenever before referencing the property.

// 👇 It required to perform null-check.
if (this.JS != null)
{
    await this.JS.InvokeVoidAsync("foo");
}
Enter fullscreen mode Exit fullscreen mode

(If we do not this, the C# compiler raise another warning "CS8604".)

This solution is strictly the right way.

But as I said before, the injected property will never be null in the developer's code as a practical matter.

So I feel this solution is too redundant.

We also can use the "!" symbol to make the C# compiler ignore the condition that the variable is null or not.

//          👇 The "!" symbol suppresses the null state check.
await this.JS!.InvokeVoidAsync("foo");
Enter fullscreen mode Exit fullscreen mode

But I do not prefer this way because this way loses the benefits of the nullable reference type feature.

2. Add [AllowNull] attribute

The next of the ideas is to make the property is annotated with the [AllowNull] attribute.

// 👇 Add the [AllowNull] attribute
[Inject, AllowNull]
public IJSRuntime JS { get; set; }
Enter fullscreen mode Exit fullscreen mode

This solution almost works well ― except the property is acceptable to the null even it is a non-nullable type.

// 👇 There is no compiler error even if set to null.😥
this.JS = null;
Enter fullscreen mode Exit fullscreen mode

3. Use "#pragma waning disable"

The last of the ideas is to make the property is surrounded by the "warning disable" pragma.

// 👇 Surround the property by the "warning disable" pragma
#pragma warning disable CS8618 
    [Inject]
    public IJSRuntime JS { get; set; }
#pragma warning restore CS8618 
Enter fullscreen mode Exit fullscreen mode

This solution also works fine.
And, I think this solution is reflected what the developer wants to do most directly.

But this solution breaks the indent of the C# code, and the description volume is a little bit much, so the code loses readability.

Due to this, I do not prefer this solution.

4. [May 2, 2021, Updated] Use a null value with the ! symbol to initialize the property

I did not know until I heard that, but we can initialize the non-nullable reference type variable by null value with the ! symbol, like this:

[Inject]
public IJSRuntime JS { get; set; } = null!;
                                 //  👆 "null" with the "!" symbol
                                 //     can be set to non-nullable
                                 //     reference type property.
Enter fullscreen mode Exit fullscreen mode

This implementation suppresses the CS8618 warning because the property is initialized, even the initial value is null.

And in my understanding, this solution doesn't have more side-effects any non-desirable.

So this solution became my best favorite solution.

Conclusion

In my job and hobby, I often use the first solution out of the above 3.
But I am significantly dissatisfied with every solution above because those are not smart.

I hope a near future C# compiler and syntax resolve this annoying problem smartly.

[May 2, 2021, Updated] As I said before, using the null value with the ! symbol to initialize the property became my best solution at this time.

Actually, I use another solution too. That is, I don't mix it.
This means I use code-behind style, but I write the "@inject" directive in .razor files, do not write the properties that will be injected from DI container in .cs files, like this code:

<!-- This is MyComponent.razor file -->

<!-- 👇 Inject the service in .razor file ... -->
@inject IJSRuntime JS
Enter fullscreen mode Exit fullscreen mode
// This is MyComponent.razor.cs file
public partial class MyComponent {
  ...
  // ... and use it in code-behind C# file.
  await this.JS.InvokeVoidAsync("foo");
Enter fullscreen mode Exit fullscreen mode

If somebody knows a good solution for this problem, I appreciate it if you let me know via comment on this post.

Happy coding! :)

Discussion (1)

Collapse
ryanbuening profile image
Ryan Buening

What about public IJSRuntime JS { get; set; } = default!; Would that be any better than public IJSRuntime JS { get; set; } = null!; ?