¿Puede un controlador ASP.NET MVC devolver una imagen?


455

¿Puedo crear un controlador que simplemente devuelva un activo de imagen?

Me gustaría enrutar esta lógica a través de un controlador, siempre que se solicite una URL como la siguiente:

www.mywebsite.com/resource/image/topbanner

El controlador buscará topbanner.pngy enviará esa imagen directamente al cliente.

He visto ejemplos de esto en los que tienes que crear una Vista. No quiero usar una Vista. Quiero hacerlo todo solo con el controlador.

es posible?


1
Hice una pregunta similar aquí /programming/155906/creating-a-private-photo-gallery-using-aspnet-mvc y terminé encontrando una gran guía para hacer esto. Creé una clase ImageResult siguiendo esta guía. https://blog.maartenballiauw.be/post/2008/05/13/aspnet-mvc-custom-actionresult.html
Vyrotek

2
Si desea modificar la imagen, use ImageResizing.Net HttpModule para obtener el mejor rendimiento. Si no lo hace, un FilePathResult agrega solo un pequeño porcentaje de la sobrecarga. La reescritura de URL agrega un poco menos.
Lilith River

1
¿Por qué no usar el controlador WebApi en lugar de MVC? ApiController class
A-Sharabiani

Respuestas:


534

Utilice el método de archivo de controladores base.

public ActionResult Image(string id)
{
    var dir = Server.MapPath("/Images");
    var path = Path.Combine(dir, id + ".jpg"); //validate the path for security or use other means to generate the path.
    return base.File(path, "image/jpeg");
}

Como nota, esto parece ser bastante eficiente. Hice una prueba donde solicité la imagen a través del controlador ( http://localhost/MyController/Image/MyImage) y a través de la URL directa ( http://localhost/Images/MyImage.jpg) y los resultados fueron:

  • MVC: 7.6 milisegundos por foto
  • Directo: 6.7 milisegundos por foto

Nota: este es el tiempo promedio de una solicitud. El promedio se calculó haciendo miles de solicitudes en la máquina local, por lo que los totales no deben incluir problemas de latencia de red o ancho de banda.


10
Para aquellos que están llegando a esta pregunta ahora, esta fue la solución que mejor funcionó para mí.
Clarence Klopfstein

177
Este no es un código seguro. Permitir que el usuario pase un nombre de archivo (ruta) como este significa que podría acceder a los archivos desde cualquier lugar del servidor. Puede querer advertir a la gente que no lo use como está.
Ian Mercer

77
A menos que esté construyendo los archivos sobre la marcha, ya que son necesarios y almacenarlos en caché una vez que se crean (eso es lo que hacemos).
Brian

15
@ mare- también puede hacer esto si está sirviendo archivos desde una ubicación restringida, por ejemplo, puede tener imágenes App_Dataque deberían ser firmadas por algunos usuarios de su aplicación pero no por otros. El uso de una acción de controlador para servirlos le permite restringir el acceso.
Russ Cam

8
Como otros han mencionado, tenga cuidado en la construcción de su ruta, ya que he visto el código de producción real que permitió al usuario navegar por un directorio con POST o cadena de consulta cuidadosamente construida: /../../../danger/someFileTheyTHoughtWasInaccessible
AaronLS

128

Usando la versión de lanzamiento de MVC, esto es lo que hago:

[AcceptVerbs(HttpVerbs.Get)]
[OutputCache(CacheProfile = "CustomerImages")]
public FileResult Show(int customerId, string imageName)
{
    var path = string.Concat(ConfigData.ImagesDirectory, customerId, "\\", imageName);
    return new FileStreamResult(new FileStream(path, FileMode.Open), "image/jpeg");
}

Obviamente tengo algunas cosas específicas de la aplicación aquí con respecto a la construcción de la ruta, pero la devolución de FileStreamResult es agradable y simple.

Hice algunas pruebas de rendimiento con respecto a esta acción contra su llamada diaria a la imagen (sin pasar por el controlador) y la diferencia entre los promedios fue de solo 3 milisegundos (el promedio del controlador fue de 68 ms, el no controlador fue de 65 ms).

Probé algunos de los otros métodos mencionados en las respuestas aquí y el rendimiento fue mucho más dramático ... varias de las respuestas de las soluciones fueron hasta 6 veces el no controlador (otros controladores prom. 340ms, no controlador 65ms).


12
¿Qué pasa con la imagen no se modifica? FileStreamResult debe enviar 304 cuando la imagen no se modifica desde la última solicitud.
dariol

Puede usar en Path.Combinelugar del concat para obtener un código más seguro y más legible.
Marcell Toth el

101

Para explicar ligeramente la respuesta de Dyland:

Tres clases implementan la clase FileResult :

System.Web.Mvc.FileResult
      System.Web.Mvc.FileContentResult
      System.Web.Mvc.FilePathResult
      System.Web.Mvc.FileStreamResult

Todos son bastante explicativos:

  • Para las descargas de ruta de archivo donde el archivo existe en el disco, use FilePathResult: esta es la forma más fácil y evita tener que usar Streams.
  • Para las matrices de bytes [] (similar a Response.BinaryWrite), use FileContentResult.
  • Para las matrices de bytes [] donde desea que se descargue el archivo (disposición de contenido: archivo adjunto), use FileStreamResultde la misma manera que a continuación, pero con ay MemoryStreamusando GetBuffer().
  • Para Streamssu uso FileStreamResult. Se llama FileStreamResult pero toma un, Streamasí que supongo que funciona con un MemoryStream.

A continuación se muestra un ejemplo del uso de la técnica de disposición de contenido (no probado):

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult GetFile()
    {
        // No need to dispose the stream, MVC does it for you
        string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "App_Data", "myimage.png");
        FileStream stream = new FileStream(path, FileMode.Open);
        FileStreamResult result = new FileStreamResult(stream, "image/png");
        result.FileDownloadName = "image.png";
        return result;
    }

