Bisakah kontroler ASP.NET MVC mengembalikan gambar?

455

Bisakah saya membuat Pengontrol yang hanya mengembalikan aset gambar?

Saya ingin merutekan logika ini melalui pengontrol, setiap kali URL seperti yang berikut ini diminta:

www.mywebsite.com/resource/image/topbanner

Pengontrol akan mencari topbanner.pngdan mengirim gambar itu langsung kembali ke klien.

Saya telah melihat contoh ini di mana Anda harus membuat Tampilan - Saya tidak ingin menggunakan Tampilan. Saya ingin melakukan semuanya hanya dengan Controller.

Apakah ini mungkin?

Jonathan
sumber
1
Saya mengajukan pertanyaan serupa di sini /programming/155906/creating-a-private-photo-gallery-using-aspnet-mvc dan akhirnya menemukan panduan hebat untuk melakukan ini. Saya membuat kelas ImageResult mengikuti panduan ini. https://blog.maartenballiauw.be/post/2008/05/13/aspnet-mvc-custom-actionresult.html
Vyrotek
2
Jika Anda ingin memodifikasi gambar, gunakan HttpModule ImageResizing.Net untuk kinerja terbaik. Jika tidak, FilePathResult hanya menambahkan beberapa persen dari overhead. Penulisan ulang URL menambahkan sedikit lebih sedikit.
Sungai Lilith
1
Mengapa tidak menggunakan WebApi Controller, bukan MVC? ApiController class
A-Sharabiani

Jawaban:

534

Gunakan metode File pengendali dasar.

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

