Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ConfigurationValidation - wrapper for IConfiguration nullable checks #526

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

gajdusep
Copy link
Contributor

After changes of nullability of some types in Microsoft.Extensions 7.0.0

  • added to simplify and unify nullable checks of IConfiguration objects

@gajdusep gajdusep requested a review from a team as a code owner November 23, 2022 14:27
/// <param name="sectionName">Name of configuration section to get the object from</param>
/// <returns>Non-nullable object of type T</returns>
/// <exception cref="ArgumentNullException">If the type T is not found in the given section of the configuration</exception>
public static T GetNonNullableOrThrow<T>(IConfiguration configuration, string sectionName)
Copy link
Contributor

@AndreyTretyak AndreyTretyak Nov 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like GetRequiredSection doing the same thing.
And if we need for Get<T>() to throw we can probably set BinderOptions.ErrorOnUnknownConfiguration globally, that would provide a better exception with details about what properties missing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with nullables was not on GetSection (returns always not-null) but on Get<T>.
Would the BinderOptions.ErrorOnUnknownConfiguration get us rid of the nullable warnings? That's why this PR - to have the validation in one place and not repeat the code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if Get<T> provides any options of ensuring compiler that return type is not nullable. If you won't be able to find one and decide to go ahead with adding custom wrapper I would suggest following:

  • Use method name that aligns with other methods of the API, ex 'GetRequired`.
  • In the implementation unitize methods that I've mentioned above to get proper explanation if what failed to bind instead of generic exception we are creating now.
  • First of all create an API suggestion for ,NET explaining your use case and making sure that they don't have better suggestions with .NET 7 and possibly convincing them to add 'GetRequired` into next version.

public class ConfigurationValidationTests
{
[TestMethod]
public void GetNonNullableOrThrow_WhenValueNotNull()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In most cases our tests are named Method_Scenario_ExpectedResult.

Mock<IConfigurationSection> mockSectionString = new Mock<IConfigurationSection>();
Mock<IConfiguration> mockConfig = new Mock<IConfiguration>();

mockSectionInt.Setup(x => x.Value).Returns(configValueInt);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure your mocks behave the same way as the real configuration? It's better to just build a configuration object: https://stackoverflow.com/questions/64794219/how-to-mock-iconfiguration-getvalue.

public static T GetNonNullableOrThrow<T>(IConfiguration configuration, string sectionName)
{
return configuration.GetSection(sectionName).Get<T>()
?? throw new InvalidOperationException($"{nameof(T)} missing in the {sectionName} section of the configuration");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the result of nameof(T), does it help in troubleshooting?

string sectionKeyInt = "SectionKeyInt";
string sectionKeyString = "SectionKeyString";
string configValueInt = "5";
string configValueString = "asdf";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you accounting for a case where bound value is an object, not a primitive type?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants