Qiuzman
Qiuzman

Reputation: 1739

How to avoid the nullable reference types in ASP.NET Core 7

I have been working on a project at work where I do not run into many null warnings and it is ASP.NET Core 5 I believe however I started a new project recently and decided to go with ASP.NET Core 7 and I am not sure why but I keep running into warnings related to nullable (CS8602 typically). I now find myself having to add if then statements to check if the variable is null or not. What am I doing wrong in this new version of .net? Its increasing my code side susbtantially I feel like. I have included a sample excerpt of my code below and as you will see if I removed a if(blah == null) then usage of that object would then become a warning for possible null. This snippet of code is for a contact us form and to implement a recaptcha on the front end (aka the token).

namespace Chesapeake_Tees_API.Controllers;

[ApiController]
[Route("api/[controller]")]
public class ContactUsController : ControllerBase
{

private readonly ILogger<ContactUsController> _logger;
private readonly IEmailService _emailService;
private readonly string? reCaptchaKey = "";

public ContactUsController(IConfiguration configuration, ILogger<ContactUsController> logger, IEmailService emailService)
{
    _logger = logger;
    _emailService = emailService;
    reCaptchaKey = configuration["CFTunrstileSecretKey"];
}

[HttpPost(Name = "PostContactUs")]
[EnableRateLimiting("api")]
public async Task<IActionResult> Post([FromBody] CustomerInquiry customerInquiry)
{
    try
    {
        if (reCaptchaKey != null && customerInquiry.Token != null)
        {
            var dictionary = new Dictionary<string, string>
                {
                    { "secret", reCaptchaKey },
                    { "response", customerInquiry.Token }
                };

            var postContent = new FormUrlEncodedContent(dictionary);

            HttpResponseMessage? recaptchaResponseHTTP = null;
            string? reCaptchaResponseJSON;

            // Call recaptcha api and validate the token
            using (var http = new HttpClient())
            {
                recaptchaResponseHTTP = await http.PostAsync("https://challenges.cloudflare.com/turnstile/v0/siteverify", postContent);
                reCaptchaResponseJSON = await recaptchaResponseHTTP.Content.ReadAsStringAsync();
            }

            var reCaptchaResponse = JsonSerializer.Deserialize<ReCaptchaResponse>(reCaptchaResponseJSON);

            if (recaptchaResponseHTTP.IsSuccessStatusCode)
            {
                if (reCaptchaResponse == null)
                {
                    return Ok($"Message: Error code 503, unable to verify reCaptcha token");
                }
                else
                {
                    if (reCaptchaResponse.Success)
                    {
                        EmailMessage emailMessage = new EmailMessage();

                        emailMessage.From = new Contact { DisplayName = "Chesapeake Tees", EmailAddress = "[email protected]" };
                        emailMessage.To.Add(new Contact { DisplayName = "Chesapeake Tees", EmailAddress = "[email protected]" });
                        emailMessage.ReplyTo = new Contact { DisplayName = customerInquiry.FirstName + " " + customerInquiry.LastName, EmailAddress = customerInquiry.EmailAddress };
                        emailMessage.Subject = "Custome Inquiry";
                        emailMessage.Body = customerInquiry.Message;
                        await _emailService.SendEmailAsync(emailMessage);
                        return Ok("Message: Success - email sent");
                    }
                    else
                    {
                        string? errors = null;

                        if (reCaptchaResponse.ErrorCodes != null)
                        {
                            errors = string.Join(",", reCaptchaResponse.ErrorCodes);
                        }
                        return Ok($"Message: Error code 500, {errors}");

                    }
                }
            }
            else
            {
                return Ok($"Message: Error code 503, unable to verify reCaptcha token");
            }
        }
        else
        {
            return Ok($"Message: Error code 503, unable to verify reCaptcha token");
        }
    }
    catch (Exception ex)
    {
        _logger.LogInformation("Message: Error: " + ex.ToString(), DateTime.UtcNow.ToLongTimeString());
        throw;
    }
}
}

The model I am using which I have included ? to make them nullable (setting a default value to something doesnt seem to solve my warnings for the parent class itself but maybe the one property) includes the following:

    public class ReCaptchaResponse
{
    [JsonPropertyName("success")]
    public bool Success { get; set; }

    [JsonPropertyName("cdata")]
    public string? CData { get; set; }

    [JsonPropertyName("challenge_ts")]
    public DateTime ChallengeTs { get; set; } // timestamp of the challenge load (ISO format yyyy-MM-dd'T'HH:mm:ssZZ)

    [JsonPropertyName("hostname")]
    public string? HostName { get; set; }    // the hostname of the site where the reCAPTCHA was solved

    [JsonPropertyName("error-codes")]
    public string[]? ErrorCodes { get; set; }
}

public class CustomerInquiry
{
    [Required]
    public string FirstName { get; set; } = "";

    [Required]
    public string LastName { get; set; } = "";

    [Required]
    [Phone]
    [RegularExpression(@"^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$")]
    public string PhoneNumber { get; set; } = "";

    [Required]
    [EmailAddress]
    public string EmailAddress { get; set; } = "";

    [Required]
    public string Message { get; set; } = "";

    [Required]
    public string Token { get; set; } = "";
}

Upvotes: 1

Views: 2438

Answers (1)

Guru Stron
Guru Stron

Reputation: 142273

  1. You can disable NRTs (see the docs) either completely (<Nullable>disable</Nullable> in csproj) or partially (#nullable disable)

  2. Do not specify the empty strings for properties, you can use the new required keyword which I would argue is a nicer alternative:

public class CustomerInquiry
{
    [Required]
    public required string FirstName { get; set; } 
    // ... 
}
  1. customerInquiry.Token != null is not needed from nullable analysis standpoint since Token is declared as non-nullable.

  2. Do not declare variables as nullable when not needed:

HttpResponseMessage recaptchaResponseHTTP;
string reCaptchaResponseJSON;

// Call recaptcha api and validate the token
using (var http = new HttpClient())
{
    // PostAsync returns non-nullable HttpResponseMessage wrapped in task
    recaptchaResponseHTTP = await http.PostAsync("https://challenges.cloudflare.com/turnstile/v0/siteverify", postContent);
    // ReadAsStringAsync returns non-nullable string wrapped in task
    reCaptchaResponseJSON = await recaptchaResponseHTTP.Content.ReadAsStringAsync();
}

recaptchaResponseHTTP.ToString(); // no warning

Also as I wrote in the comment - do not instantiate http clients manually, use DI with IHttpClientFactory which will make the using obsolete and you can use inline assignment.

  1. Use operators helping with nullability for example the null-coalescing operators:
if (reCaptchaResponse.Success)
{
   // ...
}
else
{  
    string errors =  string.Join(",", reCaptchaResponse.ErrorCodes ?? new List<...>());
    return Ok($"Message: Error code 500, {errors}");
}
  1. This depends on the app logic but I would argue that missing recaptcha key from the config is an error and you should fail fast in ctor:
private readonly string reCaptchaKey = "";

public ContactUsController(IConfiguration configuration, ILogger<ContactUsController> logger, IEmailService emailService)
{
    var captchaKey = configuration["CFTunrstileSecretKey"];
    ArgumentException.ThrowIfNullOrEmpty(captchaKey);
    reCaptchaKey = captchaKey;
}

Upvotes: 2

Related Questions