22

I need to write a method like below to return a text document (.txt, pdf, .doc, .docx etc) While there are good examples of posting file in Web API 2.0 on the web , I couldn't find a relevant one for just downloading one. (I know how to do it in HttpResponseMessage.)

  public async Task<IHttpActionResult> GetFileAsync(int FileId)
  {    
       //just returning file part (no other logic needed)
  }

Does the above needs to be async at all? I am only looking to return stream. (Is that okay?)

More importantly before I end up doing the job one way or the otther, I wanted to know what's the "right" way of doing this sort of job... (so approaches and techniques mentioning this would be greatly appreciated).. thanks.

Wiebe Tijsma
  • 10,173
  • 5
  • 52
  • 68
Kcats Wolfrevo
  • 803
  • 1
  • 11
  • 15

2 Answers2

40

Right, for your above scenario the action does not need to return an async action result. Here I am creating a custom IHttpActionResult. You can check my comments in the below code here.

public IHttpActionResult GetFileAsync(int fileId)
{
    // NOTE: If there was any other 'async' stuff here, then you would need to return
    // a Task<IHttpActionResult>, but for this simple case you need not.

    return new FileActionResult(fileId);
}

public class FileActionResult : IHttpActionResult
{
    public FileActionResult(int fileId)
    {
        this.FileId = fileId;
    }

    public int FileId { get; private set; }

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        HttpResponseMessage response = new HttpResponseMessage();
        response.Content = new StreamContent(File.OpenRead(@"<base path>" + FileId));
        response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");

        // NOTE: Here I am just setting the result on the Task and not really doing any async stuff. 
        // But let's say you do stuff like contacting a File hosting service to get the file, then you would do 'async' stuff here.

        return Task.FromResult(response);
    }
}
Shahin
  • 12,543
  • 39
  • 127
  • 205
Kiran
  • 56,921
  • 15
  • 176
  • 161
  • Thanks @Kiran, However I had already implemented a very similar approach (without async). I will accept your answer as I believe that this is the right answer for the given simple scenario. – Kcats Wolfrevo Feb 07 '14 at 14:12
  • @Kiran Challa, please do you think you could help me with this question : http://stackoverflow.com/questions/24235617/asp-net-web-api-what-problems-could-arise-if-i-use-post-instead-of-put-and-del/24235652#24235652 – eddy Jun 16 '14 at 02:36
  • 1
    @eddy: Sure, the answer to your question has a link which seems to address this...there it seems to mention `X-HTTP-Method-Override` header which you seem to be not using...but you can use a similar approach of using delegating handler to look for your custom header and modify your request...have you already tried this or do you have any concerns regarding this? – Kiran Jun 16 '14 at 03:04
  • @KiranChalla I found this http://www.hanselman.com/blog/HTTPPUTOrDELETENotAllowedUseXHTTPMethodOverrideForYourRESTServiceWithASPNETWebAPI.aspx , But I don't know where to add the MethodOverrideHandler class, because when I tried to register this new override handler `config.MessageHandlers.Add(new MethodOverrideHandler());` the class isn't recognized. I also tried to download it from NuGet, but I couldn't because I'm using netframework v 4.0 – eddy Jun 16 '14 at 03:41
  • @loop: i updated the above answer with information related to `ContentDisposition` header and this allow you to download the file in the browser. – Kiran Sep 08 '14 at 20:54
  • @KiranChalla It does download a file, but instead of returning a pdf file (which I extracted from a database), it returns a file called 1.htm. If I rename the 1.htm to 1.pdf, it is the actual pdf file that got downloaded. Any ideas why? – Thierry Apr 09 '21 at 15:52
3

Methods are asynchronous if return a Task object, not because are decorated with async keyword. async is only a syntactical sugar to replace this syntax what can get rather complex when there are more tasks combined or more continuations:

public Task<int> ExampleMethodAsync()
{
    var httpClient = new HttpClient();

    var task = httpClient.GetStringAsync("http://msdn.microsoft.com")
        .ContinueWith(previousTask =>
        {
            ResultsTextBox.Text += "Preparing to finish ExampleMethodAsync.\n";

            int exampleInt = previousTask.Result.Length;

            return exampleInt;
        });

    return task;
}

Original sample with async: http://msdn.microsoft.com/en-us/library/hh156513.aspx

async always requires await, this is enforced by compiler.

Both implementations are asynchroous, the only difference is that async+await replaces expands the ContinueWith into "synchronous" code.

Returning Task from controller methods what do IO (99% of cases I estimate) is important because the runtime can suspend and reuse the request thread to serve other requests while the IO operation is in progress. This lowers the chances of running out of thread pool threads. Here's an article on the topic: http://www.asp.net/mvc/overview/performance/using-asynchronous-methods-in-aspnet-mvc-4

So the answer to your question "Does the above needs to be async at all? I am only looking to return stream. (Is that okay?)" is that it makes no difference to the caller, it only changes how your code looks (but not how it works).

user3285954
  • 4,499
  • 2
  • 27
  • 19