Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVC 6 WebAPI returns serialized HttpResponseMessage instead of file stream

I'm trying to make my ASP.Net 5 MVC 6 WebAPI project output a file, in response to a HttpGET request.

The file is from an Azure Files share, but it could be any stream containing a binary file.

It seems to me that MVC serializes the response object, and returns the resulting JSON, rather than returning the response object itself.

Here is my controller method:

    [HttpGet]
    [Route("GetFile")]
    public HttpResponseMessage GetFile(string Username, string Password, string FullName)
    {

        var client = new AzureFilesClient.AzureFilesClient(Username, Password);
        Stream azureFileStream =  client.GetFileStream(FullName).Result;
        var fileName = Path.GetFileName(FullName);

        using (HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK))
        {
            response.Content = new StreamContent(azureFileStream);
            response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
            response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = fileName };
            return response;
        }
    }

The GetFileStream method on the AzureFilesClient is here, though the stream source could be anything containing binary file content:

    public async Task<Stream> GetFileStream(string fileName)
    {
        var uri = new Uri(share.Uri + "/" + fileName);
        var file = new CloudFile(uri, credentials);

        using (var stream = new MemoryStream())
        {
            await file.DownloadToStreamAsync(stream);
            stream.Seek(0, SeekOrigin.Begin);
            return stream;
        }
    }

Edit: Here is a sample of the JSON response:

{
    "Version": {
        "Major": 1,
        "Minor": 1,
        "Build": -1,
        "Revision": -1,
        "MajorRevision": -1,
        "MinorRevision": -1
    },
    "Content": {
        "Headers": [
            {
                "Key": "Content-Type",
                "Value": [
                    "application/octet-stream"
                ]
            },
            {
                "Key": "Content-Disposition",
                "Value": [
                    "attachmentx; filename=\"samplefile.docx\""
                ]
            }
        ]
    },
    "StatusCode": 200,
    "ReasonPhrase": "OK",
    "Headers": [],
    "RequestMessage": null,
    "IsSuccessStatusCode": true
}
like image 213
Gertsen Avatar asked May 14 '15 14:05

Gertsen


People also ask

What does HttpResponseMessage return?

A HttpResponseMessage allows us to work with the HTTP protocol (for example, with the headers property) and unifies our return type. In simple words an HttpResponseMessage is a way of returning a message/data from your action.


1 Answers

After a combination of reading documentation aswell as some trial and error, the problems have been solved.

The Azure part was made using the nuGet package "WindowsAzure.Storage" (4.4.1-preview)

First the output that got JSON serialized. That required a custom action result to be returned instead.

using Microsoft.AspNet.Mvc;
using System.IO;
using System.Threading.Tasks;

public class FileResultFromStream : ActionResult
{
    public FileResultFromStream(string fileDownloadName, Stream fileStream, string contentType)
    {
        FileDownloadName = fileDownloadName;
        FileStream = fileStream;
        ContentType = contentType;
    }

    public string ContentType { get; private set; }
    public string FileDownloadName { get; private set; }
    public Stream FileStream { get; private set; }

    public async override Task ExecuteResultAsync(ActionContext context)
    {
        var response = context.HttpContext.Response;
        response.ContentType = ContentType;
        context.HttpContext.Response.Headers.Add("Content-Disposition", new[] { "attachment; filename=" + FileDownloadName });
        await FileStream.CopyToAsync(context.HttpContext.Response.Body);
    }
}

Now for getting the binary data streamed from an Azure files share (or any other async stream source)

using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Auth;
using Microsoft.WindowsAzure.Storage.File;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;

    public async Task<Stream> GetFileStreamAsync(string fileName)
    {
        var uri = new Uri(share.Uri + "/" + fileName);
        var file = new CloudFile(uri, credentials);

        // Note: Do not wrap the stream variable in a Using, since it will close the stream too soon.
        var stream = new MemoryStream();
        await file.DownloadToStreamAsync(stream);
        stream.Seek(0, SeekOrigin.Begin);
        return stream;
    }

And finally the controller code. Note the use of the IActionResult interface.

    [HttpGet]
    [Route("GetFile")]
    public async Task<IActionResult> GetFile(string username, string password, string fullName)
    {
        var client = new AzureFilesClient.AzureFilesClient(username, password);
        Stream stream = await client.GetFileStreamAsync(fullName);
        string fileName = Path.GetFileName(fullName);
        return new CustomActionResults.FileResultFromStream(fileName, stream, "application/msword");
    }

Note: This example is only used with Word files, you might want to look into making the ContentType parameter dynamic, instead of static like this.

like image 186
Gertsen Avatar answered Nov 19 '22 02:11

Gertsen