Sebagai catatan, ini tampaknya cukup efisien. Saya melakukan tes di mana saya meminta gambar melalui controller ( http://localhost/MyController/Image/MyImage) dan melalui URL langsung ( http://localhost/Images/MyImage.jpg) dan hasilnya adalah:

  • MVC: 7,6 milidetik per foto
  • Langsung: 6,7 milidetik per foto

Catatan: ini adalah waktu rata-rata permintaan. Rata-rata dihitung dengan membuat ribuan permintaan pada mesin lokal, sehingga totalnya tidak termasuk latensi jaringan atau masalah bandwidth.

Brian
sumber
10
Bagi mereka yang masuk ke pertanyaan ini sekarang, ini adalah solusi yang paling cocok untuk saya.
Clarence Klopfstein
177
Ini bukan kode aman. Membiarkan pengguna memberikan nama file (jalur) seperti ini berarti mereka berpotensi mengakses file dari mana saja di server. Mungkin ingin memperingatkan orang untuk tidak menggunakannya apa adanya.
Ian Mercer
7
Kecuali jika Anda membuat file dengan cepat sebagaimana diperlukan dan menyimpannya setelah dibuat (itulah yang kami lakukan).
Brian
15
@ mare- Anda mungkin juga melakukan ini jika Anda menyajikan file dari lokasi terbatas misalnya Anda mungkin memiliki gambar App_Datayang harus ditandatangani oleh beberapa pengguna aplikasi Anda tetapi tidak yang lain. Menggunakan tindakan pengontrol untuk melayani mereka memungkinkan Anda untuk membatasi akses.
Russ Cam
8
Seperti yang lain telah disebutkan, berhati-hatilah dalam membangun jalur Anda, karena saya telah melihat kode produksi aktual yang memungkinkan pengguna untuk menavigasi direktori dengan POST atau string kueri yang dibuat dengan cermat: /../../../danger/someFileTheyTHoughtWasInaccessible
AaronLS
128

Menggunakan versi rilis MVC, inilah yang saya lakukan:

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

Saya jelas memiliki beberapa hal khusus aplikasi di sini mengenai pembangunan jalur, tetapi kembalinya FileStreamResult bagus dan sederhana.

Saya melakukan beberapa pengujian kinerja sehubungan dengan tindakan ini terhadap panggilan sehari-hari Anda ke gambar (melewati controller) dan perbedaan antara rata-rata hanya sekitar 3 milidetik (rata-rata pengontrol adalah 68ms, non-controller adalah 65ms).

Saya telah mencoba beberapa metode lain yang disebutkan dalam jawaban di sini dan kinerja hit jauh lebih dramatis ... beberapa jawaban solusi adalah sebanyak 6x non-controller (pengendali lain rata-rata 340ms, 65ms non-controller).

Judo berlayar
sumber
12
Bagaimana dengan gambar yang tidak dimodifikasi? FileStreamResult harus mengirim 304 ketika gambar tidak diubah sejak permintaan terakhir.
dariol
Anda dapat menggunakan Path.Combinealih-alih concat untuk kode yang lebih aman dan lebih mudah dibaca.
Marcell Toth
101

Untuk sedikit mengeksploitasi tanggapan Dyland:

Tiga kelas menerapkan kelas FileResult :

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

Semuanya cukup jelas:

  • Untuk unduhan jalur file di mana file itu ada di disk, gunakan FilePathResult- ini adalah cara termudah dan menghindari Anda harus menggunakan Streaming.
  • Untuk array byte [] (mirip dengan Response.BinaryWrite), gunakan FileContentResult.
  • Untuk array byte [] tempat Anda ingin mengunduh file (disposisi konten: lampiran), gunakan FileStreamResultcara yang mirip dengan di bawah ini, tetapi dengan a MemoryStreamdan menggunakan GetBuffer().
  • Untuk Streamspenggunaan FileStreamResult. Ini disebut FileStreamResult tetapi butuh Streamjadi saya kira itu berfungsi dengan a MemoryStream.

Di bawah ini adalah contoh penggunaan teknik konten-disposisi (tidak diuji):

    [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
sumber
2
Bagian disposisi konten dari posting ini sangat membantu
Diego
VS memberi tahu saya bahwa kelebihan FileStream () ini sudah usang.
MrBoJangles
1
Sesuatu yang perlu diperhatikan: jika Anda memiliki koma dalam nama file Anda, Chrome akan menolaknya dengan kesalahan "terlalu banyak header yang diterima". Jadi, ganti semua koma dengan "-" atau "".
Chris S
Bagaimana orang melakukan ini hanya dengan menggunakan pengontrol api web?
Zapnologica
74

Ini mungkin berguna jika Anda ingin memodifikasi gambar sebelum mengembalikannya:

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
sumber
1
Terima kasih. Ini sempurna untuk skenario di mana proxy diperlukan untuk mengunduh gambar yang memerlukan otentikasi yang tidak dapat dilakukan di sisi klien.
Hong
1
Anda lupa membuang 3 objek asli: Font, SolidBrush, dan Gambar.
Wout
3
Perbaikan yang disarankan di sini: Anda membuat aliran memori, menulis data dan kemudian membuat hasil File dengan data menggunakan .ToArray () Anda juga bisa menghubungi ms.Seek (0, SeekOrigin.Begin) dan kemudian kembalikan File (ms, " image / png ") // kembalikan alirannya sendiri
Quango
12

Anda dapat membuat ekstensi sendiri dan melakukannya dengan cara ini.

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
sumber
10

Anda dapat menulis langsung ke respons tetapi tidak dapat diuji. Lebih disukai untuk mengembalikan ActionResult yang telah menunda eksekusi. Inilah StreamResult saya yang dapat digunakan kembali:

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
sumber
9

Mengapa tidak pergi sederhana dan menggunakan ~operator tilde ?

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

UPDATE: Ada opsi yang lebih baik daripada jawaban asli saya. Ini berfungsi di luar MVC dengan cukup baik, tetapi lebih baik menggunakan metode bawaan untuk mengembalikan konten gambar. Lihat jawaban yang dipilih.

Anda tentu bisa. Coba langkah-langkah ini:

  1. Muat gambar dari disk ke array byte
  2. cache gambar jika Anda mengharapkan lebih banyak permintaan untuk gambar dan tidak ingin disk I / O (sampel saya tidak menyimpannya di bawah)
  3. Ubah tipe mime melalui Response.ContentType
  4. Response.BinaryWrite keluar array byte gambar

Berikut beberapa contoh kode:

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

Semoga itu bisa membantu!

Ian Suttle
sumber
4
dan bagaimana ini akan terlihat dalam tindakan pengontrol?
CRice
5

Solusi 1: Untuk merender gambar dalam tampilan dari URL gambar

Anda dapat membuat metode ekstensi sendiri:

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

Kemudian gunakan seperti:

@Html.Image(@Model.ImagePath);

Solusi 2: Untuk merender gambar dari basis data

Buat metode pengontrol yang mengembalikan data gambar seperti di bawah ini

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

Dan gunakan dalam tampilan seperti:

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

Untuk menggunakan gambar yang dihasilkan dari tindakan ini menghasilkan HTML apa pun, gunakan

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

Ini berhasil untuk saya. Karena saya menyimpan gambar pada database 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;
    }

Dalam cuplikan di atas _db.ImageFiles.Find(uuid)sedang mencari catatan file gambar dalam db (konteks EF). Ini mengembalikan objek FileImage yang hanya kelas kustom yang saya buat untuk model dan kemudian menggunakannya sebagai FileContentResult.

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

Anda dapat menggunakan File untuk mengembalikan file seperti Lihat, Konten dll

 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
sumber
3

Lihatlah ContentResult. Ini mengembalikan sebuah string, tetapi dapat digunakan untuk membuat kelas BinaryResult Anda sendiri.

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

SomeHelper.EmptyImageResult()harus kembali FileResultdengan gambar yang ada (1x1 transparan, misalnya).

Ini adalah cara termudah jika Anda memiliki file yang disimpan di drive lokal. Jika file byte[]atau stream- maka gunakan FileContentResultatau FileStreamResultseperti yang disarankan Dylan.

Victor Gelmutdinov
sumber
1

Saya melihat dua opsi:

1) Terapkan IViewEngine Anda sendiri dan tetapkan properti ViewEngine dari Controller yang Anda gunakan untuk ImageViewEngine Anda dengan metode "gambar" yang Anda inginkan.

