483 Stimmen

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

Kann ich einen Controller erstellen, der einfach ein Bild-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 sucht nach topbanner.png und senden dieses Bild direkt an den Kunden zurück.

Ich habe Beispiele gesehen, bei denen man eine Ansicht erstellen muss - ich möchte keine Ansicht verwenden. Ich möchte das alles nur mit dem Controller machen.

Ist dies möglich?

1 Stimmen

Ich habe eine ähnliche Frage hier gestellt https://stackoverflow.com/questions/155906/creating-a-private-photo-gallery-using-aspnet-mvc und fand schließlich einen tollen Leitfaden für diese Aufgabe. Ich habe eine ImageResult-Klasse nach diesem Leitfaden erstellt. https://blog.maartenballiauw.be/post/2008/05/13/aspnet-mvc-custom-actionresult.html

2 Stimmen

Wenn Sie das Bild verändern wollen, das ImageResizing.Net HttpModul verwenden um die beste Leistung zu erzielen. Wenn Sie das nicht tun, fügt ein FilePathResult nur ein paar Prozent des Overheads hinzu. URL-Rewriting fügt etwas weniger hinzu.

1 Stimmen

Warum nicht mit WebApi Controller statt MVC? ApiController class

556voto

Brian Punkte 36229

Verwenden Sie die Methode base controllers File.

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

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

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

Hinweis: Dies ist die durchschnittliche Dauer einer Anfrage. Der Durchschnitt wurde durch Tausende von Anfragen auf dem lokalen Rechner berechnet, so dass die Gesamtzahlen keine Netzwerklatenz- oder Bandbreitenprobleme enthalten sollten.

10 Stimmen

Für diejenigen, die sich jetzt mit dieser Frage beschäftigen, war dies die Lösung, die für mich am besten funktioniert hat.

2 Stimmen

Tolle Antwort, Brian. Eine Sache, der contentType Parameter im File() Methodenaufruf sollte in Ihrem Beispiel "image/jpeg" sein. Der IE8 öffnet sonst einen Download-Dialog. Auch diese Antwort sollte ausgewählt werden.

189 Stimmen

Das ist kein sicherer Code. Wenn der Benutzer einen Dateinamen (Pfad) auf diese Weise übergibt, kann er potenziell von überall auf dem Server auf Dateien zugreifen. Vielleicht sollten Sie die Leute davor warnen, diesen Code so zu verwenden.

138voto

Sailing Judo Punkte 10635

Mit der Release-Version von MVC, hier ist, was ich tun:

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

Offensichtlich habe ich hier einige anwendungsspezifische Dinge in Bezug auf die Pfadkonstruktion, aber die Rückgabe des FileStreamResult ist schön und einfach.

Ich habe einige Performance-Tests in Bezug auf diese Aktion gegen Ihre täglichen Aufruf des Bildes (unter Umgehung der Controller) und der Unterschied zwischen den Durchschnittswerten war nur etwa 3 Millisekunden (Controller avg war 68ms, nicht-Controller war 65ms).

Ich hatte einige der anderen Methoden ausprobiert, die in den Antworten hier erwähnt wurden, und die Leistungseinbußen waren viel dramatischer... mehrere der Lösungen reagierten bis zu 6 Mal schneller als die Nicht-Controller (andere Controller avg 340ms, Nicht-Controller 65ms).

12 Stimmen

Was ist mit dem Bild, das nicht geändert wird? FileStreamResult sollte 304 senden, wenn das Bild seit der letzten Anfrage nicht geändert wurde.

0 Stimmen

Sie können verwenden Path.Combine anstelle von concat, um den Code sicherer und lesbarer zu machen.

104voto

Chris S Punkte 63542

Um die Antwort von Dyland etwas zu erweitern:

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:

  • Für Dateipfad-Downloads, bei denen die Datei auf der Festplatte existiert, verwenden Sie FilePathResult - Dies ist der einfachste Weg und erspart Ihnen die Verwendung von Streams.
  • Für Byte[]-Arrays (ähnlich wie bei Response.BinaryWrite) verwenden Sie FileContentResult .
  • Für Byte[]-Arrays, bei denen die Datei heruntergeladen werden soll (content-disposition: attachment), verwenden Sie FileStreamResult in ähnlicher Weise wie unten, aber mit einem MemoryStream und mit GetBuffer() .
  • Für Streams verwenden. FileStreamResult . Es wird FileStreamResult genannt, aber es nimmt eine Stream also würde ich erraten es funktioniert mit einem MemoryStream .

Nachfolgend finden Sie ein Beispiel für die Anwendung der Technik der 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;
    }

2 Stimmen

Der Teil dieses Beitrags, in dem es um den Inhalt geht, war äußerst hilfreich.

0 Stimmen

VS sagt mir, dass diese Überladung von FileStream() veraltet ist.

1 Stimmen

Ein Hinweis: Wenn Sie ein Komma im Dateinamen haben, weist Chrome die Datei mit der Fehlermeldung "too many headers received" zurück. Ersetzen Sie also alle Kommas durch ein "-" oder "".

76voto

staromeste Punkte 931

Dies kann hilfreich sein, wenn Sie das Bild vor der Rücksendung ä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");
}

1 Stimmen

Ich danke Ihnen. Dies ist perfekt für das Szenario, in dem ein Proxy benötigt wird, um ein Bild herunterzuladen, das eine Authentifizierung erfordert, die nicht auf der Client-Seite durchgeführt werden kann.

1 Stimmen

Sie vergessen, ganze 3 einheimische Objekte zu entsorgen: Schrift, SolidBrush und Bild.

3 Stimmen

Verbesserungsvorschlag: Sie erstellen einen Speicherstrom, schreiben die Daten und erstellen dann mit .ToArray() ein Dateiergebnis mit den Daten. Sie könnten auch einfach ms.Seek(0, SeekOrigin.Begin) aufrufen und dann File(ms, "image/png") // return the stream itself

13voto

Oleksandr Fentsyk Punkte 4980

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

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")%>

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X