Un controller ASP.NET MVC può restituire un'immagine?


455

Posso creare un controller che restituisce semplicemente una risorsa immagine?

Vorrei instradare questa logica attraverso un controller, ogni volta che viene richiesto un URL come il seguente:

www.mywebsite.com/resource/image/topbanner

Il controller cercherà topbanner.pnge invierà l'immagine direttamente al client.

Ho visto esempi di ciò in cui devi creare una vista: non voglio usare una vista. Voglio fare tutto con il solo controller.

È possibile?


1
Ho fatto una domanda simile qui /programming/155906/creating-a-private-photo-gallery-using-aspnet-mvc e alla fine ho trovato un'ottima guida per farlo. Ho creato una classe ImageResult seguendo questa guida. https://blog.maartenballiauw.be/post/2008/05/13/aspnet-mvc-custom-actionresult.html
Vyrotek,

2
Se si desidera modificare l'immagine, utilizzare il modulo Http ImageResizing.Net per le migliori prestazioni. In caso contrario, un FilePathResult aggiunge solo una percentuale del sovraccarico. La riscrittura degli URL aggiunge un po 'meno.
Lilith River,

1
Perché non utilizzare WebApi Controller anziché MVC? ApiController class
A-Sharabiani,

Risposte:


534

Utilizzare il metodo File dei controller di 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");
}

Come nota, questo sembra essere abbastanza efficiente. Ho fatto un test in cui ho richiesto l'immagine tramite il controller ( http://localhost/MyController/Image/MyImage) e tramite l'URL diretto ( http://localhost/Images/MyImage.jpg) e i risultati sono stati:

  • MVC: 7,6 millisecondi per foto
  • Diretto: 6,7 millisecondi per foto

Nota: questo è il tempo medio di una richiesta. La media è stata calcolata effettuando migliaia di richieste sul computer locale, quindi i totali non dovrebbero includere problemi di latenza di rete o larghezza di banda.


10
Per coloro che stanno affrontando questa domanda ora, questa è stata la soluzione che ha funzionato meglio per me.
Clarence Klopfstein il

177
Questo non è un codice sicuro. Consentire all'utente di passare un nome file (percorso) in questo modo significa che potrebbero potenzialmente accedere ai file da qualsiasi parte del server. Potrebbe voler avvisare le persone di non usarlo così com'è.
Ian Mercer,

7
A meno che tu non stia costruendo i file al volo quando sono necessari e li memorizziamo nella cache una volta creati (è quello che facciamo).
Brian,

15
@ mare- potresti farlo anche se stai offrendo file da una posizione riservata, ad esempio potresti avere delle immagini App_Datache dovrebbero essere firmate da alcuni utenti dell'applicazione ma non da altri. L'uso di un'azione del controller per servirli consente di limitare l'accesso.
Russ Cam,

8
Come altri hanno già detto, sii cauto nella creazione del tuo percorso, poiché ho visto l'effettivo codice di produzione che permetteva all'utente di navigare in una directory con POST o stringa di query accuratamente costruiti: /../../../danger/someFileTheyTHoughtWasInaccessible
AaronLS

128

Utilizzando la versione di rilascio di MVC, ecco cosa faccio:

[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");
}

Ovviamente ho alcune cose specifiche dell'applicazione qui riguardanti la costruzione del percorso, ma il ritorno di FileStreamResult è bello e semplice.

Ho fatto alcuni test delle prestazioni riguardo a questa azione contro la tua chiamata quotidiana all'immagine (bypassando il controller) e la differenza tra le medie era solo di circa 3 millisecondi (la media del controller era di 68 ms, il non controller era di 65 ms).

Avevo provato alcuni degli altri metodi citati nelle risposte qui e il successo delle prestazioni era molto più drammatico ... molte delle risposte delle soluzioni erano fino a 6 volte il non controller (altri controller avg 340ms, non controller 65ms).


12
Che dire dell'immagine non viene modificata? FileStreamResult dovrebbe inviare 304 quando l'immagine non viene modificata dall'ultima richiesta.
dariol,

È possibile utilizzare al Path.Combineposto di concat per un codice più sicuro e più leggibile.
Marcell Toth,

101

Per espandere leggermente la risposta di Dyland:

Tre classi implementano la classe FileResult :

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

Sono tutti abbastanza autoesplicativi:

  • Per i download di percorsi di file in cui il file esiste sul disco, utilizzare FilePathResult: questo è il modo più semplice ed evita di dover utilizzare Stream.
  • Per array di byte [] (simile a Response.BinaryWrite), utilizzare FileContentResult.
  • Per gli array di byte [] in cui si desidera scaricare il file (content-disposition: allegato), utilizzare FileStreamResultin modo simile al seguente, ma con a MemoryStreame using GetBuffer().
  • Per l' Streamsuso FileStreamResult. Si chiama FileStreamResult ma ci vuole un Streamquindi immagino che funzioni con a MemoryStream.

Di seguito è riportato un esempio di utilizzo della tecnica di disposizione dei contenuti (non testata):

    [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 relativa al contenuto di questo post è stata estremamente utile
Diego,

VS mi sta dicendo che questo sovraccarico di FileStream () è obsoleto.
MrBoJangles,

1
Qualcosa da notare: se hai una virgola nel nome del tuo file, Chrome la rifiuterà con un errore "troppe intestazioni ricevute". Quindi sostituire tutte le virgole con un "-" o "".
Chris S,

Come si farebbe usando solo i controller Web API?
Zapnologica

74

Questo potrebbe essere utile se desideri modificare l'immagine prima di restituirla:

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
Grazie. Questo è perfetto per lo scenario in cui è necessario un proxy per scaricare un'immagine che richiede autenticazione che non può essere eseguita sul lato client.
Hong

1
Ti stai dimenticando di eliminare 3 oggetti nativi enormi: Font, SolidBrush e Image.
Wout,

3
Miglioramento suggerito qui: si crea un flusso di memoria, si scrivono i dati e quindi si crea un risultato File con i dati utilizzando .ToArray () È anche possibile chiamare ms.Seek (0, SeekOrigin.Begin) e quindi restituire File (ms, " image / png ") // restituisce il flusso stesso
Quango

12

Puoi creare la tua estensione e farlo in questo modo.

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

Puoi scrivere direttamente alla risposta ma non è testabile. Si preferisce restituire un ActionResult che ha differito l'esecuzione. Ecco il mio StreamResult riutilizzabile:

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

Perché non andare semplice e usare l' ~operatore tilde ?

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

6

AGGIORNAMENTO: ci sono opzioni migliori della mia risposta originale. Funziona abbastanza al di fuori di MVC ma è meglio attenersi ai metodi integrati per restituire il contenuto dell'immagine. Vedi le risposte votate.

Certamente puoi. Prova questi passaggi:

  1. Carica l'immagine dal disco in un array di byte
  2. memorizza nella cache l'immagine nel caso in cui ti aspetti più richieste per l'immagine e non desideri l'I / O del disco (il mio esempio non lo memorizza nella cache di seguito)
  3. Cambia il tipo mime tramite Response.ContentType
  4. Response.BinaryScrive l'array di byte di immagini

Ecco un po 'di codice di esempio:

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

Spero che aiuti!


4
e come apparirebbe nell'azione del controller?
CRice

5

Soluzione 1: eseguire il rendering di un'immagine in una vista da un URL immagine

Puoi creare il tuo metodo di estensione:

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

Quindi usalo come:

@Html.Image(@Model.ImagePath);

Soluzione 2: eseguire il rendering dell'immagine dal database

Crea un metodo controller che restituisca dati di immagine come di seguito

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);
  }
}