2) Gunakan tampilan :-). Ubah saja jenis konten, dll.

Matt Mitchell
sumber
1
Ini bisa menjadi masalah karena ruang ekstra atau CRLF di Tampilan.
Elan Hasson
2
Saya salah dalam posting terakhir saya ... msdn.microsoft.com/en-us/library/ ... Anda dapat menggunakan kelas WebImage dan WebImage. Tulislah dalam tampilan :)
Elan Hasson
1

Anda bisa menggunakan HttpContext.Response dan langsung menulis konten untuk itu (WriteFile () mungkin bekerja untuk Anda) dan kemudian mengembalikan ContentResult dari tindakan Anda alih-alih ActionResult.

Penafian: Saya belum mencoba ini, ini didasarkan pada melihat API yang tersedia. :-)

Franci Penov
sumber
1
Ya saya hanya memperhatikan ContentResult hanya mendukung string, tetapi cukup mudah untuk membuat kelas berbasis ActionResult Anda sendiri.
leppie
1

Kode di bawah ini digunakan System.Drawing.Bitmapuntuk memuat gambar.

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
sumber
0

Saya juga menemui persyaratan serupa,

Jadi dalam kasus saya, saya membuat permintaan ke Controller dengan path folder gambar, yang pada gilirannya mengirimkan kembali objek ImageResult.

Cuplikan kode berikut menggambarkan pekerjaan:

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

Dan di Controller dari jalur gambar saya menghasilkan gambar dan mengembalikannya ke pemanggil

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
sumber
0

Baca gambar, konversikan ke byte[], lalu kembalikan File()dengan tipe konten.

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

Berikut adalah usings:

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