W jednym z ostatnich projektów musieliśmy zezwolić użytkownikowi na wgrywanie plików wraz z listą dodatkowych pól. Wszystko to powinno być zamknięte w ładnym formularzu, który ajaxowo będzie wykorzystywał Web API do wgrywania plików wraz z metadanymi.

Do tej pory, wszystkie sposoby jakie znałem, albo ograniczyły się do form post bez ajax, albo do flash czy też kodu generującego iframe w którym robiło się normalny post. By móc przyjąć plik nasz formularz musi ustawić content type na multipart/form-data – jest to content type, który służy do przesyłania dużych binarnych danych. Niby za pomocą jquery możemy ustawić sobie contentType i zrobić post, jednak próba zrobienia tego (tylko na danych bez pliku) za każdym razem odbijały się o Web API w trakcie sprawdzenia czy request jest multpart/form-data:

if (!Request.Content.IsMimeMultipartContent())
{
    throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}

Postman już był bardziej pomocny, wystarczyło tylko nie ustawiać headersów – to znaczy, jak chcecie zrobić poprawny request mutipart/form-data za pomocą postman, to powinien on wyglądać tak:

Postman - multipart/form-data
Postman – multipart/form-data

Więc coś gdzieś musiało być robione przez nas źle. Byśmy się pewnie tak i głowili i głowili, gdyby nie to, że odkryliśmy obiekt FormData, który pozwala ustawiać pary klucz/wartość, które mogą reprezentować pola formularza czy też dowolne wartości jakie sami ustawimy. Dane w ten sposób przygotowane mogą następnie zostać wysłane za pomocą XMLHttpRequest na serwer (jak i za pomocą $.ajax).

Przy wysyłaniu danych przez jQuery, trzeba jednak ustawić dwie wartości na false:

processData: false,
contentType: false

W przeciwnym wypadku request nam się nie uda.

By skorzystać z FormData wystarczy:

var fd = new FormData();
fd.append('key', value);

To na co warto zwrócić uwagę, to drugi parametr. Jeżeli nie jest on File czy Blob, zostanie on automatycznie przekonwertowany do string. Dane po stronie serwera więc będzie trzeba konwertować ręcznie.

Podobnie jak wartości, możemy dodawać pliki i bloby. Dodanie pliku jest proste, wystarczy, że podamy element html i wyciągniemy z niego pliki:

fd.append('my_own_file_key_name_whatever_i_want', intpuTypeFile.files[0]);

Podobnie jak z plikiem, możemy też zrobić z “listą elementów”. To znaczy, do FormData, możemy podać element którego dzieci typu input mają być traktowane jako element wartości FormData:

var fd = new FormData(document.querySelector('.my-custom-form'));

Jeżeli elementem grupującym jest formularz (tak jak wyżej), to wszystkie pola zostaną dodane. Jeżeli zaś interesuje nas subset pól, to możemy podać div lub inny element, który zawiera elementy. Plus taki, że nie trzeba nic ręcznie klepać. Ale warto wiedzieć, że taka możliwość też jest :)

Mając gotowy FormData, możemy to z ajaxować:

var promise = $.ajax({
    url: 'url',
    type: 'POST',
    data: fd,
    processData: false,
    contentType: false
  });

Lub za pomocą XMLHttpRequest za-requestować:

var request = new XMLHttpRequest();
request.open('POST', 'url');
request.onload = function(oEvent) {
    if (request.status == 200) {
    } else {
    }
};

request.send(fd);

Oczywiście onload może być bardziej zaawansowane itp. Podaje to tutaj jako ciekawostkę, sami skorzystaliśmy z $.ajax.

Innym rozwiązaniem wgrywania plików jest manualna implementacja tego z wykorzystaniem XMLHttpRequest. Jest to ciężkie, wolne i uciążliwe. Polega na łączeniu stringów… :)

A wy znaliście już FormData?

9 KOMENTARZE

  1. Znałem bo też musiałem wgrywać pliki na serwer przez Ajax :P I radzę pilnować czy użytkownicy nie mają błędów, wiem że u mnie były problemy z kompatybilnością tego rozwiązania.

  2. Tak dokładnie – formData jedyny słuszny :) generalnie z DZ można dużo fajnych zrobić rzeczy, tak ze jak byś kiedyś sie wahał – szczerze polecam :-)

Comments are closed.