1

My question/challenge is very much akin to a couple of different Stack Overflow postings, but I've read them in detail and am still struggling to get even the most basic application to work. Specifically, I believe "Returning binary file from controller in ASP.NET Web API" and "How to return a file (FileContentResult) in ASP.NET WebAPI" are both very close to what I'm trying to achieve.

To give you context, I'm trying to write/stub a simple Firmware OTA (over the air) server. My initial goal is seemingly very simple, but I've spent a lot of time with almost zero progress. I want to provide a URL that can be hit from a browser (and ultimately some firmware running on an IoT device) that will take in some parameters (either via URL parameters or via the header). The call should return a file as the body of the response if a firmware update is available, or a suitable HTTP Reponse code (or just an empty message body) if no updated is currently available.

I'm not very experienced in Visual Studio for Website applications, but plenty comfortable in C# and it seemed like a good environment to get this running quickly. I am open to other implementations, but I thought writing a controller would be the simplest way. I am learning that it seems this Web API is primarily intended for .Net to .Net communication, so if there's a better platform, I'm happy to be pointed in a better direction. Here's what I put together (heavily leveraging links above, so no credit due to me!):

namespace OverTheAirApi.Controllers
{
[Route("api/[controller]")]
public class OTAController : ApiController
{
    [HttpGet]
    public HttpResponseMessage Download(string name)
    {
        string fileName = "hello-world.bin";
        string filePath = "C:\\Devel\\Code\\...\\hello_world\\build\\" + fileName;

        HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
        var stream = new FileStream(filePath, FileMode.Open);
        result.Content = new StreamContent(stream);
        result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
        result.Content.Headers.ContentDisposition.FileName = Path.GetFileName(filePath);
        result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
        result.Content.Headers.ContentLength = stream.Length;
        return result;

    }
}

}

When I type in the URL http://localhost:2265/api/ota, I see the following raw output on screen:

{"version":{"major":1,"minor":1,"build":-1,"revision":-1,"majorRevision":-1,"minorRevision":-1},"content":{"headers":[{"key":"Content-Disposition","value":["attachment; filename=hello-world.bin"]},{"key":"Content-Type","value":["application/octet-stream"]},{"key":"Content-Length","value":["407904"]}]},"statusCode":200,"reasonPhrase":"OK","headers":[],"requestMessage":null,"isSuccessStatusCode":true}

The entire response, captured in Fiddler2 looks like this:

HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Server: Kestrel
X-SourceFiles: =?UTF-8?B? YzpcdXNlcnNcZG91Z2NcZG9jdW1lbnRzXHZpc3VhbCBzdHVkaW8gMjAxNVxQcm9qZWN0c1xPdmVyVGhl QWlyQXBpXHNyY1xPdmVyVGhlQWlyQXBpXGFwaVxvdGE=?=
X-Powered-By: ASP.NET
Date: Tue, 28 Feb 2017 16:43:01 GMT
192
{"version": {"major":1,"minor":1,"build":-1,"revision":-1,"majorRevision":-1,"minorRevision":-1},"content":{"headers":[{"key":"Content-Disposition","value":["attachment; filename=hello-world.bin"]},{"key":"Content-Type","value":["application/octet-stream"]},{"key":"Content-Length","value":["407904"]}]},"statusCode":200,"reasonPhrase":"OK","headers":[],"requestMessage":null,"isSuccessStatusCode":true}
0

(Sorry if that formatting is a bit ugly, I guess I need some tutoring on using MD for HTTP Responses as well!)

Once again, my goal is to write C# code that will return back an HTTP response where the header indicates that the file is an attachment and where the body of the message is just the file itself.

Thank you again for any assistance/insight you might be able to provide!

Thank You - doug.

Community
  • 1
  • 1
  • Just to provide clarity, the problem I was facing had to do with the way in which I created the project. I created a mess my my VS project by mixing ASP.Net Web API 2 and the ASP.Net Core references and assemblies. With a clean project that was built using [this guide](https://learn.microsoft.com/en-us/aspnet/web-api/overview/getting-started-with-aspnet-web-api/tutorial-your-first-web-api), I was able to get this code working without issue. Thanks @ron-c for your help! – Doug Conyers Mar 01 '17 at 13:50
  • One more detail... This does NOT work if you create an ASP.Net Core 1.0.1 Project. Instead, it returns the XML as is detailed above. In the end, the solution to my problem was to create an ASP.NET Web API 2 Project and NOT to use the ASP.NET Core... – Doug Conyers Mar 01 '17 at 14:23

1 Answers1

1

Here's my standard method for sending a file to the browser. The method takes two parameters, one the contents of the file and one the default filename that the client will receive. Enjoy.

    /// <summary>
    /// Sends the fileData to the browser and prompts the visitor to save it with the 
    /// name specified in fileName.
    /// </summary>
    /// <param name="defaultFilename">File name to use when browser prompts visitor (spaces will be replaced with dashes)</param>
    /// <param name="data">Data to be sent to visitor's browser</param>
    /// <param name="errorMsg"></param>
    // Mod 08/04/09 Ron C - Reworked code to work right in modern browsers.
    public static bool DownloadToBrowser(string data, string defaultFilename, out string errorMsg){
        errorMsg = "";
        System.Web.HttpResponse response = HttpContext.Current.Response;

        try {
            defaultFilename = defaultFilename.Replace(' ', '-');        //firefox will cut the name off at the space if there is one, so get rid of 'em
            response.Clear();
            response.ContentType = "application/octet-stream";
            response.AddHeader("Content-Disposition", "attachment; filename=" + defaultFilename);
            //8/5/09 Adding a "Content-Length" header was cutting off part of my file.  Apparently
            //       it would need to factor in the length of the headers as well as the conent.
            //       since I have no easy way to figure out the length of the headers, initially I was gonna
            //       eliminate "Content-Length" header.  Doing so works great in IE 7 and FireFox 3, but not 
            //       in Safari 3 or 4(which doesn't download the file without the "Content-Length" header.  So I
            //       resorted to a cludge of using the header tag and setting it to content length + 1000 for headers.
            //       I'd love to have a better solution, but his one works and I've already wasted 6+ hours to get to this
            //       solution.  Cross broswer downloading of a file shouldn't have to be so hard.  -Ron
            int len = data.Length + 1000;
            response.AddHeader("Content-Length", len.ToString()); //file size for progress dialog
            response.Write(data);
            response.Flush();
            response.Close();       //Close() needed to prevent html from page being streamed back after the file data we sent.

            //Don't use response.End() cause it throws a thread abort exception that can't be caught! Actually you can
            //catch it but then it rethrows after the catch block! (What bozo thought that up?).  I found lots of threads on this.


        } catch (Exception ex) {
            errorMsg = "Unable to download file to browser.";
            //Add code here to log the error in your environment
            return false;
        }
        return true;
    }
RonC
  • 31,330
  • 19
  • 94
  • 139
  • Mr. Ron - Thanks for your response. I appreciate the help! Are you recommending that I use this in conjunction with the Web Api call that I implemented above? If not, how should this be connect to an HTTP GET command? The reason I'm asking is because my initial attempt at using your code was unsuccessful in two ways. One is that I wasn't sure what to return from the Web Api command and the second was that the System.Web.HttpContext.Current was set to null. Any additional help would be most appreciated! Thank YOu and have a great day! – Doug Conyers Feb 28 '17 at 23:14
  • I used this code from a web forms .aspx page by just calling this method and passing the parameters. But it should work from a web API end point as well, after calling this method from your action method you just need to return a status code 200 and no additional content. – RonC Mar 01 '17 at 00:28