Kann ein ASP.NET MVC-Controller ein Image zurückgeben?

455

Kann ich einen Controller erstellen, der einfach ein Image-Asset zurückgibt?

Ich möchte diese Logik durch einen Controller leiten, wenn eine URL wie die folgende angefordert wird:

www.mywebsite.com/resource/image/topbanner

Der Controller wird nachschlagen topbanner.png und sendet das Bild direkt an den Client zurück.

Ich habe Beispiele dafür gesehen, in denen Sie eine Ansicht erstellen müssen - ich möchte keine Ansicht verwenden. Ich möchte alles nur mit dem Controller machen.

Ist das möglich?

Jonathan
quelle
1
Ich habe hier eine ähnliche Frage gestellt: /programming/155906/creating-a-private-photo-gallery-using-aspnet-mvc und fand schließlich einen großartigen Leitfaden dafür. Ich habe eine ImageResult-Klasse gemäß dieser Anleitung erstellt. https://blog.maartenballiauw.be/post/2008/05/13/aspnet-mvc-custom-actionresult.html
Vyrotek
2
Wenn Sie das Bild ändern möchten , verwenden Sie das ImageResizing.Net-HttpModule, um die beste Leistung zu erzielen. Wenn Sie dies nicht tun, fügt ein FilePathResult nur wenige Prozent des Overheads hinzu. Durch das Umschreiben von URLs wird etwas weniger hinzugefügt.
Lilith River
1
Warum nicht WebApi Controller anstelle von MVC verwenden? ApiController class
A-Sharabiani

Antworten:

534

Verwenden Sie die Dateimethode der Basiscontroller.

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

