¿Cómo publico datos de formularios con fetch api?

117

Mi código:

fetch("api/xxx", {
    body: new FormData(document.getElementById("form")),
    headers: {
        "Content-Type": "application/x-www-form-urlencoded",
        // "Content-Type": "multipart/form-data",
    },
    method: "post",
}

Intenté publicar mi formulario usando fetch api, y el cuerpo que envía es como:

-----------------------------114782935826962
Content-Disposition: form-data; name="email"

test@example.com
-----------------------------114782935826962
Content-Disposition: form-data; name="password"

pw
-----------------------------114782935826962--

(No sé por qué se cambia el número en el límite cada vez que envía ...)

Me gustaría que envíe los datos con "Content-Type": "application / x-www-form-urlencoded", ¿qué debo hacer? O si solo tengo que lidiar con eso, ¿cómo decodifico los datos en mi controlador?


A quien responda mi pregunta, sé que puedo hacerlo con:

fetch("api/xxx", {
    body: "[email protected]&password=pw",
    headers: {
        "Content-Type": "application/x-www-form-urlencoded",
    },
    method: "post",
}

Lo que quiero es algo como $ ("# formulario"). Serialize () en jQuery (sin usar jQuery) o la forma de decodificar mulitpart / form-data en el controlador. Sin embargo, gracias por tus respuestas.

Zack
fuente
¿Cuál es el problema con el uso FormData?
guest271314
1
Quiero publicarlo como "[email protected]&password=pw". ¿Es posible?
Zack
1
“No sé por qué el número en el límite se cambia cada vez que envía…” - El identificador de límite es simplemente un identificador aleatorio, puede ser cualquier cosa y no tiene ningún significado por sí solo. Así que no hay nada de malo en elegir un número aleatorio allí (que es lo que suelen hacer los clientes).
empuje el

Respuestas:

149

Para citar MDN enFormData (el énfasis es mío):

La FormDatainterfaz proporciona una forma de construir fácilmente un conjunto de pares clave / valor que representan campos de formulario y sus valores, que luego se pueden enviar fácilmente mediante el XMLHttpRequest.send()método. Utiliza el mismo formato que usaría un formulario si se configurara el tipo de codificación"multipart/form-data" .

Entonces, cuando lo usas, FormDatate estás encerrando en multipart/form-data. No hay forma de enviar un FormDataobjeto como cuerpo y no enviar datos en el multipart/form-dataformato.

Si desea enviar los datos, application/x-www-form-urlencodedtendrá que especificar el cuerpo como una cadena codificada en URL o pasar un URLSearchParamsobjeto. Desafortunadamente, este último no se puede inicializar directamente desde un formelemento. Si no desea iterar a través de los elementos de su formulario usted mismo (lo que podría hacer usando HTMLFormElement.elements), también puede crear un URLSearchParamsobjeto a partir de un FormDataobjeto:

const data = new URLSearchParams();
for (const pair of new FormData(formElement)) {
    data.append(pair[0], pair[1]);
}

fetch(url, {
    method: 'post',
    body: data,
})
.then(…);

Tenga en cuenta que no es necesario que especifique un Content-Typeencabezado usted mismo.


Como señaló monk-time en los comentarios, también puede crear URLSearchParamsy pasar el FormDataobjeto directamente, en lugar de agregar los valores en un bucle:

const data = new URLSearchParams(new FormData(formElement));

Sin embargo, esto todavía tiene cierto soporte experimental en los navegadores, así que asegúrese de probarlo correctamente antes de usarlo.

dar un toque
fuente
18
También puede usar un objeto o simplemente FormDataen el constructor directamente en lugar de un bucle:new URLSearchParams(new FormData(formElement))
monk-time
@ monk-time En el momento de escribir esa respuesta, el argumento del constructor de URLSearchParamsera muy nuevo y tenía un soporte muy limitado.
empuje el
3
lo siento, eso no fue una queja, solo una nota para todos los que leerán esto en el futuro.
hora del monje el
1
@Prasanth Puede especificar el tipo de contenido usted mismo explícitamente, pero debe elegir el correcto . Es más fácil dejarlo y fetchencargarse de él por usted.
empuje el
1
@chovy URLSearchParamsestá integrado en la mayoría de los navegadores modernos como un objeto global y también funciona desde Node.
empuje el
67

Cliente

No establezca el encabezado de tipo de contenido.

// Build formData object.
let formData = new FormData();
formData.append('name', 'John');
formData.append('password', 'John123');

fetch("api/SampleData",
    {
        body: formData,
        method: "post"
    });

Servidor

Utilice el FromFormatributo para especificar que la fuente de enlace son datos de formulario.

[Route("api/[controller]")]
public class SampleDataController : Controller
{
    [HttpPost]
    public IActionResult Create([FromForm]UserDto dto)
    {
        return Ok();
    }
}

public class UserDto
{
    public string Name { get; set; }
    public string Password { get; set; }
}
regnauld
fuente
4
Si bien esto funciona, esto no envía los datos, ya application/x-www-form-urlencodedque es lo que solicita OP.
empuje el
5
Para mí, funcionó cuando quité Content-Type del encabezado y dejé que el navegador lo hiciera automáticamente. ¡Gracias!
Chris
¡Gracias @regnauld han estado tratando de resolver esto todo el día!
ak85
1
Si no configura 'Tipo de contenido' para Fetch, lo configurará como multipart/form-data, que es lo que debería ser para los datos del formulario. Luego, puede usar multeren expressjs para analizar ese formato de datos fácilmente.
kyw
23

Puede establecer bodyuna instancia de URLSearchParamscon una cadena de consulta pasada como argumento

fetch("/path/to/server", {
  method:"POST"
, body:new URLSearchParams("[email protected]&password=pw")
})

document.forms[0].onsubmit = async(e) => {
  e.preventDefault();
  const params = new URLSearchParams([...new FormData(e.target).entries()]);
  // fetch("/path/to/server", {method:"POST", body:params})
  const response = await new Response(params).text();
  console.log(response);
}
<form>
  <input name="email" value="[email protected]">
  <input name="password" value="pw">
  <input type="submit">
</form>

invitado271314
fuente
2
Reflect.apply(params.set, params, props)es una forma de decir particularmente ilegible params.set(props[0], props[1]).
toque el
@poke Reflect.apply(params.set, params, props)se puede leer desde la perspectiva aquí.
guest271314
Esta parece ser la única respuesta que funciona aquí: / ¡gracias! :)
OZZIE
0

Utilice FormDatay fetchpara capturar y enviar datos

Kamil Kiełczewski
fuente
0
function card(fileUri) {
let body = new FormData();
let formData = new FormData();
formData.append('file', fileUri);

fetch("http://X.X.X.X:PORT/upload",
  {
      body: formData,
      method: "post"
  });
 }
soufiane ELAMMARI
fuente
7
Las respuestas de solo código generalmente se pueden mejorar agregando alguna explicación de cómo y por qué funcionan. Cuando agregue una respuesta a una pregunta de dos años con respuestas existentes, es importante señalar qué aspecto nuevo de la pregunta aborda su respuesta.
Jason Aller