I'm unable to upload assets via the API; 'Can only upload one file" error

I have…

I’m submitting a…

  • [ x] Regression (a behavior that stopped working in a new release) ( I’m unsure but I suspect so )
  • [ ] Bug report
  • [ ] Performance issue
  • [ ] Documentation issue or request

Current behavior

I am trying to use the Assets API to upload new files. I am able to create the folder, but when it comes to the files I can’t seem to get past the “Can only upload one file.” error.

I am using Node.JS and referenced the squidex-client-manager project:

setup the Swagger client and force multipart/form-data :

this.squidexApi = await new Swagger({
      url: this.squidexSpecUrl,
      requestInterceptor: (req) => {
        // eslint-disable-next-line no-underscore-dangle
        if (req.body && req.body._currentStream !== undefined) {
          // If a stream is detected, use multipart/form-data
          req.headers['Content-Type'] = 'multipart/form-data';
        } else if (req.body && !req.headers['Content-Type']) {
          req.headers['Content-Type'] = 'application/json';
        }
        req.headers.Authorization = `Bearer ${token}`;
      },
    });

Upload the file:


const createAssetAsync = async (assetUrl, parentId, id, Duplicate) => {
  await client.ensureValidClient();
  const mimeType = findMimeType(assetUrl);

  if (!mimeType) {
    throw new Error(`Invalid content type when looking up mime type for ${assetUrl}`);
  }

  const form = new FormData();
  form.append('file', fs.createReadStream(assetUrl));
  form.append('mimeType', mimeType);

  const res = await client.squidexApi.apis.Assets
      .Assets_PostAsset({ app: client.appName, parentId }, { requestBody: form });
  return res;
}

I’ve been trying variations of this, but no matter what I try I end up with the same error. I’ve also tried without the parentId and manually setting the mime type. Also, I know the multipart/form-data is being applied

Expected behavior

Upload the file successfully

more explicit error messaging.

Minimal reproduction of the problem

see desc

Environment

  • [ x] Self hosted with docker
  • [ ] Self hosted with IIS
  • [ ] Self hosted with other version
  • [ ] Cloud version

Version: squidex/squidex:local

Browser:

  • [ x] Chrome (desktop)
  • [ ] Chrome (Android)
  • [ ] Chrome (iOS)
  • [ ] Firefox
  • [ ] Safari (desktop)
  • [ ] Safari (iOS)
  • [ ] IE
  • [ ] Edge

Others:

I saw a minimal example ( codepen? ) on this that you linked to at one point, but I lost it. Do you still have that link? It might help.

There is this sample: https://jsfiddle.net/1273uf8a/4/

I am also curious why the swagger code does not work. I generate the client library for .NET myself and it works. You could try another generator. I use this one: https://github.com/RicoSuter/NSwag

it also created Typescript code.

1 Like

@Sebastian - Thank you, I was able to upload a single file withe the fetch API example, and will look into NSwag next, it looks very useful.

My fetch example turned into this:

  const formData = new FormData();
  formData.append('file', fs.createReadStream(assetUrl));
  formData.append('mimeType', mimeType);

  return fetch(`http://squidex.local/api/apps/${app}/assets`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${client.tokenManager.accessToken.access_token}`
    },
    body: formData
  });

I am unable, however, to also pass the parentId param using this. I’ve tried to append this to the formData, and even thought to use it as a _GET param, but no luck. How would you suggest submitting the JSON with the formData?

Solved:

What worked for me was using axios and applying the parentId in the config object :


const createAssetAsync = async (assetUrl, parentId, id, duplicate=true) => {
  await client.ensureValidClient();
  const mimeType = findMimeType(assetUrl);
  // TODO: add support for remote URLS
  if (!mimeType) {
    throw new Error(`Invalid content type when looking up mime type for ${assetUrl}`);
  }

  const formData = new FormData();
  formData.append('file', fs.createReadStream(assetUrl));
  formData.append('mimeType', mimeType);
  let params = {
    duplicate
  }
  if( !_.isUndefined( parentId ) ) {
    params['ParentId'] = `${parentId}`;
  }
  if( !_.isUndefined( id ) ) {
    params['id'] = `${id}`;
  }

  // In Node.js environment you need to set boundary in the header field 'Content-Type' by calling method `getHeaders` - NOTE I don't think this is required with modern Node versions.
  let formHeaders = formData.getHeaders();
  formHeaders['Authorization'] = `Bearer ${client.tokenManager.accessToken.access_token}`;

  return axios.post(`http://squidex.local/api/apps/${app}/assets`, formData, {
    headers: {
      ...formHeaders,
    },
    params
  })
}

I don’t know what axios does with the params but the parentId ist just query string.

Yeah I think I was just confusing myself with a few things. I’m more used to axios than I am any other method, so this solution worked for me. Thank you for your help