Als Hinweis scheint dies ziemlich effizient zu sein. Ich habe einen Test durchgeführt, bei dem ich das Bild über den Controller ( http://localhost/MyController/Image/MyImage) und die direkte URL ( http://localhost/Images/MyImage.jpg) angefordert habe. Die Ergebnisse waren:

  • MVC: 7,6 Millisekunden pro Foto
  • Direkt: 6,7 Millisekunden pro Foto

Hinweis: Dies ist die durchschnittliche Zeit einer Anfrage. Der Durchschnitt wurde berechnet, indem Tausende von Anforderungen auf dem lokalen Computer gestellt wurden. Daher sollten die Gesamtsummen keine Probleme mit der Netzwerklatenz oder der Bandbreite enthalten.

Brian
quelle
10
Für diejenigen, die jetzt in diese Frage kommen, war dies die Lösung, die für mich am besten funktioniert hat.
Clarence Klopfstein
177
Dies ist kein sicherer Code. Wenn der Benutzer einen Dateinamen (Pfad) wie diesen übergibt, kann er möglicherweise von überall auf dem Server auf Dateien zugreifen. Vielleicht möchten Sie die Leute warnen, es nicht so zu verwenden, wie es ist.
Ian Mercer
7
Es sei denn, Sie erstellen die Dateien im laufenden Betrieb nach Bedarf und zwischenspeichern sie, sobald sie erstellt wurden (genau das tun wir).
Brian
15
@ mare- Sie können dies auch tun, wenn Sie Dateien von einem eingeschränkten Speicherort aus bereitstellen, z. B. Bilder App_Data, die von einigen Benutzern Ihrer Anwendung signiert werden sollten, von anderen jedoch nicht. Durch die Verwendung einer Controller-Aktion können Sie den Zugriff einschränken.
Russ Cam
8
Wie andere bereits erwähnt haben, seien Sie bei der Erstellung Ihres Pfads vorsichtig, da ich den tatsächlichen Produktionscode gesehen habe, der es dem Benutzer ermöglichte, mit sorgfältig erstellten /../../../danger/someFileTheyTHoughtWasInaccessible
POST-
128

Mit der Release-Version von MVC mache ich Folgendes:

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

Ich habe hier natürlich einige anwendungsspezifische Dinge bezüglich der Pfadkonstruktion, aber die Rückgabe des FileStreamResult ist nett und einfach.

Ich habe einige Leistungstests in Bezug auf diese Aktion gegen Ihren täglichen Aufruf des Bildes (unter Umgehung des Controllers) durchgeführt, und die Differenz zwischen den Durchschnittswerten betrug nur etwa 3 Millisekunden (der durchschnittliche Controller betrug 68 ms, der Nicht-Controller betrug 65 ms).

Ich hatte einige der anderen Methoden ausprobiert, die in den Antworten hier erwähnt wurden, und der Leistungseinbruch war viel dramatischer ... Einige der Lösungsantworten waren bis zum 6-fachen des Nicht-Controllers (andere Controller durchschnittlich 340 ms, Nicht-Controller 65 ms).

Segeln Judo
quelle
12
Was ist mit Bild nicht geändert? FileStreamResult sollte 304 senden, wenn das Bild seit der letzten Anforderung nicht geändert wurde.
Dariol
Sie können Path.Combineanstelle von concat sichereren und besser lesbaren Code verwenden.
Marcell Toth
101

Um Dylands Antwort etwas näher zu erläutern:

Drei Klassen implementieren die FileResult- Klasse:

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

Sie sind alle ziemlich selbsterklärend:

  • Verwenden Sie für Dateipfad-Downloads, bei denen die Datei auf der Festplatte vorhanden ist, FilePathResultdies. Dies ist der einfachste Weg und vermeidet die Verwendung von Streams.
  • Verwenden Sie für Byte [] -Arrays (ähnlich wie Response.BinaryWrite) FileContentResult.
  • Verwenden Sie für Byte [] -Arrays, in die die Datei heruntergeladen werden soll (Inhaltsdisposition: Anhang), FileStreamResultauf ähnliche Weise wie unten, jedoch mit a MemoryStreamund using GetBuffer().
  • Zur StreamsVerwendung FileStreamResult. Es heißt FileStreamResult, aber es dauert ein, Streamalso würde ich vermuten, dass es mit einem funktioniert MemoryStream.

Nachfolgend finden Sie ein Beispiel für die Verwendung der Technik zur Inhaltsdisposition (nicht getestet):

    [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;
    }
Chris S.
quelle
2
Der inhaltliche Teil dieses Beitrags war äußerst hilfreich
Diego
VS sagt mir, dass diese Überladung von FileStream () veraltet ist.
MrBoJangles
1
Beachten Sie Folgendes: Wenn Ihr Dateiname ein Komma enthält, lehnt Chrome dieses mit dem Fehler "Zu viele Header empfangen" ab. Ersetzen Sie daher alle Kommas durch ein "-" oder "".
Chris S
Wie würde man das nur mit Web-API-Controllern machen?
Zapnologica
74

Dies kann hilfreich sein, wenn Sie das Bild vor der Rückgabe ändern möchten:

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");
}
staromeste
quelle
1
Vielen Dank. Dies ist ideal für das Szenario, in dem ein Proxy benötigt wird, um ein Image herunterzuladen, für das eine Authentifizierung erforderlich ist, die auf der Clientseite nicht möglich ist.
Hong
1
Sie vergessen, satte 3 native Objekte zu entsorgen: Font, SolidBrush und Image.
Wout
3
Verbesserungsvorschlag hier: Sie erstellen einen Speicherstrom, schreiben die Daten und erstellen dann mit .ToArray () ein Dateiergebnis mit den Daten. Sie können auch einfach ms.Seek (0, SeekOrigin.Begin) aufrufen und dann File (ms, "zurückgeben image / png ") // den Stream selbst zurückgeben
Quango
12

Sie können Ihre eigene Erweiterung erstellen und auf diese Weise vorgehen.

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")%>
Oleksandr Fentsyk
quelle
10

Sie können direkt in die Antwort schreiben, diese ist jedoch nicht testbar. Es wird bevorzugt, ein ActionResult zurückzugeben, dessen Ausführung verzögert wurde. Hier ist mein wiederverwendbares StreamResult:

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);
    }
}
JarrettV
quelle
9

Warum nicht einfach den Tilde- ~Operator verwenden?

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

UPDATE: Es gibt bessere Optionen als meine ursprüngliche Antwort. Dies funktioniert außerhalb von MVC recht gut, aber es ist besser, sich an die integrierten Methoden zur Rückgabe von Bildinhalten zu halten. Siehe hochgestimmte Antworten.

Das kannst du sicher. Probieren Sie diese Schritte aus:

  1. Laden Sie das Image von der Festplatte in ein Byte-Array
  2. Zwischenspeichern Sie das Image für den Fall, dass Sie mehr Anforderungen für das Image erwarten und die Festplatten-E / A nicht möchten (in meinem Beispiel wird es unten nicht zwischengespeichert).
  3. Ändern Sie den MIME-Typ über den Response.ContentType
  4. Response.BinarySchreiben Sie das Bildbyte-Array aus

