locked
How to Verify Multiple File Uploads with Regular Expression Annotation? RRS feed

  • Question

  • User-939035612 posted

    I have an input used to upload multiple images that needs client side file extension validation. I have tried using the RegularExpression annotation with limited success. By that I mean that I have found one regular expression that only works on lower case extensions of a single file. If I try using it on an input with a multiple attribute it will validate all files if just one of them is valid. 

    [RegularExpression(@"([a-zA-Z0-9\s_\\.\-:])+(.png|.jpg|.gif|.jpeg|.bmp)$", ErrorMessage = "Invalid File Extension: Must Be .jpg, .jpeg, .png, .gif, or .bmp!")]

    It is being used on an IFormFile object, but whenever I tweak the expression at all it breaks. If I try enabling insensitive character mode by adding (?i) before the file extensions it causes every file I submit to be valid. If I try adding |.JPG|.GIF|.PNG|.BMP|.JPEG every file I submit is not valid. Does anyone know of a way to make this expression work with all cases and on a multiple input?

    Tuesday, November 3, 2020 9:44 AM

All replies

  • User475983607 posted

    You'll need custom validation.

    @{
        ViewData["Title"] = "Index";
    }
    
    <h1>Index</h1>
    
    <div>
        <form id="form" method="post">
           <div>
               <input type="file" name="files" id="files" multiple />
               <span id="errorMessage"></span>
           </div>
            <div>
                <input id="Submit1" type="submit" value="submit" />
            </div>
        </form>
    </div>
    
    @section scripts {
    <script>
    
        const form = document.getElementById('form');
        form.addEventListener('submit', validateFiles);
    
        function validateFiles(event) {
            //event.preventDefault();
            var fileElement = document.getElementById("files");
            var re = /^.*\.(png|jpg|gif|jpeg|bmp)$/;
    
            for (var i = 0; i < fileElement.files.length; i++) {
                if (!re.test(fileElement.files[i].name.toLowerCase())) {
                    document.getElementById("errorMessage").innerText = "Error! " + fileElement.files[i].name.toLowerCase() + " must be a png, jpg, gif, jpeg , or bmp.";
                    event.preventDefault();
                    return;
                }
            }       
            //document.getElementById("errorMessage").innerText = "Success";
        }
    </script>
    }

    Tuesday, November 3, 2020 12:45 PM
  • User-939035612 posted

    Thanks, that works well except for one thing. The error is not included with the validation summary for the form. As a result the user has to scroll down the form to see the error. It also allows the form to be submitted even though an invalid file exists.

    Wednesday, November 4, 2020 1:33 AM
  • User-939035612 posted

    Another problem with that code is if you change the file to proper permitted image the error message does not go away. Do you have an example that works with the other validation controls and is correctable?

    Wednesday, November 4, 2020 3:13 AM
  • User-939035612 posted

    I integrated your example the best I could with the image preview feature that I already have. This is the result:

    <div class="form-group">
                <div>
                    <div class="input-group mb-3 px-2 py-2 rounded-pill bg-white shadow-sm">
                        <input asp-for="FormFiles" id="upload2" type="file" onchange="readURL2(this);" multiple class="form-control border-0 upload">
                        <label asp-for="FormFiles" id="upload-label2" for="upload2" class="font-weight-light text-muted upload-label">Images (Optional)</label>
                        <div class="input-group-append">
                            <label for="upload2" class="btn btn-light m-0 rounded-pill px-4"> <i class="fa fa-upload mr-2 text-muted"></i><small class="text-uppercase font-weight-bold text-muted">Choose Images</small></label>
                        </div>
                        <span id="error-FormFiles" class="text-danger" asp-validation-for="FormFiles"></span>
                    </div>
                    <div class="bg-light">
                        <p class="font-italic text-black-50 text-center">The image uploaded will be rendered inside the box below.</p>
                        <div id="imageResults" class="image-area mt-4"></div>
                    </div>
                </div>
            </div>
    
    
    
    
    
    
    /* Multi Upload Validation */
            var input2 = document.getElementById('upload2');
            var infoArea2 = document.getElementById('upload-label2');
            function readURL2(input2) {
                if (input2.files && input2.files.length > 0) {
                    $('#imageResults').empty();
                    $('#error-FormFiles').empty();
                    var ic1 = 0;
                    var ic2 = 0;
                    var ic3 = 0;
                    var ic4 = 0;
                    var ic5 = 0;
                    var re = /^.*\.(png|jpg|gif|jpeg|bmp)$/;
                    for (var i = 0; i < input2.files.length; i++) {
                        ++ic1;
                        var reader2 = new FileReader();
                        reader2.onload = function (e) {
                            $('#imageResults').append('<img id="imageResult_' + (ic1) + '" src="' + e.target.result + '" alt="" class="img-fluid w-100 rounded shadow-sm mx-auto d-block"><div class="row mb-3 mt-3"><div class="col-sm-2"><label for="imageName_' + (++ic2) + '">Name Above Image:</label></div><div class="col-sm-7"><input id="imageName_' + (++ic3) + '" name="imageName-' + (++ic4) + '" onchange="reValidateForm();" class="form-control" type="text"  data-val="true" data-val-maxlength="Max 50 Chars." data-val-maxlength-max="50" data-val-regex="No HTML tags allowed!" data-val-regex-pattern="^(?!.*&lt;[^&gt;]&#x2B;&gt;).*" maxlength="50" value="" /></div><span class="text-danger col-sm-3 col-form-label" data-valmsg-for="imageName-' + (++ic5) + '" data-valmsg-replace="true"></span></div >');
                        };
                        infoArea2.textContent = 'Image Count: ' + input2.files.length;
                        reader2.readAsDataURL(input2.files[i]); 
                        if (!re.test(input2.files[i].name.toLowerCase())) {
                            document.getElementById("error-FormFiles").innerText = input2.files[i].name.toLowerCase() + " must be a png, jpg, gif, jpeg, or bmp!";
                            event.preventDefault();
                            return;
                        }
                    }
                }
                reValidateForm();
            }
            function reValidateForm() {
                $("#newpost").removeData("validator");
                $("#newpost").removeData("unobtrusiveValidator");
                $.validator.unobtrusive.parse("#newpost");
            }  

    I think the problem is that I need to add something to the validation for the model. I think the solution requires jQuery.validator.addMethod or jQuery.validator.unobtrusive.adapters.add neither or which I have worked with before. Do you have a simple example for integrating your code?

    Wednesday, November 4, 2020 3:24 AM
  • User711641945 posted

    Hi CopBlaster,

    I have tested  mgebhard's code,it would meet your requirement that it will not pass the files to the backend when the files fail to validate.

    CopBlaster

    The error is not included with the validation summary for the form.

    You could change your code like below:

    @model TestModel
    <div>
        <form asp-action="Upload" enctype="multipart/form-data" id="form">
            <div asp-validation-summary="All" class="text-danger"></div>
    
            <div class="form-group">
                <input asp-for="files" />
                <span id="errorMessage" class="text-danger"></span>
            </div>
    
            <div>
                <input id="Submit1" type="submit" value="submit" />
            </div>
        </form>
    </div>
    
    @section Scripts {
    <script>
    $("#files").change(function () {
            $(".validation-summary-errors").empty();
            $("#errorMessage").empty();
        }); const form = document.getElementById('form'); form.addEventListener('submit', validateFiles); function validateFiles(event) { //event.preventDefault(); var fileElement = document.getElementById("files"); var re = /^.*\.(png|jpg|gif|jpeg|bmp)$/; for (var i = 0; i < fileElement.files.length; i++) { if (!re.test(fileElement.files[i].name.toLowerCase())) { var errormessage = "Error! " + fileElement.files[i].name.toLowerCase() + " must be a png, jpg, gif, jpeg , or bmp."; document.getElementById("errorMessage").innerText = errormessage; var vs = $('[data-valmsg-summary]'); // get the div // update class name vs.addClass('validation-summary-errors').removeClass('validation-summary-valid'); // add error vs.children('ul').append($('<li></li>').text(errormessage)); event.preventDefault(); return; } } } </script> }

    Model:

    public class TestModel
    {
        public List<IFormFile> files { get; set; }
    }

    Controller:

    [HttpPost]
    public IActionResult Upload(TestModel model)
    {
        //do your stuff...
        return View();
    }
    Another problem with that code is if you change the file to proper permitted image the error message does not go away.

    Add the following code:

    $("#files").change(function () {
            $(".validation-summary-errors").empty();
            $("#errorMessage").empty();
        });

    BTW,you need to remeber to add enctype="multipart/form-data" to the form,otherwise,the backend would not receive the correct files:

    <form asp-action="Upload" enctype="multipart/form-data" id="form">
    

    Result:

    Best Regards,

    Rena

    Wednesday, November 4, 2020 8:31 AM
  • User-939035612 posted

    This does not seem to work in conjunction with validation for other inputs on the page. For instance, I have several bound inputs with Required annotations. If I click submit I see the errors for them in the summary, but not the custom error. Then if I change the image input the validation summary is changed to include only the custom error and not the others.

    This seems like it is on the right track, but it needs to just add the custom error to the summary and remove just that error from the summary when the file is changed.

    I tried adding a validation rule like this but it did not work:

    $('#form').rules("add",
                        {
                            pattern: /^.*\.(png|jpg|gif|jpeg|bmp)$/,
                            messages: {
                                pattern: "Invalid File Extention: must be a png, jpg, gif, jpeg, or bmp!",
                            }
                        });

    What would your code look like if that form also included something like this:

    <div class="form-group row">
                <label class="col-sm-2 col-form-label" asp-for="Posts.Title"></label>
                <div class="col-sm-7">
                    <input class="form-control" asp-for="Posts.Title" />
                </div>
                <span class="text-danger col-sm-3 col-form-label" asp-validation-for="Posts.Title"></span>
            </div>

    Thursday, November 5, 2020 12:34 AM
  • User-939035612 posted

    Is there a way to set the valid = false in the aforementioned code? In my old site I would just set args.isvalid = false but I am not sure what the jQuery validation equivalent of that is.

    Thursday, November 5, 2020 5:43 AM
  • User-939035612 posted

    Upon further review the form still posts with the errors. It gives the client warnings, but as I mentioned earlier the other controls validate but the custom one does not. Hopefully the client will see the error and fix the problem, but due to data being lost on postback I would rather they not be allowed to post.

    I figure the problem is that your code does not work if there are other bound inputs on the form .

    Thursday, November 5, 2020 7:42 AM
  • User711641945 posted

    Hi CopBlaster,

    Could you share your requirement now clearly?A lot of posts make me confused.

    What would your code look like if that form also included something like this:

    If i add other properties and add required attribute on the property,It would work well.

    When I select wrong file and filled with other inputs,it would display error file format does not correct.When I select wrong file and do not fill the required inputs,it would display several errors.When I select correct file and do not fill the required inputs,it would display the error the field is required.When I select right file and filled the inputs,it would pass to the backend successfully.

    Best Regards,

    Rena

    Thursday, November 5, 2020 9:42 AM
  • User-939035612 posted

    Rena,

    The requirements are that I have a form on a Razor Page that uses annotations for validation on everything except for one input element. That input element has a multiple attribute. That input is used to upload multiple images, so I need a client side validation solution that throws an error if any file being uploaded does not have a permitted image file extension (.jpg, .jpeg, .png, .gif, .bmp). 

    There is also an image input for uploading a single image. That image is used as a page header for the post being created. I am able to validate file extensions for that one using a Regular Expression annotation with the regex @"^.*\.(png|jpg|gif|jpeg|bmp|JPG|PNG|GIF|JPEG|BMP)$" but it does not work on multiple files. If I use that for multiple files the input is validated if any one of the files matches the regex. When I posted this I was hoping I might find a regex that works with multiple files. A regex that matches multiple files only if all of them have a valid file extension.

    On the server side I verify the file type, file size, and file count using an async task when the form posts like this:

    if (FormFiles != null)
    			{
    				if(FormFiles.Count > _fileCountLimit)
                    {
    					ModelState.AddModelError("FormFiles", _fileCountLimitMessage);
                    }
    				foreach (var unvalidatedfile in FormFiles)
                    {
    					var ext = Path.GetExtension(unvalidatedfile.FileName.ToString().ToLower());
    					if (!_permittedExtensions.Contains(ext))
                        {
    						ModelState.AddModelError("FormFiles", _invalidExtentionErrorMessage);
                        }
    					if(unvalidatedfile.Length > _fileSizeLimit)
                        {
    						ModelState.AddModelError("FormFiles", _fileSizeErrorMessage);
                        }
                    }
    			}
    			if(HeaderImage != null)
                {
    				if(HeaderImage.Length > _fileSizeLimit)
                    {
    					ModelState.AddModelError("HeaderImage", _fileSizeErrorMessage);
                    }
    				var ext2 = Path.GetExtension(HeaderImage.FileName.ToString().ToLower());
    				if (!_permittedExtensions.Contains(ext2))
    				{
    					ModelState.AddModelError("HeaderImage", _invalidExtentionErrorMessage);
    				}
    			}

    Unfortunately, that code adds nothing on the client side, so I need a client side solution similar to ModelState.AddModelError but the code I've found so far only gives the client error messages if their file type is invalid and those message do not stop the form from being submitted. It appears to be firing separately from the rest of the validation on the form.

    My last question was asking how you would write the code you previously wrote if you needed the validation to work as part of the entire form, not just one control. When I click submit I need that input validated along with the others and any error included in that error summary. I'll need to do the same thing for file size and file count constraints next.

    Friday, November 6, 2020 2:30 AM
  • User711641945 posted

    Hi CopBlaster,

    My last question was asking how you would write the code you previously wrote if you needed the validation to work as part of the entire form, not just one control. When I click submit I need that input validated along with the others and any error included in that error summary. I'll need to do the same thing for file size and file count constraints next.

    As my previous post said,I could validate the whole form with multiple files' validation and default validation.I think mybe your project contains other issue we do not figure out.If possible,you could create a new project and using my whole code.

    Best Regards,

    Rena

    Friday, November 27, 2020 8:32 AM