0

When I run the page off my computer, or users inside the office try to submit a package, attachments go through just fine. From outside the office, and on tablets, there's issues where all of the data makes it in just fine, but the attachments (sent as a byte array to a varbinary(max) column) don't always make it through Sometimes none, sometimes 2 out of 5, sometimes all. Is it timing out before all of the data is received? Or is there something else?

C# code (Blazor Server):

<Microsoft.AspNetCore.Components.Forms.InputFile multiple OnChange="OnChange" />

List<PkgAttachments> SelectedFiles = new List<PkgAttachments>();        
async Task OnChange(InputFileChangeEventArgs e)
{
    var files = e.GetMultipleFiles();
    foreach(var file in files)
    {
        string fileExt = file.Name.Substring(file.Name.LastIndexOf('.') + 1).ToUpper();
        if (fileExt.ToUpper() == "JPG" || fileExt.ToUpper() == "JPEG" || fileExt.ToUpper() == "BMP" || fileExt.ToUpper() == "PNG")
        {
            var resizedFile = await file.RequestImageFileAsync(file.ContentType, 830, 715);

            var buf = new byte[resizedFile.Size];
            using (var stream = resizedFile.OpenReadStream(maxAllowedSize:10000000))
            {
                await stream.ReadAsync(buf);
            }
            SelectedFiles.Add(new PkgAttachments { Data = buf, ContentType = file.ContentType, Name = file.Name });
        }
        else if (fileExt.ToUpper() == "PDF")
        {
            var buf = new byte[file.Size];
            using (var stream = file.OpenReadStream(maxAllowedSize:10000000))
            {
                await stream.ReadAsync(buf);
            }
            SelectedFiles.Add(new PkgAttachments { Data = buf, ContentType = file.ContentType, Name = file.Name });
        }
    }
}

When they click the submit button....

if (SelectedFiles != null)
        {
            if (SelectedFiles.Count() > 0)
            {
                using (DataTable dt = new DataTable())
                {
                    dt.Columns.Add("FileName", typeof(string));
                    dt.Columns.Add("ImageData", typeof(byte[]));
                    foreach (var file in SelectedFiles)
                    {
                        string fileExt = file.Name.Substring(file.Name.LastIndexOf('.') + 1).ToUpper();
                        if (fileExt.ToUpper() == "JPG" || fileExt.ToUpper() == "JPEG" || fileExt.ToUpper() == "BMP" || fileExt.ToUpper() == "PNG" || fileExt.ToUpper() == "PDF")
                        {
                            dt.Rows.Add(file.Name, file.Data);
                        }
                    }
                    int test = dt.Rows.Count;
                    packageSubmission.attachments = dt;  //This is the datatable that matches the user defined type in the DB
                }
            }
        }
        string User = await oLocalStore.GetItemAsync<string>("User");            
        _claims.SubmitPackage(packageSubmission, User);

In the stored procedure, it has a field that counts the attachments that are sent through (to track in case someone submits a package without any attachments).. and also visible when we go to look at the attachments, and there's nothing there.

Ann L.
  • 13,760
  • 5
  • 35
  • 66
