htmlasp.netasp.net-core-mvcrazor-pagesdropzone.js

Dropzone wtih Razor Pages not submitting files to HttpContext.Request.Form.Files


I have been struggling to implement dropzone.js on my razor page. I'm currently trying to create a help desk where dropzone allows the user to drag a file to upload along with their ticket. I haven't had much luck in getting the file upload to the backend of the razor page. I can get it to work with a regular <input> tag but not dropzone. The HttpContext.Request.Form.Files attribute is blank, and if I bind an IformFile list, or at least attempt to, I don't get the file either.

I have had some luck with getting the file to appear in Form.Files in a separate controller, but I am not sure how I would be able to pass the file location from the controller to the help ticket that is processed on the backend. I am aware I could handle the entire form upload on the backend, but I am going to be implementing a change to file uploads for all our forms, and if I must have a controller it should be able to handle just the file upload section and then return to the razor backend to finish the rest of the form submission The file uploads are held in a folder in my wwwroot folder called attachments, and then I have a model for the ticket uploads that stores their location and the ticket they are associated with (and there can be multiple uploads per ticket) So I must have some way to handle creating the help ticket in it's model, and then create the file upload and it's location afterwards.

Here are the relevant parts of my code:

Firstly my dropzone configuration:

$(document).ready(function () { 
    Dropzone.autoDiscover = false;
    console.log(window.location.pathname); // path returned here is what is put in url. Will use for other razor pages implementing this in the future?
    $("#fileUploadArea").dropzone({
        url: "/api/fileupload", // or to razor backend: /IT/Help (matches location.pathname)
        autoProcessQueue: false,
        addRemoveLinks: true,
        acceptedFiles: ".png,.jpg,.gif,.jpeg,.bmp",
        uploadMultiple: true,
        maxFiles: 10,
        headers: { "RequestVerificationToken": $('input[name="__RequestVerificationToken"]').val() },
        paramName: "files",
        init: function () {
            var myDropzone = this;
            myDropzone.options.addRemoveLinks = true;
            myDropzone.options.dictRemoveFile = "Remove";
            //myDropzone.paramName = () => "files";
            this.on("success", function (file, response) {
                alert("success");                
            });
            this.on("error", function (file, errorMsg) {
                alert("error");
                console.error(errorMsg);
            });
            //Manually submit form with files?
            $('#wmf-help-submit-button').on('click', function (e) {
               myDropzone.processQueue();
            });
        }
    });
    
});

Form:

<form method="POST" enctype="multipart/form-data" class="" id="ticketForm" asp-action="/Index">
    @* @Html.AntiForgeryToken(); *@
    <head>
        <title></title>
    </head>
    <body>
        <!-- Two inputs, one for a title and another for a body/description omitted -->

                <!-- Image Uploads -->

                <div class="col-12 text-dark">
                    <p>Please upload any screenshots below:</p>
                    <div id="fileUploadArea" class="dropzone text-center">
                       <!-- <input type="file" name="Uploads" accept=".jpg, .jpeg, .png" multiple hidden> -->
                        <span class="dz-message">Drop files here or <span class="text-decoration-underline">Click to browse</span></span>
                    </div>
                    <!-- TODO: implement fallback for no js? Non-DZ way that works. -->
                    <!-- <label for="image_uploads">Choose images to upload (PNG, JPG)</label>
                    <div id="wmf-files-to-upload">
                    <input type="file" name="Uploads" accept=".jpg, .jpeg, .png" multiple>
                    </div> -->

                </div>
                <div class="col-12 text-center mt-4 mb-2">
                    <input id="wmf-help-submit-button" class="wmf-button-main btn form-control text-center" type="submit" />
                </div>
                
</form>

I have a razor backend that looks like this:

namespace mynamespacehere
{
    [Authorize]
    public class IndexModel : PageModel
    {
        private readonly IUnitofWork _unitofWork;
        private readonly IWebHostEnvironment _webHostEnvironment;
        
        /* Irrelevant fields removed */

        public HelpDeskTicket HelpTicket { get; set; }
        [BindProperty]
        public List<IFormFile> Uploads {  get; set; } // DZ uploads don't show here either.

/* ... Get method omitted */