E usalo in una vista come:

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

Per utilizzare un'immagine renderizzata da questo risultato in qualsiasi HTML, utilizzare

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

5

Questo ha funzionato per me. Dal momento che sto memorizzando immagini su un database di 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;
    }

Nel frammento sopra _db.ImageFiles.Find(uuid)sta cercando il record del file immagine nel db (contesto EF). Restituisce un oggetto FileImage che è solo una classe personalizzata creata per il modello e quindi lo utilizza come FileContentResult.

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

4

puoi usare File per restituire un file come Visualizza, Contenuto ecc

 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

Guarda ContentResult. Ciò restituisce una stringa, ma può essere utilizzato per creare la propria classe simile a BinaryResult.


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

SomeHelper.EmptyImageResult()dovrebbe tornare FileResultcon l'immagine esistente (1x1 trasparente, per esempio).

Questo è il modo più semplice se hai file archiviati sull'unità locale. Se i file sono byte[]o stream- quindi utilizzare FileContentResulto FileStreamResultcome suggerito da Dylan.


1

Vedo due opzioni:

1) Implementa il tuo IViewEngine e imposta la proprietà ViewEngine del controller che stai utilizzando su ImageViewEngine nel metodo "immagine" desiderato.

2) Usa una vista :-). Basta cambiare il tipo di contenuto ecc.


1
Questo potrebbe essere un problema a causa di spazi extra o CRLF nella vista.
Elan Hasson,

2
Ho sbagliato nel mio ultimo post ... msdn.microsoft.com/en-us/library/… Puoi usare la classe WebImage e WebImage.Write in una vista :)
Elan Hasson,

1

È possibile utilizzare HttpContext.Response e scrivere direttamente il contenuto su di esso (WriteFile () potrebbe funzionare per voi) e quindi restituire ContentResult dall'azione anziché ActionResult.

Disclaimer: non l'ho provato, si basa sull'esame delle API disponibili. :-)


1
Sì, ho appena notato che ContentResult supporta solo le stringhe, ma è abbastanza facile creare la tua classe basata su ActionResult.
Leppie,

1

Il codice seguente utilizza System.Drawing.Bitmapper caricare l'immagine.

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

Ho anche incontrato requisiti simili,

Quindi, nel mio caso, faccio una richiesta al controller con il percorso della cartella dell'immagine, che a sua volta restituisce un oggetto ImageResult.

Il frammento di codice seguente illustra il lavoro:

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>";

E nel controller dal percorso dell'immagine produco l'immagine e la restituisco al chiamante

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

Leggi l'immagine, convertila in byte[], quindi restituisci a File()con un tipo di contenuto.

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

Ecco gli utilizzi:

using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using Microsoft.AspNetCore.Mvc;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.