Pobierz plik dowolnego typu w Asp.Net MVC za pomocą FileResult?

228

Zasugerowałem, że powinienem użyć FileResult, aby umożliwić użytkownikom pobieranie plików z mojej aplikacji Asp.Net MVC. Ale jedyne przykłady, jakie mogę znaleźć, zawsze dotyczą plików obrazów (określając typ zawartości image / jpeg).

Ale co, jeśli nie mogę poznać typu pliku? Chcę, aby użytkownicy mogli pobierać praktycznie dowolny plik z obszaru plików mojej witryny.

Przeczytałem jedną z metod, aby to zrobić (zobacz kod w poprzednim poście ), która faktycznie działa dobrze, z wyjątkiem jednej rzeczy: nazwa pliku pojawiająca się w oknie dialogowym Zapisz jako jest połączona ze ścieżką pliku z podkreślnikami ( folder_folder_plik.ext). Ponadto wydaje się, że ludzie myślą, że powinienem zwrócić FileResult zamiast używać tej niestandardowej klasy, którą znalazłem BinaryContentResult.

Czy ktoś zna „prawidłowy” sposób takiego pobrania w MVC?

EDYCJA: Mam odpowiedź (poniżej), ale pomyślałem, że powinienem opublikować pełny działający kod, jeśli ktoś inny jest zainteresowany:

