3

In a project I have a CS8602 Dereference of a possibly null reference warning.

Out of curiosity I changed the severity level from warning to error. Now I cannot change it back to warning, or default.

I also tried restarting VS multiple times and restoring it. How can I undo this?

enter image description here

baouss
  • 1,312
  • 1
  • 22
  • 52
  • What does your project file look like? (I suspect that's what was changed in the first place, so that's what you'd want to change back.) Hopefully you've got the older version in source control so you can compare them easily... – Jon Skeet Nov 14 '21 at 07:37
  • Thanks for the hint looking at the csproj, this led me to also look around elsewhere and found the culprit in the .editorconfig. – baouss Nov 14 '21 at 10:42

2 Answers2

2

VS made an entry in the .editorconfig. Deleting it reset it to a warning level. Thanks to Jon Skeet for pointing me in the right direction.

baouss
  • 1,312
  • 1
  • 22
  • 52
1

So I realize this isn't exactly the answer to the question that you asked--but it should help explain what's going on and possible fixes you can make:

GetService can return null. The compiler doesn't know that in this case the dereference of the options object and it's Value property is safe*. So because it thinks it can be null it's raising CS8602.

If you want the original behavior, you can ensure that this line isn't in your .editorconfig file:

dotnet_diagnostic.CS8602.severity = XXX

Similarly, there are other places where nullable warnings/errors can be configured. Rather than re-hash them here, I refer you to this QA.

If you want to keep nullable annotations and warnings enabled, you can silence this particular warning with the null suppression operator (!):

Settings settings = serviceProvider.GetService<IOptions<Settings>>()!.Value;

There also exists a method GetRequiredService<T> which will throw an exception if the service doesn't exist. The return value of this method is non-nullable and would therefore not trigger any warnings:

Settings settings = serviceProvider.GetRequiredService<IOptions<Settings>>().Value;

But--and a bit of a tangent here--you're doing it wrong. You should never call BuildServiceProvider yourself. There's overhead to calling it, but more importantly the services you get won't necessarily be the same ones the application gets. There could now technically be two different instances of a singleton, including your settings object! This is such a code-smell/bad practice that in AspNetCore they've added a built-in analyzer that will yell at you for it.

The proper way to register your interface singleton in this case is to use the factory version of AddSingleton which gives you access to the built IServiceProvider (the one that the rest of the application will use):

builder.Services.AddSingleton<ISettings>(
   sp => sp.GetRequiredService<IOptions<Settings>>().Value
);`

Now that said, AddOptions is already called by the Azure Functions runtime. Calling Bind the way you are is unnecessary. You should just call Configure and pass the configuration section directly. You can retrieve your IConfiguration object from the FunctionsHostBuilderContext

var context = builder.GetContext();
var section = context.Configuration.GetSection("YourSectionName");
builder.Services.Configure<Settings>(section);

// or if you want to bind to the root of the configuration 
builder.Services.Configure<Settings>(context.Configuration);

* In fact, once you AddOptions, any request for IOptions<>--no matter the generic argument nor whether you've called Configure<> for that type--will succeed.

pinkfloydx33
  • 11,863
  • 3
  • 46
  • 63
  • Thanks for that detailled information. I am trying to refactor. The main reason why I called BuildServiceProvider().GetService() was that I need access to the strongly-typed configuration within Startup.cs... One configuration value, eg `RunsRemotely` then decides whether another service is authenticated using a ManagedIdentityCredential (only available in Azure) or a AzureCliCredential (only available locally)... do you have a suggestion how I can accomplish this? – baouss Nov 14 '21 at 18:51
  • You can use `DefaultAzureCredential` instead. It will try one of ten or so different ways to Auth. One is managed ID and another is cli. You don't need any settings. It's actually how I have mine running. Otherwise if you need the settings, I would pull it out of the config directly and don't build the provider. Ie `context.Configuration["section:setting"]` or `context.Configuration.GetSection("section").Get()`. Don't need the provider built for anything – pinkfloydx33 Nov 14 '21 at 19:26
  • Thanks again... I need the config in order to configure other services in the startup.cs, e.g. for conneciton strings etc.... You recommened to not explicitly call BuildServiceProvider() but instead use the one provided in the (e.g.) AddSingleton() method... I need to do this several times. Is there performance penalty of calling sp.GetRequiredService(); several times? Is it worse than calling BuildServiceProvider() once? – baouss Nov 14 '21 at 20:50
  • 1
    No penalty...at least not compared to build service provider. Call GetRequiredSerice as much as you want. – pinkfloydx33 Nov 14 '21 at 21:10
  • 1
    Thanks sharing your in-depth knowledge on the subject. I already feel that code getting less smellier :D – baouss Nov 14 '21 at 21:21
  • Btw, regarding the DefaultAzureCredential... I found out that this had a problem to get a token for the scope `https://database.windows.net` (Azure Sql), complaining that the format was not valid... I then opted for a `ChainedTokenCredential cred = new(new ManagedIdentityCredential(), new AzureCliCredential())` which worked fine. – baouss Nov 14 '21 at 21:31
  • That's weird. I've not had that issue with Azure Sql . Make sure you have the latest Azure.Identity and Microsoft.Data.SqlClient packages. Also I would reverse the order of the tokens. It tries them in that order. **If** the MSAL endpoint is available it will try the MI token first and if it fails (ie MI has no perms) it won't move to the next one. It'll only fall back if the MSAL endpoint doesn't exist. But if you do it the other way then it will fallback from cli to MI – pinkfloydx33 Nov 14 '21 at 21:52