24

Nuestra aplicación ASP.NET MVC 3 se ejecuta en Azure y utiliza Blob como almacenamiento de archivos. Tengo la parte de carga resuelta.Descarga de archivos Azure Blob en MVC3

La Vista tendrá el Nombre de archivo, que, al hacer clic, solicitará que aparezca la pantalla de descarga del archivo.

¿Alguien puede decirme cómo hacer esto?

Respuesta

52

Dos opciones realmente ... la primera es simplemente redirigir al usuario al blob directamente (si los blobs están en un contenedor público). Eso sería mirar un poco como:

return Redirect(container.GetBlobReference(name).Uri.AbsoluteUri); 

Si la mancha está en un recipiente privada, que podría o bien utilizar una firma de acceso compartida y ya redirección al igual que el ejemplo anterior, o se puede leer la mancha en su acción del controlador y empujar hacia abajo al cliente como una descarga:

Response.AddHeader("Content-Disposition", "attachment; filename=" + name); // force download 
container.GetBlobReference(name).DownloadToStream(Response.OutputStream); 
return new EmptyResult(); 
+0

Es un blob privado, así que utilicé el segundo método que publicaste y funcionó exactamente como yo quería. ¡Muchas gracias! – James

+0

Quiero ocultar el nombre de archivo del usuario (y poner el mío) ¿sabes cómo hacerlo? – James

+2

Simplemente ponga lo que desee en el encabezado de disposición de contenido. – smarx

9

Aquí hay una versión resumable (útil para archivos de gran tamaño o permitir que buscar en la reproducción de vídeo o audio) de acceso burbuja privada:

public class AzureBlobStream : ActionResult 
{ 
    private string filename, containerName; 

    public AzureBlobStream(string containerName, string filename) 
    { 
     this.containerName = containerName; 
     this.filename = filename; 
     this.contentType = contentType; 
    } 

    public override void ExecuteResult(ControllerContext context) 
    { 
     var response = context.HttpContext.Response; 
     var request = context.HttpContext.Request; 

     var connectionString = ConfigurationManager.ConnectionStrings["Storage"].ConnectionString; 
     var account = CloudStorageAccount.Parse(connectionString); 
     var client = account.CreateCloudBlobClient(); 
     var container = client.GetContainerReference(containerName); 
     var blob = container.GetBlockBlobReference(filename); 

     blob.FetchAttributes(); 
     var fileLength = blob.Properties.Length; 
     var fileExists = fileLength > 0; 
     var etag = blob.Properties.ETag; 

     var responseLength = fileLength; 
     var buffer = new byte[4096]; 
     var startIndex = 0; 

     //if the "If-Match" exists and is different to etag (or is equal to any "*" with no resource) then return 412 precondition failed 
     if (request.Headers["If-Match"] == "*" && !fileExists || 
      request.Headers["If-Match"] != null && request.Headers["If-Match"] != "*" && request.Headers["If-Match"] != etag) 
     { 
      response.StatusCode = (int)HttpStatusCode.PreconditionFailed; 
      return; 
     } 

     if (!fileExists) 
     { 
      response.StatusCode = (int)HttpStatusCode.NotFound; 
      return; 
     } 

     if (request.Headers["If-None-Match"] == etag) 
     { 
      response.StatusCode = (int)HttpStatusCode.NotModified; 
      return; 
     } 

     if (request.Headers["Range"] != null && (request.Headers["If-Range"] == null || request.Headers["IF-Range"] == etag)) 
     { 
      var match = Regex.Match(request.Headers["Range"], @"bytes=(\d*)-(\d*)"); 
      startIndex = Util.Parse<int>(match.Groups[1].Value); 
      responseLength = (Util.Parse<int?>(match.Groups[2].Value) + 1 ?? fileLength) - startIndex; 
      response.StatusCode = (int)HttpStatusCode.PartialContent; 
      response.Headers["Content-Range"] = "bytes " + startIndex + "-" + (startIndex + responseLength - 1) + "/" + fileLength; 
     } 

     response.Headers["Accept-Ranges"] = "bytes"; 
     response.Headers["Content-Length"] = responseLength.ToString(); 
     response.Cache.SetCacheability(HttpCacheability.Public); //required for etag output 
     response.Cache.SetETag(etag); //required for IE9 resumable downloads 
     response.ContentType = blob.Properties.ContentType; 

     blob.DownloadRangeToStream(response.OutputStream, startIndex, responseLength); 
    } 
} 

Ejemplo:

Response.AddHeader("Content-Disposition", "attachment; filename=" + filename); // force download 
return new AzureBlobStream(blobContainerName, filename); 
+0

¿Alguna idea de cómo forzar el encabezado 'Cache-Control' del resultado para que sea el mismo que el de blob? – dlras2

+0

¿Qué es 'Util.Parse'? –

8

me di cuenta de que la escritura a la secuencia de respuesta del método de acción mete hasta las cabeceras HTTP. Algunos encabezados esperados faltan y otros no están configurados correctamente.

Así que, en lugar de escribir en el flujo de respuesta, obtengo el contenido del blob como una secuencia y lo paso al método Controller.File().

CloudBlockBlob blob = container.GetBlockBlobReference(blobName); 
Stream blobStream = blob.OpenRead(); 
return File(blobStream, blob.Properties.ContentType, "FileName.txt"); 
Cuestiones relacionadas