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




produces asp net core (13)

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

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

www.mywebsite.com/resource/image/topbanner

Der Controller sucht topbanner.png und sendet dieses Bild direkt an den Client zurück.

Ich habe Beispiele dafür gesehen, wo 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?


Um auf Dylands Antwort etwas zu erklären:

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 vorhanden ist, verwenden Sie FilePathResult - dies ist der einfachste Weg und vermeidet die Verwendung von Streams.
  • Verwenden Sie FileContentResult Arrays vom FileContentResult Byte [] (ähnlich wie Response.BinaryWrite).
  • Für Byte [] -Arrays, bei denen die Datei heruntergeladen werden soll (content-disposition: Anhang), verwenden Sie FileStreamResult auf ähnliche Weise wie unten, jedoch mit einem MemoryStream und mit GetBuffer() .
  • Verwenden FileStreamResult für Streams FileStreamResult . Es heißt FileStreamResult, aber es braucht einen Stream also würde ich vermuten, dass es mit einem MemoryStream funktioniert.

Im Folgenden finden Sie ein Beispiel für die Verwendung der Content-Disposition-Technik (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;
    }

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

[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 offensichtlich einige anwendungsspezifische Dinge hier bezüglich der Pfadkonstruktion, aber die Rückgabe des FileStreamResult ist nett und einfach.

Ich habe einige Performance-Tests in Bezug auf diese Aktion gegen Ihren täglichen Aufruf an das Bild (unter Umgehung des Controllers) durchgeführt und der Unterschied zwischen den Durchschnitten betrug nur ca. 3 Millisekunden (Controller-Durchschnitt 68ms, Nicht-Controller 65ms).

Ich hatte einige der anderen in den Antworten erwähnten Methoden ausprobiert und der Performance-Hit war viel dramatischer ... einige der Lösungsantworten waren sechsmal so groß wie der Nicht-Controller (andere Controller durchschnittlich 340ms, Nicht-Controller 65ms).


Lösung 1: Um ein Bild in einer Ansicht von einer Bild-URL zu rendern

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:

@Html.Image(@Model.ImagePath);

Lösung 2: Bild von der Datenbank rendern

Erstellen Sie eine Controller-Methode, die Bilddaten wie folgt 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 benutze es in einer Ansicht wie:

@ { Html.RenderAction("View","Image",new {[email protected]})}

Um ein Bild zu verwenden, das von diesem actionresult in irgendeinem HTML gerendert wird, verwenden Sie

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

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


UPDATE: Es gibt bessere Möglichkeiten als meine ursprüngliche Antwort. Dies funktioniert ziemlich gut außerhalb von MVC, aber es ist besser, bei den integrierten Methoden für die Rückgabe von Bildinhalten zu bleiben. Siehe nachgewählte Antworten.

Das kannst du sicherlich. Probieren Sie diese Schritte aus:

  1. Laden Sie das Image von der Festplatte in ein Byte-Array
  2. Cache das Bild für den Fall, dass Sie mehr Anfragen für das Bild erwarten und nicht die Festplatte I / O (mein Beispiel speichert es nicht im Cache)
  3. Ändern Sie den MIME-Typ über den Response.ContentType
  4. Response.BinaryWorte das Bild-Byte-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!


Das hat für mich funktioniert. Da ich Bilder in einer SQL Server-Datenbank speichern.

    [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 _db.ImageFiles.Find(uuid) sucht _db.ImageFiles.Find(uuid) nach dem Image-Datei-Record in der db (EF-Kontext). Sie 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; }
}

Warum nicht einfach gehen und den Tilde ~ Operator benutzen?

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

Sie können Ihre eigene Erweiterung erstellen und so machen.

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

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

SomeHelper.EmptyImageResult() sollte FileResult mit vorhandenem Bild zurückgeben ( FileResult 1x1 transparent).

Dies ist am einfachsten, wenn Dateien auf dem lokalen Laufwerk gespeichert sind. Wenn Dateien byte[] oder stream verwenden Sie FileContentResult oder FileStreamResult wie es Dylan vorgeschlagen hat.


Verwenden Sie die Basis-Controller-Dateimethode.

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 Anmerkung scheint dies ziemlich effizient zu sein. Ich habe einen Test gemacht, bei dem ich das Bild über den Controller ( http://localhost/MyController/Image/MyImage ) und über die direkte URL ( http://localhost/Images/MyImage.jpg ) 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 Zeit einer Anfrage. Der Durchschnitt wurde berechnet, indem Tausende von Anforderungen auf dem lokalen Computer ausgeführt wurden. Daher sollten die Gesamtwerte keine Netzwerklatenz oder Bandbreitenprobleme enthalten.


Ich sehe zwei Möglichkeiten:

1) Implementieren Sie Ihre eigene IViewEngine und legen Sie die ViewEngine-Eigenschaft des verwendeten Controllers für Ihre ImageViewEngine in Ihrer gewünschten "image" -Methode fest.

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


Sie können direkt in die Antwort schreiben, aber dann ist es nicht testbar. Es wird bevorzugt, ein ActionResult zurückzugeben, das die Ausführung verzögert hat. Hier ist mein resusesbares 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);
    }
}

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

Disclaimer: Ich habe das nicht versucht, es basiert auf dem Blick auf die verfügbaren APIs. :-)







controller