Попытка потоковой передачи файла PDF с помощью asp.net приводит к «поврежденному файлу» - PullRequest
7 голосов
/ 19 августа 2009

В одном из моих веб-приложений asp.net мне нужно скрыть местоположение файла pdf, который обслуживается пользователям.

Таким образом, я пишу метод, который извлекает его двоичное содержимое из его местоположения в системе CMS, а затем сбрасывает массив байтов веб-пользователю.

Я получаю, к сожалению, ошибку при загрузке потока: «Не удалось открыть файл, потому что он поврежден» (или что-то подобное при открытии файла в Adobe Reader).

Вопрос 1: что я делаю не так? Вопрос 2: могу ли я загружать большие файлы, используя этот подход?

    private void StreamFile(IItem documentItem)
    {
        //CMS vendor specific API
        BinaryContent itemBinaryContent = documentItem.getBinaryContent();
        //Plain old .NET
        Stream fileStream = itemBinaryContent.getContentStream();
        var len = itemBinaryContent.getContentLength();
        SendStream(fileStream, len, itemBinaryContent.getContentType());
    }

    private void SendStream(Stream stream, int contentLen, string contentType)
    {
        Response.ClearContent();
        Response.ContentType = contentType;
        Response.AppendHeader("content-Disposition", string.Format("inline;filename=file.pdf"));
        Response.AppendHeader("content-length", contentLen.ToString());
        var bytes = new byte[contentLen];
        stream.Read(bytes, 0, contentLen);
        stream.Close();
        Response.BinaryWrite(bytes);
        Response.Flush();
    }

Ответы [ 6 ]

7 голосов
/ 09 июля 2010

Другие ответы копируют содержимое файла в память перед отправкой ответа. Если данные уже находятся в памяти, у вас будет две копии, что не очень хорошо для масштабируемости. Это может работать лучше вместо:

public void SendFile(Stream inputStream, long contentLength, string mimeType, string fileName)
{
    string clength = contentLength.ToString(CultureInfo.InvariantCulture);
    HttpResponse response = HttpContext.Current.Response;
    response.ContentType = mimeType;
    response.AddHeader("Content-Disposition", "attachment; filename=" + fileName);
    if (contentLength != -1) response.AddHeader("Content-Length", clength);
    response.ClearContent();
    inputStream.CopyTo(response.OutputStream);
    response.OutputStream.Flush();
    response.End();
}

Поскольку поток представляет собой набор байтов, нет необходимости использовать BinaryReader. И пока входной поток заканчивается в конце файла, вы можете просто использовать метод CopyTo () для потока, который вы хотите отправить в веб-браузер. Все содержимое будет записано в целевой поток без каких-либо промежуточных копий данных.

Если вам нужно скопировать только определенное количество байтов из потока, вы можете создать метод расширения, который добавляет еще пару перегрузок CopyTo ():

public static class Extensions
{
    public static void CopyTo(this Stream inStream, Stream outStream, long length)
    {
        CopyTo(inStream, outStream, length, 4096);
    }

    public static void CopyTo(this Stream inStream, Stream outStream, long length, int blockSize)
    {
        byte[] buffer = new byte[blockSize];
        long currentPosition = 0;

        while (true)
        {
            int read = inStream.Read(buffer, 0, blockSize);
            if (read == 0) break;
            long cPosition = currentPosition + read;
            if (cPosition > length) read = read - Convert.ToInt32(cPosition - length);
            outStream.Write(buffer, 0, read);
            currentPosition += read;
            if (currentPosition >= length) break;
        }
    }
}

Затем вы можете использовать его так:

inputStream.CopyTo(response.OutputStream, contentLength);

Это будет работать с любым входным потоком, но кратким примером будет чтение файла из файловой системы:

string filename = @"C:\dirs.txt";
using (FileStream fs = File.Open(filename, FileMode.Open))
{
    SendFile(fs, fs.Length, "application/octet-stream", filename);
}

Как уже упоминалось ранее, убедитесь, что ваш тип MIME соответствует содержимому.

7 голосов
/ 20 августа 2009

Вот метод, который я использую. Это возвращает вложение, поэтому IE создает диалог открытия / сохранения. Я также знаю, что файлы не будут больше 1M, поэтому я уверен, что есть более чистый способ сделать это.

У меня была похожая проблема с PDF-файлами, и я понял, что мне абсолютно необходимо использовать двоичные потоки и ReadBytes. Что-нибудь со строками испортило это.

Stream stream = GetStream(); // Assuming you have a method that does this.
BinaryReader reader = new BinaryReader(stream);

HttpResponse response = HttpContext.Current.Response;
response.ContentType = "application/pdf";
response.AddHeader("Content-Disposition", "attachment; filename=file.pdf");
response.ClearContent();
response.OutputStream.Write(reader.ReadBytes(1000000), 0, 1000000);

// End the response to prevent further work by the page processor.
response.End();
1 голос
/ 12 ноября 2009

Этот фрагмент сделал это для меня:

                Response.Clear();
                Response.ClearContent();
                Response.ClearHeaders();
                Response.ContentType = mimeType;
                Response.AddHeader("Content-Disposition", "attachment");
                Response.WriteFile(filePath);
                Response.Flush();
0 голосов
/ 24 октября 2017

Этот фрагмент содержит код для чтения в файле из пути к файлу и извлечения имени файла:

private void DownloadFile(string path)
{
    using (var file = System.IO.File.Open(path, FileMode.Open))
    {
        var buffer = new byte[file.Length];
        file.Read(buffer, 0, (int)file.Length);

        var fileName = GetFileName(path);
        WriteFileToOutputStream(fileName, buffer);
    }
}

private string GetFileName(string path)
{
    var fileNameSplit = path.Split('\\');
    var fileNameSplitLength = fileNameSplit.Length;
    var fileName = fileNameSplit[fileNameSplitLength - 1].Split('.')[0];
    return fileName;
}

private void WriteFileToOutputStream(string fileName, byte[] buffer)
{
    Response.Clear();
    Response.ContentType = "application/pdf";
    Response.AddHeader("Content-Disposition",
        $"attachment; filename\"{fileName}.pdf");
    Response.OutputStream.Write(buffer, 0, buffer.Length);
    Response.OutputStream.Close();
    Response.Flush();
    Response.End();
}
0 голосов
/ 12 сентября 2017

когда вы используете свой код, вы копируете байты из вашего PDF-файла прямо в своем ответе. все специальные коды ascii должны быть закодированы для передачи в http. При использовании функции потока поток кодируется, поэтому вам не нужно об этом беспокоиться.

    var bytes = new byte[contentLen];
    stream.Read(bytes, 0, contentLen);
    stream.Close();
    Response.BinaryWrite(bytes);
0 голосов
/ 19 августа 2009

У меня есть нечто похожее, работающее на текущем веб-сайте 2.0. И я помню, как изо всех сил пытался заставить его работать, хотя это было давно, поэтому я не помню борьбу.

Однако есть несколько различий между тем, что у меня есть, и тем, что у вас есть. Надеюсь, это поможет вам решить проблему.

  • После вызова ClearContent я вызываю ClearHeaders ();
  • Я не указываю длину.
  • Я не указываю встраивание в Disposition
  • Я знаю, что все говорят не делать этого, но у меня есть Response.Flush (); сопровождаемый Response.Close ();

И еще одна вещь, проверьте значение contentType, чтобы убедиться, что оно корректно для PDF-файлов (("Приложение / pdf").

...