Hier ist ein Beispielcode:

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

Ich hoffe, das hilft!

Ian Suttle
quelle
4
und wie würde dies in der Aktion des Controllers aussehen?
CRice
5

Lösung 1: Rendern eines Bildes in einer Ansicht von einer Bild-URL

Sie können Ihre eigene Erweiterungsmethode erstellen:

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

Dann benutze es wie folgt:

@Html.Image(@Model.ImagePath);

Lösung 2: Rendern von Bildern aus der Datenbank

Erstellen Sie eine Controller-Methode, die Bilddaten wie unten zurückgibt

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

Und verwenden Sie es in einer Ansicht wie:

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

Verwenden Sie, um ein aus diesem Aktionsergebnis gerendertes Bild in einem beliebigen HTML-Code zu verwenden

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

Das hat bei mir funktioniert. Da ich Bilder in einer SQL Server-Datenbank speichere.

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

Im obigen Snippet _db.ImageFiles.Find(uuid)wird nach dem Bilddateidatensatz in der Datenbank (EF-Kontext) gesucht. Es gibt ein FileImage-Objekt zurück, das nur eine benutzerdefinierte Klasse ist, die ich für das Modell erstellt habe, und verwendet es dann als FileContentResult.

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

Sie können Datei verwenden, um eine Datei wie Ansicht, Inhalt usw. zurückzugeben

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

            }
Avinash Urs
quelle
3

Schauen Sie sich ContentResult an. Dies gibt eine Zeichenfolge zurück, kann jedoch verwendet werden, um eine eigene BinaryResult-ähnliche Klasse zu erstellen.

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

SomeHelper.EmptyImageResult()sollte FileResultmit vorhandenem Bild zurückkehren (z. B. 1x1 transparent).

Dies ist der einfachste Weg, wenn Sie Dateien auf dem lokalen Laufwerk gespeichert haben. Wenn Dateien byte[]oder stream- sind, verwenden Sie FileContentResultoder FileStreamResultwie von Dylan vorgeschlagen.

Victor Gelmutdinov
quelle
1

Ich sehe zwei Möglichkeiten:

1) Implementieren Sie Ihre eigene IViewEngine und setzen Sie die ViewEngine-Eigenschaft des Controllers, den Sie verwenden, in Ihrer gewünschten "image" -Methode auf Ihre ImageViewEngine.

2) Verwenden Sie eine Ansicht :-). Ändern Sie einfach den Inhaltstyp usw.

Matt Mitchell
quelle
1
Dies kann aufgrund zusätzlicher Leerzeichen oder CRLFs in der Ansicht zu Problemen führen.
Elan Hasson
2
Ich habe mich in meinem letzten Beitrag geirrt ... msdn.microsoft.com/en-us/library/… Sie können WebImage-Klasse und WebImage.Write in einer Ansicht verwenden :)
Elan Hasson
1

Sie können die HttpContext.Response verwenden und den Inhalt direkt darauf schreiben (WriteFile () funktioniert möglicherweise für Sie) und dann ContentResult von Ihrer Aktion anstelle von ActionResult zurückgeben.

Haftungsausschluss: Ich habe dies nicht versucht, es basiert auf der Überprüfung der verfügbaren APIs. :-)

Franci Penov
quelle
1
Ja, ich habe gerade bemerkt, dass ContentResult nur Zeichenfolgen unterstützt, aber es ist einfach genug, eine eigene ActionResult-basierte Klasse zu erstellen.
Leppie
1

Der folgende Code wird System.Drawing.Bitmapzum Laden des Bildes verwendet.

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");
}
Youngjae
quelle
0

Ich bin auch auf eine ähnliche Anforderung gestoßen,

In meinem Fall stelle ich eine Anfrage an Controller mit dem Bildordnerpfad, der im Gegenzug ein ImageResult-Objekt zurücksendet.

Das folgende Codefragment veranschaulicht die Arbeit:

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

Und im Controller aus dem Bildpfad produziere ich das Bild und gebe es an den Anrufer zurück

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();
}
Shriram Navaratnalingam
quelle
0

Lesen Sie das Bild, konvertieren Sie es in byte[]und geben Sie ein File()mit einem Inhaltstyp zurück.

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

Hier sind die Verwendungen:

using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using Microsoft.AspNetCore.Mvc;
facepalm42
quelle