public ActionResult Download(string filePath, string fileName)
{
    string fullName = Path.Combine(GetBaseDir(), filePath, fileName);

    byte[] fileBytes = GetFile(fullName);
    return File(
        fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
}

byte[] GetFile(string s)
{
    System.IO.FileStream fs = System.IO.File.OpenRead(s);
    byte[] data = new byte[fs.Length];
    int br = fs.Read(data, 0, data.Length);
    if (br != fs.Length)
        throw new System.IO.IOException(s);
    return data;
}
Anders
źródło
12
To, co robisz, jest raczej niebezpieczne. Zasadniczo pozwalasz użytkownikom pobierać dowolny plik z serwera, do którego użytkownik wykonujący może uzyskać dostęp.
Paul Fleming,
1
To prawda - usunięcie ścieżki pliku i przybicie jej do ciała w wyniku działania byłoby nieco bezpieczniejsze. Przynajmniej w ten sposób mają dostęp tylko do określonego folderu.
shubniggurath
2
Czy są jakieś narzędzia, które pozwalają znaleźć potencjalnie niebezpieczne luki, takie jak ta?
David,
Uważam, że wygodnie jest ustawić typ zawartości jako Response.ContentType = MimeMapping.GetMimeMapping(filePath);, ze stackoverflow.com/a/22231074/4573839
yu yang Jian,
Czego używasz po stronie klienta?
FrenkyB

Odpowiedzi:

425

Możesz po prostu określić ogólny typ MIME strumienia oktetów:

public FileResult Download()
{
    byte[] fileBytes = System.IO.File.ReadAllBytes(@"c:\folder\myfile.ext");
    string fileName = "myfile.ext";
    return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
}
Ian Henry
źródło
4
Ok, mógłbym spróbować, ale co wchodzi w tablicę bajtów []?
Anders
3
Nieważne, myślę, że to rozgryzłem. Czytam nazwę pliku (pełną ścieżkę) do FileStream, a następnie do tablicy bajtów, a potem działało to jak urok! Dzięki!
Anders
5
To ładuje cały plik do pamięci, aby go przesłać strumieniowo; w przypadku dużych plików jest to świnia. O wiele lepszym rozwiązaniem jest poniższe rozwiązanie, które nie musi najpierw ładować pliku do pamięci.
HBlackorby
13
Ponieważ ta odpowiedź ma prawie pięć lat, tak. Jeśli robisz to w celu obsługi bardzo dużych plików, nie rób tego. Jeśli to możliwe, użyj osobnego statycznego serwera plików, aby nie wiązać wątków aplikacji lub jednej z wielu nowych technik udostępniania plików dodanych do MVC od 2010 roku. Pokazuje to tylko prawidłowy typ MIME do użycia, gdy typ MIME jest nieznany . ReadAllByteszostał dodany wiele lat później w edycji. Dlaczego to moja druga najbardziej uprzywilejowana odpowiedź? No cóż.
Ian Henry
10
Uzyskiwanie tego błędu:non-invocable member "File" cannot be used like a method.
A-Sharabiani
105

Struktura MVC obsługuje to natywnie. Kontroler System.Web.MVC.Controller.File zapewnia metody zwracania pliku według nazwy / strumienia / tablicy .

Na przykład używając wirtualnej ścieżki do pliku, możesz wykonać następujące czynności.

return File(virtualFilePath, System.Net.Mime.MediaTypeNames.Application.Octet,  Path.GetFileName(virtualFilePath));
Jonathan
źródło
36

Jeśli korzystasz z .NET Framework 4.5, wówczas używasz MimeMapping.GetMimeMapping (ciąg FileName), aby uzyskać typ MIME dla swojego pliku. Tak to wykorzystałem w swojej akcji.

return File(Path.Combine(@"c:\path", fileFromDB.FileNameOnDisk), MimeMapping.GetMimeMapping(fileFromDB.FileName), fileFromDB.FileName);
Salman Hasrat Khan
źródło
To, że mapowanie Mime jest przyjemne, ale czy nie jest to prosty proces, aby dowiedzieć się, jaki jest typ pliku w czasie wykonywania?
Mohammed Noureldin,
@MohammedNoureldin to nie „rozgadywanie”, istnieje prosta tabela mapowania oparta na rozszerzeniach plików lub coś w tym rodzaju. Serwer robi to dla wszystkich plików statycznych, nie jest wolny.
Al Kepp
13

Phil Haack ma fajny artykuł, w którym stworzył niestandardową klasę Wynik działania pobierania pliku. Musisz jedynie określić wirtualną ścieżkę pliku i nazwę, którą chcesz zapisać.

Użyłem go raz i oto mój kod.

        [AcceptVerbs(HttpVerbs.Get)]
        public ActionResult Download(int fileID)
        {
            Data.LinqToSql.File file = _fileService.GetByID(fileID);

            return new DownloadResult { VirtualPath = GetVirtualPath(file.Path),
                                        FileDownloadName = file.Name };
        }

W moim przykładzie przechowywałem fizyczną ścieżkę plików, więc użyłem tej metody pomocnika - że znalazłem gdzieś, czego nie pamiętam - aby przekonwertować ją na ścieżkę wirtualną

        private string GetVirtualPath(string physicalPath)
        {
            string rootpath = Server.MapPath("~/");

            physicalPath = physicalPath.Replace(rootpath, "");
            physicalPath = physicalPath.Replace("\\", "/");

            return "~/" + physicalPath;
        }

Oto pełna klasa zaczerpnięta z artykułu Phill Haack

public class DownloadResult : ActionResult {

    public DownloadResult() {}

    public DownloadResult(string virtualPath) {
        this.VirtualPath = virtualPath;
    }

    public string VirtualPath {
        get;
        set;
    }

    public string FileDownloadName {
        get;
        set;
    }

    public override void ExecuteResult(ControllerContext context) {
        if (!String.IsNullOrEmpty(FileDownloadName)) {
            context.HttpContext.Response.AddHeader("content-disposition", 
            "attachment; filename=" + this.FileDownloadName)
        }

        string filePath = context.HttpContext.Server.MapPath(this.VirtualPath);
        context.HttpContext.Response.TransmitFile(filePath);
    }
}
Manaf Abu.Rous
źródło
1
Tak, tak, widziałem też ten artykuł, ale wydaje się, że robi on coś w rodzaju tego samego artykułu, którego użyłem (patrz odniesienie do mojego poprzedniego postu), a on sam mówi u góry strony, że obejście to nie powinno nie będą już potrzebne, ponieważ: „NOWA AKTUALIZACJA: Nie jest już potrzebny ten niestandardowy ActionResult, ponieważ ASP.NET MVC zawiera teraz jeden w pudełku”. Ale niestety nie mówi nic więcej o tym, jak to wykorzystać.
Anders
@ManafAbuRous, jeśli dokładnie przeczytasz kod, zobaczysz, że faktycznie konwertuje on ścieżkę wirtualną na ścieżkę fizyczną ( Server.MapPath(this.VirtualPath)), więc używanie tego bezpośrednio bez zmian jest odrobinę naiwne. Powinieneś stworzyć alternatywę, która akceptuje, PhysicalPathbiorąc pod uwagę, że to jest ostatecznie wymagane i to jest to, co przechowujesz. Byłoby to znacznie bezpieczniejsze, ponieważ przyjęto założenie, że ścieżka fizyczna i ścieżka względna byłyby takie same (z wyłączeniem katalogu głównego). Pliki danych są często przechowywane w App_Data. Nie jest to dostępne jako ścieżka względna.
Paul Fleming
GetVirtualPath jest świetny .... bardzo przydatny. Dziękuję Ci!
Zvi Redler,
6

Dzięki Ian Henry !

W przypadku, gdy potrzebujesz pobrać plik z MS SQL Server tutaj jest rozwiązanie.

public FileResult DownloadDocument(string id)
        {
            if (!string.IsNullOrEmpty(id))
            {
                try
                {
                    var fileId = Guid.Parse(id);

                    var myFile = AppModel.MyFiles.SingleOrDefault(x => x.Id == fileId);

                    if (myFile != null)
                    {
                        byte[] fileBytes = myFile.FileData;
                        return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, myFile.FileName);
                    }
                }
                catch
                {
                }
            }

            return null;
        }