2
La parte de disposición de contenido de esta publicación fue extremadamente útil
Diego

VS me dice que esta sobrecarga de FileStream () es obsoleta.
MrBoJangles

1
Algo a tener en cuenta: si tiene una coma en su nombre de archivo, Chrome lo rechazará con un error de "se recibieron demasiados encabezados". Por lo tanto, reemplace todas las comas con un "-" o "".
Chris S

¿Cómo se podría hacer esto usando solo controladores de API web?
Zapnologica

74

Esto puede ser útil si desea modificar la imagen antes de devolverla:

public ActionResult GetModifiedImage()
{
    Image image = Image.FromFile(Path.Combine(Server.MapPath("/Content/images"), "image.png"));

    using (Graphics g = Graphics.FromImage(image))
    {
        // do something with the Graphics (eg. write "Hello World!")
        string text = "Hello World!";

        // Create font and brush.
        Font drawFont = new Font("Arial", 10);
        SolidBrush drawBrush = new SolidBrush(Color.Black);

        // Create point for upper-left corner of drawing.
        PointF stringPoint = new PointF(0, 0);

        g.DrawString(text, drawFont, drawBrush, stringPoint);
    }

    MemoryStream ms = new MemoryStream();

    image.Save(ms, System.Drawing.Imaging.ImageFormat.Png);

    return File(ms.ToArray(), "image/png");
}

1
Gracias. Esto es perfecto para el escenario donde se necesita un proxy para descargar una imagen que requiere autenticación que no se puede hacer en el lado del cliente.
Hong

1
Se está olvidando de deshacerse de la friolera de 3 objetos nativos: Font, SolidBrush e Image.
Wout

3
Mejora sugerida aquí: crea un flujo de memoria, escribe los datos y luego crea un resultado de archivo con los datos usando .ToArray () También puede simplemente llamar a ms.Seek (0, SeekOrigin.Begin) y luego devolver Archivo (ms, " image / png ") // devuelve la transmisión en sí misma
Quango

12

Puede crear su propia extensión y hacerlo de esta manera.

public static class ImageResultHelper
{
    public static string Image<T>(this HtmlHelper helper, Expression<Action<T>> action, int width, int height)
            where T : Controller
    {
        return ImageResultHelper.Image<T>(helper, action, width, height, "");
    }

    public static string Image<T>(this HtmlHelper helper, Expression<Action<T>> action, int width, int height, string alt)
            where T : Controller
    {
        var expression = action.Body as MethodCallExpression;
        string actionMethodName = string.Empty;
        if (expression != null)
        {
            actionMethodName = expression.Method.Name;
        }
        string url = new UrlHelper(helper.ViewContext.RequestContext, helper.RouteCollection).Action(actionMethodName, typeof(T).Name.Remove(typeof(T).Name.IndexOf("Controller"))).ToString();         
        //string url = LinkBuilder.BuildUrlFromExpression<T>(helper.ViewContext.RequestContext, helper.RouteCollection, action);
        return string.Format("<img src=\"{0}\" width=\"{1}\" height=\"{2}\" alt=\"{3}\" />", url, width, height, alt);
    }
}

public class ImageResult : ActionResult
{
    public ImageResult() { }

    public Image Image { get; set; }
    public ImageFormat ImageFormat { get; set; }

