Just in case, I am still quite new to ServiceStack.
I am working on a form with file upload and for some reason the sent files do not appear in the Request.Files in our webservice.
In the frontent we’re using SvelteKit and TypeScript. I am trying to send the form as FormData, following the guidelines from the documentation: Managed Files Uploads
First of all, I noticed that the files never get to the webservice - Request.Files always shows 0.
I found out that FormData with files should be sent as ‘multipart/form-data’ content type. However, inspecting the Request shows ‘application/json’ instead. The payload in the browser’s dev tools shows that the request has the ‘multipart/form-data’ content type, which makes me believe that everything on the frontend side should be correct. We even tried to set the default content type to ‘MultiPartFormData’ in our webservice, but the result is still the same - no files received and a different content type.
I have no idea where the problem is and what I am missing/misunderstanding. Can you please help me with this problem?
const client = JsonApiClient.create(BaseUrl)
const formData = new FormData(document.forms[0]) // HTML Form Element
const api = await client.apiForm(new MyRequest(), formData)
I already have ‘multipart/form-data’ in my form and I used the apiForm you mentioned. The only difference I can see is that our ‘client’ is ‘JsonServiceClient’, not ‘JsonApiClient’. Is that a significant difference?
For some reason, I can’t import ‘JsonApiClient’ and I can access only the ‘JsonServiceClient’.
If you’re using apiForm populated with FormData it should be sending multipart/form-data requests, inspect the FormData instance (e.g. Array.from(formData.keys())) to ensure it has the File Input id, then have a look at the Raw HTTP Headers in Web Inspector to see what’s actually being sent.
Note:
That’s only required when you’re submitting the HTML Form natively, not when you’re using JavaScript in which case you’ll instead need to use apiForm, e.g:
const formData = new FormData(formElement)
const api = await client.apiForm(new MyRequest(), formData)
Ok, I removed the ‘multipart/form-data’ from the form.
About what is actually being sent:
It looks like my formData is populated correctly, I can see the input’s id in the console.
The content type in the headers is ‘multipart/form-data’.
When I’m checking the payload, I can see this:
Request.Files is basically a wrapper over the underlying the Uploaded Files collection that’s populated by the underlying HTTP Framework, there’s limited control other than ensuring the HTTP Request is properly sent.
If you can provide the full HTTP Request and Response Headers in WebInspector and the C# Request DTO hopefully it will provide some insights otherwise if you can provide a minimal stand-alone repro I’ll be able to let you know what the issue is.
i.e. It should be using the Request DTO name not the /messages/send custom Route, but this should be automatically handled with apiForm(new MyRequest(), formData), what JavaScript code are you using to send the request?
Also if you’re going through a npm HTTP Proxy that could potentially be causing the issue if it doesn’t proxy multi-part requests properly, i.e. if your .NET API is running on https://localhost:5001 you can try sending a request directly to the .NET API:
import { JsonApiClient } from "@servicestack/client"
const client = JsonApiClient.create("https://localhost:5001")
const formData = new FormData(formElement)
const api = await client.apiForm(new MyRequest(), formData)
But if you’ll need to enable CORS in order to perform cross-domain requests.
I believe what appears in the HTTP Request headers is the url we’re using in the fetch above. The thing is, we’re using it to pass the data on to +server.ts (it’s a SvelteKit file, it runs on the server side), where we use the apiForm:
// @Route("/message", "POST")
export class CreateMessage implements IReturn<Message> {
public constructor(init?: Partial<CreateMessage>) {
(Object as any).assign(this, init);
}
public createResponse() {
return new Message();
}
public getTypeName() {
return 'CreateMessage';
}
}
From what I found out, we are already sending the request directly to the .NET API. All other requests work well, we only have a problem with this one. But it’s also the only case where we’re using the multipart/form-data content
So in this case you’re not using the TypeScript JsonServiceClient here and are instead using fetch directly in which case Mozilla’s Uploading a file with fetch would be more relevant.
The Route for the Request DTO suggests its custom route is /message but you’re trying to send the request to /api/messages/send which I don’t see the Request DTO for.
I don’t really know what’s going on here in your POST RequestHandler which appears is manually setting session ids instead of authenticating the client normally and it’s not clear what request.formData() is returning, but if it’s a valid FormData then the JsonServiceClient request should work:
But you’ve only send the HTTP Headers for the /messages/send request which isn’t the CreateMessage request. What are the HTTP Headers for the CreateMessage request sent with the JsonServiceClient and does it populate Request.Files?
We’re using the fetch in ‘sendMessage’ for internal routing in SvelteKit. I might have sent that part unnecessairly, it doesn’t have much to do with the ServiceStack. Sorry for that.
About the request.formData(): it’s just the form data delivered by the fetch. These are just the values that I wrote in the form and it is valid FormData.
Here are the headers for the CreateMessage request:
You’ve been sending information for the wrong request in that case, you’ll need to check the keys to ensure the uploaded file is included in the FormData sent (e.g. Array.from(formData.keys())).
If it does, what are the WebInspector HTTP Request / Response Headers that the client sends for the CreateMessage request?