Используя ASP.NET Web API, как контроллер может вернуть коллекцию потоковых изображений, сжатых с использованием библиотеки DotNetZip? - программирование
Подтвердить что ты не робот

Используя ASP.NET Web API, как контроллер может вернуть коллекцию потоковых изображений, сжатых с использованием библиотеки DotNetZip?

Как создать контроллер веб-API, который генерирует и возвращает сжатый zip файл, потоковый из коллекции файлов JPEG в памяти (объекты MemoryStream). Я пытаюсь использовать DotNetZip Library. Я нашел этот пример: http://www.4guysfromrolla.com/articles/092910-1.aspx#postadlink. Но Response.OutputStream недоступен в веб-API и поэтому техническая работа не совсем работает. Поэтому я попытался сохранить zip файл в новый MemoryStream; но он бросил. Наконец, я попытался использовать PushStreamContent. Здесь мой код:

    public HttpResponseMessage Get(string imageIDsList) {
        var imageIDs = imageIDsList.Split(',').Select(_ => int.Parse(_));
        var any = _dataContext.DeepZoomImages.Select(_ => _.ImageID).Where(_ => imageIDs.Contains(_)).Any();
        if (!any) {
            throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
        }
        var dzImages = _dataContext.DeepZoomImages.Where(_ => imageIDs.Contains(_.ImageID));
        using (var zipFile = new ZipFile()) {
            foreach (var dzImage in dzImages) {
                var bitmap = GetFullSizeBitmap(dzImage);
                var memoryStream = new MemoryStream();
                bitmap.Save(memoryStream, ImageFormat.Jpeg);
                var fileName = string.Format("{0}.jpg", dzImage.ImageName);
                zipFile.AddEntry(fileName, memoryStream);
            }
            var response = new HttpResponseMessage(HttpStatusCode.OK);
            var memStream = new MemoryStream();
            zipFile.Save(memStream); //Null Reference Exception
            response.Content = new ByteArrayContent(memStream.ToArray());
            response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/zip");
            response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = string.Format("{0}_images.zip", dzImages.Count()) };
            return response;
        }
    }

zipFile.Save(memStream) генерирует нулевую ссылку. Но ни zipFile, ни memStream не являются нулевыми, и нет внутреннего исключения. Поэтому я не уверен, что вызывает нулевую ссылку. У меня очень мало опыта работы с Web API, потоками памяти, и я никогда раньше не использовал DotNetZipLibrary. Это зависит от этого вопроса: Хотите эффективный контроллер веб-API ASP.NET, который может надежно вернуть от 30 до 50 ~ 3 МБ JPEG

Любые идеи? спасибо!

4b9b3361

Ответ 1

Более общий подход будет работать следующим образом:

using Ionic.Zip; // from NUGET-Package "DotNetZip"

public HttpResponseMessage Zipped()
{
    using (var zipFile = new ZipFile())
    {
        // add all files you need from disk, database or memory
        // zipFile.AddEntry(...);

        return ZipContentResult(zipFile);
    }
}

protected HttpResponseMessage ZipContentResult(ZipFile zipFile)
{
    // inspired from http://stackoverflow.com/a/16171977/92756
    var pushStreamContent = new PushStreamContent((stream, content, context) =>
    {
        zipFile.Save(stream);
        stream.Close(); // After save we close the stream to signal that we are done writing.
    }, "application/zip");

    return new HttpResponseMessage(HttpStatusCode.OK) {Content = pushStreamContent};
}

Метод ZipContentResult также может работать в базовом классе и использоваться из любого другого действия в любом контроллере api.

Ответ 2

В этом случае можно использовать класс PushStreamContent, чтобы исключить необходимость в MemoryStream, по крайней мере, для всего zip файла. Он может быть реализован следующим образом:

    public HttpResponseMessage Get(string imageIDsList)
    {
        var imageIDs = imageIDsList.Split(',').Select(_ => int.Parse(_));
        var any = _dataContext.DeepZoomImages.Select(_ => _.ImageID).Where(_ => imageIDs.Contains(_)).Any();
        if (!any)
        {
            throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
        }
        var dzImages = _dataContext.DeepZoomImages.Where(_ => imageIDs.Contains(_.ImageID));
        var streamContent = new PushStreamContent((outputStream, httpContext, transportContent) =>
            {
                try
                {
                    using (var zipFile = new ZipFile())
                    {
                        foreach (var dzImage in dzImages)
                        {
                            var bitmap = GetFullSizeBitmap(dzImage);
                            var memoryStream = new MemoryStream();
                            bitmap.Save(memoryStream, ImageFormat.Jpeg);
                            memoryStream.Position = 0;
                            var fileName = string.Format("{0}.jpg", dzImage.ImageName);
                            zipFile.AddEntry(fileName, memoryStream);
                        }
                        zipFile.Save(outputStream); //Null Reference Exception
                    }
                }
                finally
                {
                    outputStream.Close();
                }
            });
        streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/zip");
        streamContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
        {
            FileName = string.Format("{0}_images.zip", dzImages.Count()),
        };

        var response = new HttpResponseMessage(HttpStatusCode.OK)
            {
                Content = streamContent
            };

        return response;
    }

В идеале можно было бы сделать это еще более динамически созданным с помощью класса ZipOutputStream для динамического создания zip вместо использования ZipFile. В этом случае MemoryStream для каждого растрового изображения не понадобится.

Ответ 3

public HttpResponseMessage GetItemsInZip(int id)
    {           
            var itemsToWrite = // return array of objects based on id;

            // create zip file stream
            MemoryStream archiveStream = new MemoryStream();
            using (ZipArchive archiveFile = new ZipArchive(archiveStream, ZipArchiveMode.Create, true))
            {
                foreach (var item in itemsToWrite)
                {
                    // create file streams
                    // add the stream to zip file

                    var entry = archiveFile.CreateEntry(item.FileName);
                    using (StreamWriter sw = new StreamWriter(entry.Open()))
                    {
                        sw.Write(item.Content);
                    }
                }
            }

            // return the zip file stream to http response content                
            HttpResponseMessage responseMsg = new HttpResponseMessage(HttpStatusCode.OK);                
            responseMsg.Content = new ByteArrayContent(archiveStream.ToArray());
            archiveStream.Dispose();
            responseMsg.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = "test.zip" };
            responseMsg.Content.Headers.ContentType = new MediaTypeHeaderValue("application/zip");

            return responseMsg;          
    }

Используемая среда .NET 4.6.1 с MVC 5

Ответ 4

У меня была такая же проблема, как у вас.

zipFile.Save(outputStream); //I got Null Reference Exception here.

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

zip.AddEntry(fileName, ms);

Все, что вам нужно сделать, это изменить его на это:

zip.AddEntry(fileName, ms.ToArray());

Похоже, что когда автор решает записать файл и пытается прочитать поток, поток собирает мусор или sth...

Ура!