    public override void ExecuteResult(ControllerContext context)
    {
        // verify properties 
        if (Image == null)
        {
            throw new ArgumentNullException("Image");
        }
        if (ImageFormat == null)
        {
            throw new ArgumentNullException("ImageFormat");
        }

        // output 
        context.HttpContext.Response.Clear();
        context.HttpContext.Response.ContentType = GetMimeType(ImageFormat);
        Image.Save(context.HttpContext.Response.OutputStream, ImageFormat);
    }

    private static string GetMimeType(ImageFormat imageFormat)
    {
        ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
        return codecs.First(codec => codec.FormatID == imageFormat.Guid).MimeType;
    }
}
public ActionResult Index()
    {
  return new ImageResult { Image = image, ImageFormat = ImageFormat.Jpeg };
    }
    <%=Html.Image<CapchaController>(c => c.Index(), 120, 30, "Current time")%>

10

Puede escribir directamente a la respuesta pero luego no es comprobable. Se prefiere devolver un ActionResult que haya diferido la ejecución. Aquí está mi StreamResult reutilizable:

public class StreamResult : ViewResult
{
    public Stream Stream { get; set; }
    public string ContentType { get; set; }
    public string ETag { get; set; }

    public override void ExecuteResult(ControllerContext context)
    {
        context.HttpContext.Response.ContentType = ContentType;
        if (ETag != null) context.HttpContext.Response.AddHeader("ETag", ETag);
        const int size = 4096;
        byte[] bytes = new byte[size];
        int numBytes;
        while ((numBytes = Stream.Read(bytes, 0, size)) > 0)
            context.HttpContext.Response.OutputStream.Write(bytes, 0, numBytes);
    }
}

9

¿Por qué no ser simple y usar el ~operador tilde ?

public FileResult TopBanner() {
  return File("~/Content/images/topbanner.png", "image/png");
}

6

ACTUALIZACIÓN: hay mejores opciones que mi respuesta original. Esto funciona bastante bien fuera de MVC, pero es mejor seguir con los métodos integrados para devolver el contenido de la imagen. Ver respuestas votadas.

Ciertamente puedes. Pruebe estos pasos:

  1. Cargue la imagen del disco en una matriz de bytes
  2. guarde en caché la imagen en caso de que espere más solicitudes para la imagen y no quiera la E / S del disco (mi muestra no la almacena en caché a continuación)
  3. Cambie el tipo mime a través de Response.ContentType
  4. Response.BinaryEscribe la matriz de bytes de imagen

Aquí hay un código de muestra:

string pathToFile = @"C:\Documents and Settings\some_path.jpg";
byte[] imageData = File.ReadAllBytes(pathToFile);
Response.ContentType = "image/jpg";
Response.BinaryWrite(imageData);

¡Espero que ayude!


44
¿Y cómo se vería esto en la acción del controlador?
CRice

5

Solución 1: para representar una imagen en una vista desde una URL de imagen

Puede crear su propio método de extensión:

public static MvcHtmlString Image(this HtmlHelper helper,string imageUrl)
{
   string tag = "<img src='{0}'/>";
   tag = string.Format(tag,imageUrl);
   return MvcHtmlString.Create(tag);
}

Luego úsalo como:

@Html.Image(@Model.ImagePath);

Solución 2: para representar la imagen de la base de datos

Cree un método de controlador que devuelva datos de imagen como a continuación

public sealed class ImageController : Controller
{
  public ActionResult View(string id)
  {
    var image = _images.LoadImage(id); //Pull image from the database.
    if (image == null) 
      return HttpNotFound();
    return File(image.Data, image.Mime);
  }
}

Y úsalo en una vista como:

@ { Html.RenderAction("View","Image",new {id=@Model.ImageId})}

Para usar una imagen renderizada de este resultado de acción en cualquier HTML, use

<img src="http://something.com/image/view?id={imageid}>

5

Esto funcionó para mí. Como estoy almacenando imágenes en una base de datos de SQL Server.

    [HttpGet("/image/{uuid}")]
    public IActionResult GetImageFile(string uuid) {
        ActionResult actionResult = new NotFoundResult();
        var fileImage = _db.ImageFiles.Find(uuid);
        if (fileImage != null) {
            actionResult = new FileContentResult(fileImage.Data,
                fileImage.ContentType);
        }
        return actionResult;
    }

En el fragmento anterior _db.ImageFiles.Find(uuid)está buscando el registro del archivo de imagen en el db (contexto EF). Devuelve un objeto FileImage que es solo una clase personalizada que hice para el modelo y luego lo usa como FileContentResult.

