2

I am trying to build an image uploader in a .NET 6 Minimal API. I believe that i have all the proper components but I am utterly confused on the problem that I am having. One specific line in the code has a CS8602 error stopping me from running the code.

app.MapPost("/submit-form", async (HttpContext context) =>
{
    var form = await context.Request.ReadFormAsync();
    var title = form["imageTitle"];
    var file = form.Files["imageFile"];

    // Perform validation checks
    if (string.IsNullOrEmpty(title))
    {
        context.Response.StatusCode = 400; // Bad Request
        await context.Response.WriteAsync("Image title is required.");
        return;
    }

    if (file == null || file.Length == 0)
    {
        context.Response.StatusCode = 400; // Bad Request
        await context.Response.WriteAsync("No image file uploaded.");
        return;
    }

    var allowedExtensions = new[] { ".jpeg", ".jpg", ".png", ".gif" };
    var fileExtension = Path.GetExtension(file.FileName);

    if (!allowedExtensions.Contains(fileExtension.ToLower()))
    {
        context.Response.StatusCode = 400; // Bad Request
        await context.Response.WriteAsync("Invalid file format. Accepted formats: JPEG, PNG, GIF.");
        return;
    }

    // If validation passes, continue with further actions
    // Generate unique ID for the image
    var imageId = Guid.NewGuid().ToString();

    // Store image information
    var imageInfo = new
    {
        ImageId = imageId,
        Title = title,
        file.FileName
    };

    // Serialize image information to JSON
    var json = JsonSerializer.Serialize(imageInfo);

    // Store JSON data in a file on disk
    var filePath = Path.Combine(Directory.GetCurrentDirectory(), "images", $"{imageId}.json");
    await File.WriteAllTextAsync(filePath, json);

    // Redirect to the page with the unique ID
    var redirectUrl = $"/picture/{imageId}";
    context.Response.Redirect(redirectUrl);
});

That is the first MapPost code block.

app.MapGet("/picture/{id}", async (HttpContext context) =>
{
    var imageId = context.Request.RouteValues["id"] as string;
    var filePath = Path.Combine(Directory.GetCurrentDirectory(), "images", $"{imageId}.json");

    if (!File.Exists(filePath))
    {
        context.Response.StatusCode = 404; // Not Found
        await context.Response.WriteAsync("Image not found.");
        return;
    }

    // Read the JSON data from the file
    var json = await File.ReadAllTextAsync(filePath);

    // Deserialize the JSON data to retrieve the image information
    var imageInfo = JsonSerializer.Deserialize<Dictionary<string, object>>(json);

    // Get the title from imageInfo dictionary
    string title;
    if (imageInfo.TryGetValue("Title", out var titleObj) && titleObj is string)
    {
        title = (string)titleObj;
    }
    else
    {
        title = string.Empty;
    }

    // Get the file name from imageInfo dictionary
    string fileName;
    if (imageInfo.TryGetValue("FileName", out var fileNameObj) && fileNameObj is string)
    {
        fileName = (string)fileNameObj;
    }
    else
    {
        fileName = string.Empty;
    }

    // Render a page that displays the image title and the image itself
    await context.Response.WriteAsync($"<h1>{title}</h1>");
    await context.Response.WriteAsync($"<img src=\"/images/{imageInfo["FileName"]}\" alt=\"{title}\">");
});

app.Run();

The error is happening just above the bottom of the code. in the second if statement,

if (imageInfo.TryGetValue("Title", out var titleObj) && titleObj is string)
    {
        title = (string)titleObj;
    }
    else
    {
        title = string.Empty;
    }

imageInfo is creating the error is dereference of a possibly null reference

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • 1
    we are not a compiler so we don't know all the errors and warning a compiler or whatever tool may throw. Can you please provide the exact message also? – MakePeaceGreatAgain Jun 29 '23 at 17:50
  • oh youre right i am sorry. dereference of a possibly null reference – Curtis Antisdel Jun 29 '23 at 17:52
  • Well, apparently `JsonSerializer.DeSerialize` returns `object?`, so chances are you get a `null`. The warning just informs you that you should check for that before using the variable. If you know for sure that `Deserialize` allways returns an object, than you may ignore about that warning and use `imageInfo!.TryGetValue`, which is the null-forgiving operator – MakePeaceGreatAgain Jun 29 '23 at 17:53
  • Yeah, I simply dont know how to fix it and its giving me a compile time error and not running the build – Curtis Antisdel Jun 29 '23 at 17:55
  • Something like `if(imageInfo != null && )`? – MakePeaceGreatAgain Jun 29 '23 at 17:56
  • That worked for me on those parts. Its producing the same thing on this line now. await context.Response.WriteAsync($"\"{title}\""); on image info. If i was to place that same Strech, where would it go? – Curtis Antisdel Jun 29 '23 at 18:03
  • Well, if `null` is a valid scenario, you need to handle it all the way down. We cannot know if that is the case for you. If you know it's never `null`, use null-forgiving as already mentioned: `var o = JsonSerializer.Deserialize(...)!` (the exclamantionmarc is the operator, by the way). – MakePeaceGreatAgain Jun 29 '23 at 18:08
  • it is. I ran it all the way down until the point of the final line of code. I am not sure where to add it within that line of code to handle it. – Curtis Antisdel Jun 29 '23 at 18:11

1 Answers1

0

There is a feature called Nullable reference types which is enabled by default in the "new" application templates. It enables ability for compiler to perform nullable state static flow analysis (during compilation) which should reduce a possibility of "The Billion Dollar Mistake". Also it seems that your application has either TreatWarningsAsErrors or (more likely) corresponding warnings specifically because by default this analysis produces warnings, not errors. You need to perform null checks in the ifs:

string title;
if (imageInfo != null && imageInfo.TryGetValue("Title", out var titleObj) && titleObj is string)
{
    title = (string)titleObj;
}
else
{
    title = string.Empty;
}

string fileName;
if (imageInfo != null && imageInfo.TryGetValue("FileName", out var fileNameObj) && fileNameObj is string)
{
    fileName = (string)fileNameObj;
}
else
{
    fileName = string.Empty;
}

And then fix the write statement to actually use fileName (I suppose that was the original intent here):

await context.Response.WriteAsync($"<img src=\"/images/{fileName}\" alt=\"{title}\">");

Personally I would assign the empty value from the start and then used pattern matching to simplify the code:

var title = string.Empty;
if (imageInfo != null && imageInfo.TryGetValue("Title", out var titleObj) && titleObj is string value)
{
    title = value;
}

var fileName = string.Empty;
if (imageInfo != null && imageInfo.TryGetValue("FileName", out var fileNameObj) && fileNameObj is string obj)
{
    fileName = obj;
}
Guru Stron
  • 102,774
  • 10
  • 95
  • 132