jnelson
  • 41
  • 1
  • 10
  • I assume that you've made sure that none of the missing files have non-standard file extensions? Because it looks like you discard such files without taking any action on them. – Ann L. Jul 19 '22 at 20:58
  • BTW, if it's available in your environment, you might prefer using `System.IO.Path.GetExtension()` to getting file extensions through substringing. Just one less step you have to go through. – Ann L. Jul 19 '22 at 21:00
  • I'm wondering if you could have a race condition going on. Is it possible that your users could be hitting `Submit` before the `OnChange` file-processing `Task` is completely done? Esp given that it's happening only when they're remotely connected (and potentially submitting some big images or PDFs)? – Ann L. Jul 19 '22 at 21:04
  • I was able to verify that they were trying to submit JPG files. And, I'm trying to test your 3rd comment about the "OnChange" not processing right now. I was able to submit a 4.5MB PDF and store it successfully from my PC... so my initial thoughts are that this one may be accurate... – jnelson Jul 19 '22 at 21:17
  • 1
    The datatable does not need a `using`, that may be affecting things as you are passing it outside of the `using`. Even though it's `IDisposable`, it's a no-op, this is documented on [so] – Charlieface Jul 19 '22 at 23:31
  • @jnelson My thought is that, since it's a `Task`, it's going to finish on its own, and (from the look of things) you aren't waiting for it to complete before allowing people to submit. So you can't be sure -- can't guarantee -- it isn't still going, esp if multiple people are submitting things at the same time (which would slow things down, at least a little!) – Ann L. Jul 20 '22 at 11:51
  • Do you have a log? You could probably determine whether this is happening "in the wild" by having the `OnChange` and the `Submit` each send a message to the log when they start and finish, with enough ID info that you can tie the two events to each other. Just see which finishes first. – Ann L. Jul 20 '22 at 11:52
  • @Charlieface If it's a no-op, wouldn't the `DataTable` being in a `using` have no effect on it? – Ann L. Jul 20 '22 at 11:55
  • @jnelson Another possibility: although I didn't see anywhere obvious where exceptions would _definitely_ happen, perhaps the `OnChange` `task` is throwing an occasional exception, and it's just quietly failing, because nothing's monitoring that `task` or checking its completion status? – Ann L. Jul 20 '22 at 11:57
  • @AnnL. It's possible that for Blazor it unnecessarily clears the rows. (Unnecessary because the whole thing is managed memory so not necessary to delete). – Charlieface Jul 20 '22 at 12:00
  • Also if `_claims.SubmitPackage(packageSubmission, User);` is actually a `Task` then you need to await it `await _claims.SubmitPackage(packageSubmission, User);` – Charlieface Jul 20 '22 at 12:00
  • @AnnL. Your suggestion was accurate. After disabling the submit button until the attachments finished processing, they all submit now. On one tablet, it took 30 seconds for 7 images to resize. I'd love to mark your answer as accurate, but it looks like I don't have the reputation for it just yet. For everyone else, thank you for your feedback. – jnelson Jul 20 '22 at 16:41
  • @Charlieface, that's good to know about the datatables in blazor.. I was not aware of that. – jnelson Jul 20 '22 at 16:41
  • @jnelson Yeah, you can't mark a comment as a correct answer! It has to be turned into an official Answer. I'm going to create an answer from my comment (just the idea, not the code), which will allow you to mark it as correct. – Ann L. Jul 21 '22 at 13:13
  • I'm also going to edit your title and the tags, because what you described actually doesn't relate to SQL or custom types: it was all going on before the database got involved. So the title is a bit misleading about what the observed problem was. – Ann L. Jul 21 '22 at 13:23

2 Answers2

1

Thank you to @AnnL for the suggestion about the files not processing fast enough. The working code was adjusted for to this:

On the UI:

<div>
    <h3>@Message</h3><br/>
    <Microsoft.AspNetCore.Components.Forms.InputFile multiple OnChange="OnChange" />
</div>
<div class="@HoldUp">
    <div class="col-3 offset-6">
        <input type="submit" value="Submit" @onclick="SubmitPkg" class="btn btn-primary" >
    </div>
</div>

And:

<style>
.AttNotProcessed {
    display: none;
}
</style>

And, in the code:

private string HoldUp = "AttNotProcessed";
List<PkgAttachments> SelectedFiles = new List<PkgAttachments>();        
async Task OnChange(InputFileChangeEventArgs e)
{
    HoldUp = "AttNotProcessed";
    var files = e.GetMultipleFiles();
    foreach(var file in files)
    {
        string fileExt = file.Name.Substring(file.Name.LastIndexOf('.') + 1).ToUpper();
        if (fileExt.ToUpper() == "JPG" || fileExt.ToUpper() == "JPEG" || fileExt.ToUpper() == "BMP" || fileExt.ToUpper() == "PNG")
        {
            var resizedFile = await file.RequestImageFileAsync(file.ContentType, 830, 715);

            var buf = new byte[resizedFile.Size];
            using (var stream = resizedFile.OpenReadStream(maxAllowedSize:10000000))
            {
                await stream.ReadAsync(buf);
            }
            SelectedFiles.Add(new PkgAttachments { Data = buf, ContentType = file.ContentType, Name = file.Name });
        }
        else if (fileExt.ToUpper() == "PDF")
        {
            var buf = new byte[file.Size];
            using (var stream = file.OpenReadStream(maxAllowedSize:10000000))
            {
                await stream.ReadAsync(buf);
            }
            SelectedFiles.Add(new PkgAttachments { Data = buf, ContentType = file.ContentType, Name = file.Name });
        }
    }
    Message = "Files done processing";
    HoldUp = "form-group row";
}
jnelson
  • 41
  • 1
  • 10
0

Given that you have an async OnChange method, which returns a Task (which completes in its own time) ... I'm wondering if you have a race condition going on. It would explain what you're seeing, and why it doesn't happen all the time, or for everybody.

It doesn't look like you're making submitting the files contingent on the OnChange Task already having completed, so (if, say, transmission speed of the files were slow, or your system were heavily loaded) it would be very possible for a user to submit the files before the OnChange method has completed processing.

One way to find out whether this were happening would be to write to a log when the Submit happens, and when the OnChange finishes. Then you could see whether the Submit is sometimes happening before the OnChange finishes.

There are multiple ways to address this (awaiting the Task, having the Task enable the Submit button as its last action, etc.).

Ann L.
  • 13,760
  • 5
  • 35
  • 66