public class FileImage {
   public string Uuid { get; set; }
   public byte[] Data { get; set; }
   public string ContentType { get; set; }
}

4

puede usar Archivo para devolver un archivo como Ver, Contenido, etc.

 public ActionResult PrintDocInfo(string Attachment)
            {
                string test = Attachment;
                if (test != string.Empty || test != "" || test != null)
                {
                    string filename = Attachment.Split('\\').Last();
                    string filepath = Attachment;
                    byte[] filedata = System.IO.File.ReadAllBytes(Attachment);
                    string contentType = MimeMapping.GetMimeMapping(Attachment);

                    System.Net.Mime.ContentDisposition cd = new System.Net.Mime.ContentDisposition
                    {
                        FileName = filename,
                        Inline = true,
                    };

                    Response.AppendHeader("Content-Disposition", cd.ToString());

                    return File(filedata, contentType);          
                }
                else { return Content("<h3> Patient Clinical Document Not Uploaded</h3>"); }

            }

3

Mira ContentResult. Esto devuelve una cadena, pero puede usarse para crear su propia clase tipo BinaryResult.


2
if (!System.IO.File.Exists(filePath))
    return SomeHelper.EmptyImageResult(); // preventing JSON GET/POST exception
else
    return new FilePathResult(filePath, contentType);

SomeHelper.EmptyImageResult()debería volver FileResultcon la imagen existente (1x1 transparente, por ejemplo).

Esta es la forma más fácil si tiene archivos almacenados en la unidad local. Si los archivos son byte[]o stream, entonces use FileContentResulto FileStreamResultcomo lo sugirió Dylan.


1

Veo dos opciones:

1) Implemente su propio IViewEngine y configure la propiedad ViewEngine del Controlador que está utilizando en su ImageViewEngine en el método de "imagen" que desee.

2) Use una vista :-). Simplemente cambie el tipo de contenido, etc.


1
Esto podría ser un problema debido a espacios adicionales o CRLF en la Vista.
Elan Hasson

2
Me equivoqué en mi última publicación ... msdn.microsoft.com/en-us/library/… Puedes usar la clase WebImage y WebImage.Escribir en una vista :)
Elan Hasson

1

Puede usar HttpContext.Response y escribir directamente el contenido en él (WriteFile () podría funcionar para usted) y luego devolver ContentResult de su acción en lugar de ActionResult.

Descargo de responsabilidad: no he intentado esto, se basa en mirar las API disponibles. :-)


1
Sí, acabo de notar que ContentResult solo admite cadenas, pero es bastante fácil hacer tu propia clase basada en ActionResult.
leppie

1

El siguiente código se utiliza System.Drawing.Bitmappara cargar la imagen.

using System.Drawing;
using System.Drawing.Imaging;

public IActionResult Get()
{
    string filename = "Image/test.jpg";
    var bitmap = new Bitmap(filename);

    var ms = new System.IO.MemoryStream();
    result.Save(ms, ImageFormat.Jpeg);
    ms.Position = 0;
    return new FileStreamResult(ms, "image/jpeg");
}

0

También encontré requisitos similares,

Entonces, en mi caso, solicito al controlador la ruta de la carpeta de imágenes, que a cambio envía un objeto ImageResult.

El siguiente fragmento de código ilustra el trabajo:

var src = string.Format("/GenericGrid.mvc/DocumentPreviewImageLink?fullpath={0}&routingId={1}&siteCode={2}", fullFilePath, metaInfo.RoutingId, da.SiteCode);

                if (enlarged)
                    result = "<a class='thumbnail' href='#thumb'>" +
                        "<img src='" + src + "' height='66px' border='0' />" +
                        "<span><img src='" + src + "' /></span>" +
                        "</a>";
                else
                    result = "<span><img src='" + src + "' height='150px' border='0' /></span>";

Y en el controlador desde la ruta de la imagen, produzco la imagen y la devuelvo a la persona que llama

try
{
  var file = new FileInfo(fullpath);
  if (!file.Exists)
     return string.Empty;


  var image = new WebImage(fullpath);
  return new ImageResult(new MemoryStream(image.GetBytes()), "image/jpg");


}
catch(Exception ex)
{
  return "File Error : "+ex.ToString();
}

0

Lea la imagen, conviértala y byte[]luego devuelva un File()con un tipo de contenido.

public ActionResult ImageResult(Image image, ImageFormat format, string contentType) {
  using (var stream = new MemoryStream())
    {
      image.Save(stream, format);
      return File(stream.ToArray(), contentType);
    }
  }
}

Aquí están los usos:

using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using Microsoft.AspNetCore.Mvc;
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.