Gdzie AppModel jest EntityFrameworkmodelem, a MyFiles przedstawia tabelę w bazie danych. FileData znajduje się varbinary(MAX)w tabeli MyFiles .

Deweloper
źródło
2

jego proste po prostu podaj ścieżkę fizyczną w katalogu ścieżka z nazwą pliku

public FilePathResult GetFileFromDisk(string fileName)
{
    return File(directoryPath, "multipart/form-data", fileName);
}
DARSHAN SHINDE
źródło
A co z wywoływaniem tej metody po stronie klienta? Powiedzmy, że jeśli chcesz pokazać zapisz jako okno dialogowe?
FrenkyB
0
   public ActionResult Download()
        {
            var document = //Obtain document from database context
    var cd = new System.Net.Mime.ContentDisposition
    {
        FileName = document.FileName,
        Inline = false,
    };
            Response.AppendHeader("Content-Disposition", cd.ToString());
            return File(document.Data, document.ContentType);
        }
hossein zakizadeh
źródło
-1

if (string.IsNullOrWhiteSpace (fileName)) zwraca treść („brak nazwy pliku”);

        var path = Path.Combine(your path, your filename);

        var stream = new FileStream(path, FileMode.Open);

        return File(stream, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
Caio Augusto
źródło
-4

GetFile powinien zamykać plik (lub otwierać go w trakcie używania). Następnie możesz usunąć plik po konwersji na bajty - pobieranie zostanie wykonane w tym buforze bajtów.

    byte[] GetFile(string s)
    {
        byte[] data;
        using (System.IO.FileStream fs = System.IO.File.OpenRead(s))
        {
            data = new byte[fs.Length];
            int br = fs.Read(data, 0, data.Length);
            if (br != fs.Length)
                throw new System.IO.IOException(s);
        }
        return data;
    }

Więc w metodzie pobierania ...

        byte[] fileBytes = GetFile(file);
        // delete the file after conversion to bytes
        System.IO.File.Delete(file);
        // have the file download dialog only display the base name of the file            return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, Path.GetFileName(file));
CDichter
źródło
2
Proszę nigdy, nigdy, nigdy nie ładować całych plików do pamięci w taki sposób
makhdumi,