Question

I have a little service to upload blobs to Azure Storage. I am trying to use it from a WebApi async action, but my AzureFileStorageService says the stream is closed.

I am new to async/await, are there any good resources to help me better understand it?

WebApi Controller

public class ImageController : ApiController
{
    private IFileStorageService fileStorageService;

    public ImageController(IFileStorageService fileStorageService)
    {
        this.fileStorageService = fileStorageService;
    }

    public async Task<IHttpActionResult> Post()
    {
        if (!Request.Content.IsMimeMultipartContent())
        {
            throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.UnsupportedMediaType));
        }

        await Request.Content.ReadAsMultipartAsync(new MultipartMemoryStreamProvider()).ContinueWith((task) =>
        {

            foreach (var item in task.Result.Contents)
            {
                using (var fileStream = item.ReadAsStreamAsync().Result)
                {
                    fileStorageService.Save(@"large/Sam.jpg", fileStream);
                }

                item.Dispose();
            }

        });

        return Ok();
    }
}

AzureFileStorageService

public class AzureFileStorageService : IFileStorageService
{
    public async void Save(string path, Stream source)
    {
        await CloudStorageAccount.Parse(ConfigurationManager.AppSettings["StorageConnectionString"])
            .CreateCloudBlobClient()
            .GetContainerReference("images")
            .GetBlockBlobReference(path)
            .UploadFromStreamAsync(source); // source throws a stream is disposed exception
    }
}
Was it helpful?

Solution

You have a problem with your Save() method: you're not returning a Task, and so the calling method has no way to wait for it to finish. That would be fine if you just wanted to fire and forget it, but you can't do that because the stream you pass in is going to be disposed as soon as the Save() method returns (thanks to the using statement).

Instead, you're going to have to either return a Task and await in the calling method, or you're going to have to not have the file stream in a using block, and instead let the Save() method dispose of it when its finished.

One way you could re-write your code would be as follows:

(snippet of calling method):

    var result = await Request.Content.ReadAsMultipartAsync(new MultipartMemoryStreamProvider());
    foreach (var item in result.Contents)
    {
        using (var fileStream = await item.ReadAsStreamAsync())
        {
            await fileStorageService.Save(@"large/Sam.jpg", fileStream);
        }

        item.Dispose();
    }

And the Save method:

public async Task Save(string path, Stream source)
{
    await CloudStorageAccount.Parse(ConfigurationManager.AppSettings["StorageConnectionString"])
        .CreateCloudBlobClient()
        .GetContainerReference("images")
        .GetBlockBlobReference(path)
        .UploadFromStreamAsync(source);
}

OTHER TIPS

Checkout this AzureBlobUpload sample we just released a few weeks ago:

The previous answer is definitely a good fix. This is just a complete end to end official sample (perhaps for other folks to get started on).

https://aspnet.codeplex.com/SourceControl/latest#Samples/WebApi/AzureBlobsFileUploadSample/ReadMe.txt

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top