asp.net mvc upload wielu plików

0

Witam,

Temat wydawał mi się prosty. Ale utknąłem i mimo, że przeczytałem "całe Internety" nie znalazłem rozwiązania.

W ASP.NET MVC mam formularz z jakimiś danymi, w którym ma być przesłane także klika plików. Ilość plików nie jest określona.

Poszukuję jakiegoś "ładnego" rozwiązania. Próbowałem wykorzystać jQuery File Upload https://github.com/blueimp/jQuery-File-Upload. Próbowałem plupload http://www.plupload.com/. Ale nic z tego nie wyszło. Dostaję jakieś dziwne błędy po stronie skryptów js. Także próbowałem wykorzystać PluploadMvc. Jednak przykłady są tylko dla pojedynczych plików.

Oczywiście znam <input multiple="multiple"> ale to nie rozwiązuje mojego problemu. Nie widać listy załączonych plików a drugie kliknięcie powoduje usunięcie wcześniej dodanych plików.

Wracając do tematu. Widzę dwa rozwiązania.

  1. Przesyłam pliki pojedynczo za pomocą ajax.
    Tu pojawia się problem. Jak powiązać zapisane na serwerze pliki z danymi z formularza? Bo rozumiem, że najpierw przesyłane są pliki i gdzieś tam je sobie zapisuję. Po zapisie musiałbym gdzieś zaktualizować informacje o tych plikach. Gdzie to zrobić? w TempData? W Session? A może za pomocą partial view przekazać je z powrotem do formularza? Ta opcja by się przydała aby pokazać klientowi jakie pliki zostały przesłane. Można by ją także wykorzystać do umożliwienia klientowi usunięcia pliku.
    A co w przypadku gdy klient nie zapisze formularza? Pliki pozostaną "bezpańskie" na serwerze.

  2. Szukam pluginu do przesyłania plików zbiorowo.
    Nie ukrywam, że wolałbym tą opcję aby razem z przesłanym formularzem otrzymać pliki.
    Może szanowni forumowicze mają jakieś rozwiązanie? Coś w stylu plupload?

0

Ja bym zrobił tak:

  1. Podczas otwierania formularza (przed wyrenderowaniem widoku) dodajesz do bazy wpis o tym formularzu (tabela form_info) gdzie masz Id, Nazwę użytkownika i co tam jeszcze chcesz,
  2. W ViewModelu zwracasz id nowo dodanego rekordu form_info,
  3. Podczas przesyłania pliku AJAX'em przesyłasz również id form_info (które otrzymałeś w ViewModelu). Po zapisaniu pliku na dysku bierzesz jego nazwą i wrzucasz do tabeli file_info która jest w relacji 1-N do form_info.

Plusem tego rozwiązania jest fakt, że po wywaleniu się aplikacji, połączenia użytkownik może do niego wrócić i nie musi znowu przesyłać plików oraz masz od razu listę przesłanych i skojarzonych z użytkownikiem plików.

1

Znalazłem w końcu rozwiązanie. Nie wiem czy poprawne. Opiszę je dla "potomności" :) i w celu weryfikacji przez szanownych forumowiczów.

1. W Gloabal.asax.cs dodaję

protected void Session_Start(Object sender, EventArgs e)
        {
            Session["init"] = 0;
        }

Umożliwia mi to pobranie dla każdej sesji unikatowego ID: Session.SessionID

2. Wykorzystałem plugin plupload. Ale nie pobrałem go z oryginalnej strony tylko z https://github.com/RickStrahl/Westwind.plUploadHandler
Potrzebne są następujące plik Java Script:

  • plupload.full.js
  • plupload.full.mini.js
  • jquery.plupload.quote.js
  • jquery.plupload.quote.mini.js

Oraz plik css:

  • jquery.plupload.quote.css

3. Do formularza dodaję

        <div id="uploader">
            <p>Your browser doesn't have Flash, Silverlight or HTML5 support.</p>
        </div>

4. Do mojego pliku java script dodaję funkcję obsługującą plugin plupload:

$("#uploader").pluploadQueue({
        runtimes: 'html5,silverlight,flash,html4',
        url: 'home/upload',
        max_file_size: '10mb',
        chunk_size: '1mb',
        unique_names: false,
        filters: [
            { title: "Images", extensions: "jpg,jpeg,bmp,gif,png" },
            { title: "Archives", extensions: "zip,rar,7z,tar,tg" },           
        ],
        rename: true,
        flash_swf_url: '/js/plupload.flash.swf',
        silverlight_xap_url: '/js/plupload.silverlight.xap',
        preinit: {
            Init: function() {
                $('#uploader_container').removeAttr("title");
            }
        }
    });

5. oraz funkcję wysyłającą pliki przed przesłaniem formularza (Nie jestem pewny tej funkcji, ale działa):

$("#form-complaint").submit(function(event)
    {
        var uploader = $('#uploader').pluploadQueue();
        if (uploader.files.length > 0) {            
            uploader.bind('StateChanged', function() {
                if (uploader.files.length === (uploader.total.uploaded + uploader.total.failed)) {
                    $(this).submit();
                }
            });
            uploader.start();
            return true;
        }
        else {
           // $(this).submit();
            return true;
        }    
        return false;
    });

6. No i pozostaje utworzenie metody w kontrolerze do pobierania plików. Metoda ta zostaje wywołana oddzielnie dla każdego przesłanego pliku. Kod javascript prześle najpierw pliki a później dane z formularza.

 [HttpPost]
        public ActionResult Upload(int? chunk, string name)
        {
            var session = Session.SessionID;
            var fileUpload = Request.Files[0];            
            var uploadPath = Server.MapPath("~/App_Data/"+session);
            if (!Directory.Exists(uploadPath))
                Directory.CreateDirectory(uploadPath);
            chunk = chunk ?? 0;
           
            string uploadedFilePath = Path.Combine(uploadPath, name);
            using (var fs = new FileStream(uploadedFilePath, chunk == 0 ? FileMode.Create : FileMode.Append))
            {
                var buffer = new byte[fileUpload.InputStream.Length];
                fileUpload.InputStream.Read(buffer, 0, buffer.Length);
                fs.Write(buffer, 0, buffer.Length);
            }

            return Content("Success", "text/plain");
        }

7. W metodzie odbioru danych z formularza wykorzystuję Session.SessionID. Jest ono takie samo jak poprzednie z metody Upload.
Wydaje mi się, że to rozwiązanie działa. Zastanawiam się tylko czy jest prawidłowe. Czy jednak nie powinienem dla każdej sesji wstawiać np. do Session["MojUnikatowyId"] = coś_unikatowego i z tego korzystać w metodzie Upload oraz metodzie odbioru danych z formularza.

8. Warto także dodać do pliku css ukrycie przycisku Send files z pluginu plupload:

.plupload_button.plupload_start {
    display: none!important;
}

Oczywiście jeszcze pozostaje kwestia powiązania danych z formularza z plikami. Ale one są już w folderze o unikatowej (mam nadzieję) nazwie, którą mogę przekazać do procedury zapisującej dane z formularza.

Proszę szanownych forumowiczów o komentarze czy to rozwiązanie jest prawidłowe.

1 użytkowników online, w tym zalogowanych: 0, gości: 1