        public async Task<IActionResult> OnPost()
        {
/* ... handling other form fields validation */

/* ... Created help ticket in DB (Help ticket does not have upload as part of it's model, upload references ticket instead) */

/* file upload area. files put in via dropzone are not showing here. */

            // Upload Files if there are any. Could be in a controller instead?
            // YOU MUST ALWAYS UPLOAD FILES AFTER COMMITTING INITIAL TICKET TO DB OTHERWISE THERE WILL NOT BE A
            // TICKETID TO ASSOCIATE WITH THE UPLOADS!
            #region File Upload
            var files = HttpContext.Request.Form.Files;
            if (files.Count > 0)
            {
                string webRootPath = _webHostEnvironment.WebRootPath;


                foreach (var file in files)
                {
                    HelpDeskTicketUpload ticketUpload = new HelpDeskTicketUpload();

                    string fileName = Guid.NewGuid().ToString();
                    var uploadpath = Path.Combine(webRootPath, @"attachments\helpdesk\");
                    var extension = Path.GetExtension(file.FileName);
                    var filePath = uploadpath + fileName + extension;
                    using var fileStream = System.IO.File.Create(filePath);
                    file.CopyTo(fileStream);

                    ticketUpload.HelpDeskTicketId = HelpTicket.Id;
                    ticketUpload.Filetype = extension;
                    ticketUpload.Location = @"\attachments\helpdesk\" + fileName + extension;
                    _unitofWork.HelpDeskTicketUpload.Add(ticketUpload);
                }
            }

            #endregion


            TempData["success"] = "Your help desk message was sent successfully!";
            return RedirectToPage("./Index");
        }

    }
}

Controller that can get the files but I don't know how to pass to razor for form processing:

namespace mynamespacehere
{
    [Route("api/[controller]")]
    [ApiController]
    public class FileuploadController : Controller
    {
        private readonly UnitofWork _unitOfWork;
        private readonly IWebHostEnvironment _webHostEnvironment;
        public FileuploadController(UnitofWork unitofWork, IWebHostEnvironment webHostEnvironment)
        {
            _unitOfWork = unitofWork;
            _webHostEnvironment = webHostEnvironment;
        }

        [HttpPost]
        public async Task<IActionResult> SaveFile()
        {
            var files = HttpContext.Request.Form.Files;
            //var filesReq = HttpContext.Request.Form.Files;
            if (files.Count > 0)
            {
                string webRootPath = _webHostEnvironment.WebRootPath;


                foreach (var file in files)
                {
                    HelpDeskTicketUpload ticketUpload = new HelpDeskTicketUpload();

                    string fileName = Guid.NewGuid().ToString();
                    var uploadpath = Path.Combine(webRootPath, @"attachments\helpdesk\");
                    var extension = Path.GetExtension(file.FileName);
                    var filePath = uploadpath + fileName + extension;
                    using var fileStream = System.IO.File.Create(filePath);
                    file.CopyTo(fileStream); //Not showing up in filepath need to debug this but can be considered a separate issue.
                }
            }

            return Ok("upload successful"); //Don't know how to return response/file locations to post backend.
        }
    }
}

So just to recap, I have two approaches I'm really asking about. First is having the files from Dropzone upload in the razor backend, the second is, if I must use a controller, how I would pass the file locations (There may be multiple files uploaded) back to the razor to then index to the db.

Following the answer below - I end up with the page getting stuck as far as I can see. In my network requests I see something related to the form (the upload only?) and I get my success message alert to pop up. printing the response in the console it looks like the response is the entirety of the Help page's HTML.

Update 2 - Based on my attempts to use a controller and pass the list of file locations using TempData I can conclude that whatever Dropzone is doing and whatever my form is doing on submit is considered two separate requests. One request is dropzone doing something with the files, the other is .net processing the rest of the form. I am able to upload the file to the folder, and I'm able to submit my form and put it in the database. But there is no way to pass the location of the file back to the form processing to register the ticket's location the database.


Solution

  • Ultimately I found Kartik/Krajee Bootstrap file uploader was what I needed for my needs. https://demos.krajee.com/widget-details/fileinput

    This plugin allows async and sync file uploads and was able to put them into a list.