Upload CSV file Issue

Hi @darrenhunterEmerald ,

I created a quick setup with your code (Fix added) and tested on Umbraco 17. It’s working as expected. Please check the below setup:

File: App_Plugins/OLS/umbraco-package.json

{
  "name": "OLS.Import.Package",
  "version": "1.0.0",
  "extensions": [
    {
      "type": "dashboard",
      "alias": "ols.dashboard.import",
      "name": "OLS File Import",
      "element": "/App_Plugins/OLS/ols-extension.js",
      "weight": 100,
      "meta": {
        "label": "OLS Import",
        "pathname": "ols-import"
      },
      "conditions": [
        {
          "alias": "Umb.Condition.SectionAlias",
          "match": "Umb.Section.Content"
        }
      ]
    }
  ]
}

File: App_Plugins/OLS/ols-extension.js

import { UmbElementMixin } from "@umbraco-cms/backoffice/element-api";
import { UMB_AUTH_CONTEXT } from '@umbraco-cms/backoffice/auth';

var _authentication;

const template = document.createElement("template");
template.innerHTML = `
  <style>
    :host {
      padding: 20px;
      display: block;
      box-sizing: border-box;
    }
  </style>

  <uui-box style="height:400px;Width:auto;">
    <h1>OLS File Import</h1>
    <form id="ols-form">
      <uui-input-file id="upload" name="input-file" multiple></uui-input-file>
      <uui-button style="margin-top: 16px" type="submit" look="primary" label="Submit" id="formSubmit"></uui-button>
    </form>
  </uui-box>
`;

export default class MyDashboardElement extends UmbElementMixin(HTMLElement) {
    constructor() {
        super();
        this.attachShadow({ mode: "open" });
        this.shadowRoot.appendChild(template.content.cloneNode(true));

        this.shadowRoot
            .getElementById("ols-form")
            .addEventListener("submit", this.onClick.bind(this));

        this.consumeContext(UMB_AUTH_CONTEXT, (_auth) => {
            if (_auth) {
                _authentication = _auth;
            } else {
                console.log("Auth context is missing");
            }
        });
    }

    onClick = (e) => {
        e.preventDefault();
        
        const fileUploader = this.shadowRoot.getElementById("upload");
        const files = fileUploader.value; 
        
        if (!files || files.length === 0) {
            console.log("No files selected to upload.");
            return;
        }

        const formData = new FormData();
        
        files.forEach(file => {
            formData.append("files", file); 
        });

        this.saveData(formData);
    };
    
    async saveData(formDataPayload) {
        const url = "/umbraco/management/api/v1/ols/import";
        const TOKEN = await _authentication?.getLatestToken();
        
        const response = await fetch(url, {
            method: 'POST',
            credentials: 'include',
            headers: {
                'Authorization': 'Bearer ' + TOKEN
            },
            body: formDataPayload
        });

        if (!response.ok) {
            console.log("Upload failed with status:", response.status);
        } else {
            console.log("File uploaded successfully!");
        }
    }
}

customElements.define("ols-extension", MyDashboardElement);

File: Controllers/OLSController.cs

using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.Attributes;
using Umbraco.Cms.Api.Management.Controllers;
using Umbraco.Cms.Api.Management.Routing;
using Microsoft.AspNetCore.Hosting;
using System.IO;
using System.Linq;

namespace MyUmbracoProjecttestSQL11.Controllers
{
    [VersionedApiBackOfficeRoute("ols/import")]
    [ApiExplorerSettings(GroupName = "OLS")]
    [MapToApi("ols-api")]
    public class OLSController : ManagementApiControllerBase
    {
        private readonly IWebHostEnvironment _webHostEnvironment;

        public OLSController(IWebHostEnvironment webHostEnvironment)
        {
            _webHostEnvironment = webHostEnvironment;
        }

        [HttpPost]
        public IActionResult UpdateAddressRecords()
        {
            if (!Request.Form.Files.Any())
            {
                return BadRequest("No files received.");
            }

            var rootPath = _webHostEnvironment.ContentRootPath;
            var targetDirectory = Path.Combine(rootPath, "ols_csv_files");

            if (!Directory.Exists(targetDirectory))
            {
                Directory.CreateDirectory(targetDirectory);
            }

            foreach (var formFile in Request.Form.Files)
            {
                if (formFile.Length > 0)
                {
                    string safeFileName = Path.GetFileName(formFile.FileName);
                    string targetFilePath = Path.Combine(targetDirectory, safeFileName);

                    using (FileStream stream = new FileStream(targetFilePath, FileMode.Create))
                    {
                        formFile.CopyTo(stream);
                    }
                }
            }

            return Ok("Files uploaded successfully");
        }
    }
}

How to Test

  1. Build and run your Umbraco 17 project.

  2. Log in to the backoffice.

  3. Navigate to the Content section.

  4. You will see a new tab labeled “OLS Import” at the top of the dashboard area.

  5. Select a CSV file and hit submit. Check your browser console for the “File uploaded successfully!” message, and check the root ols_csv_files folder in your project directory to verify the file was saved.

Please try it out!

Here’s some info on what was wrong on the code which you pasted:

In the JavaScript:

  • As @Luuk has mentioned, You are setting content-type to application/x-www-form-urlencoded. For files, you must leave the Content-Type blank so the browser can automatically set multipart/form-data and generate the required boundary string. This is why your C# controller was receiving garbage data.
  • Missing File Data: Standard FormData(formElement) doesn’t automatically pull files out of custom web components like <uui-input-file>. You had to manually grab the files from the element’s .value property and append them to the FormData object.
  • The Credentials Location: credentials: 'include' was placed inside the headers object. The fetch API expects it at the root level of the configuration object.

In the C# controller:

  • Security Risk: You passed formFile.FileName directly into the FileStream. If a malicious filename contained ../../../, it could overwrite server files outside your target directory. You needed Path.GetFileName() to sanitize it.
  • Path Building: You mixed string concatenation with Path.Combine (Path.Combine(rootPath + "\\ols_csv_files\\", filename)), which defeats the purpose of the method.
  • Return Type: Returning JsonResult("OK") doesn’t send the proper HTTP status codes back to the fetch API. Swapping to IActionResult allows you to return proper Ok() or BadRequest() responses.

Hope it helps!

Regards,
Shekhar