ตัวควบคุม ASP.NET MVC สามารถส่งคืนอิมเมจได้หรือไม่?


455

ฉันสามารถสร้างตัวควบคุมที่ส่งคืนเนื้อหารูปภาพได้หรือไม่?

ฉันต้องการเส้นทางตรรกะนี้ผ่านตัวควบคุมเมื่อใดก็ตามที่มีการร้องขอ URL เช่นต่อไปนี้:

www.mywebsite.com/resource/image/topbanner

ตัวควบคุมจะค้นหาtopbanner.pngและส่งภาพนั้นกลับไปยังไคลเอนต์โดยตรง

ฉันเคยเห็นตัวอย่างของสิ่งนี้ซึ่งคุณต้องสร้างมุมมอง - ฉันไม่ต้องการใช้มุมมอง ฉันต้องการทำทุกอย่างด้วยคอนโทรลเลอร์

เป็นไปได้ไหม


1
ฉันถามคำถามที่คล้ายกันที่นี่/programming/155906/creating-a-private-photo-gallery-using-aspnet-mvcและจบลงด้วยการหาคำแนะนำที่ดีในการทำเช่นนี้ ฉันสร้างคลาส ImageResult ตามคู่มือนี้ https://blog.maartenballiauw.be/post/2008/05/13/aspnet-mvc-custom-actionresult.html
Vyrotek

2
หากคุณต้องการแก้ไขภาพให้ใช้ ImageResizing.Net HttpModuleเพื่อประสิทธิภาพที่ดีที่สุด ถ้าคุณทำไม่ได้ FilePathResult จะเพิ่มค่าใช้จ่ายเพียงไม่กี่เปอร์เซ็นต์ การเขียน URL ใหม่จะเพิ่มน้อยกว่าเล็กน้อย
Lilith River

1
ทำไมไม่ใช้ WebApi Controller แทน MVC? ApiController class
A-Sharabiani

คำตอบ:


534

ใช้วิธีการควบคุมไฟล์ฐาน

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

ตามหมายเหตุแล้วดูเหมือนว่าจะมีประสิทธิภาพพอสมควร ฉันทำการทดสอบเมื่อฉันขอภาพผ่านคอนโทรลเลอร์ ( http://localhost/MyController/Image/MyImage) และผ่าน URL โดยตรง ( http://localhost/Images/MyImage.jpg) และผลลัพธ์คือ:

  • MVC: 7.6 มิลลิวินาทีต่อภาพ
  • โดยตรง: 6.7 มิลลิวินาทีต่อภาพ

หมายเหตุ: นี่เป็นเวลาเฉลี่ยของการร้องขอ ค่าเฉลี่ยถูกคำนวณโดยทำการร้องขอหลายพันรายการบนเครื่องโลคัลดังนั้นผลรวมไม่ควรรวมเวลาแฝงของเครือข่ายหรือแบนด์วิดท์


10
สำหรับผู้ที่เข้ามาในคำถามนี้ในตอนนี้นี่เป็นทางออกที่ดีที่สุดสำหรับฉัน
Clarence Klopfstein

177
นี่ไม่ใช่รหัสที่ปลอดภัย การให้ผู้ใช้ส่งชื่อไฟล์ (พา ธ ) เช่นนี้หมายความว่าพวกเขาสามารถเข้าถึงไฟล์ได้จากทุกที่บนเซิร์ฟเวอร์ อาจต้องการเตือนผู้ใช้ไม่ให้ใช้ตามที่เป็นอยู่
Ian Mercer

7
นอกจากว่าคุณกำลังสร้างไฟล์อย่างรวดเร็วตามที่จำเป็นและทำการแคชเมื่อมีการสร้าง (นั่นคือสิ่งที่เราทำ)
Brian

15
@ mare- คุณอาจทำเช่นนี้หากคุณให้บริการไฟล์จากสถานที่ที่ จำกัด เช่นคุณอาจมีภาพApp_Dataที่ควรลงชื่อโดยผู้ใช้บางคนในแอปพลิเคชันของคุณ แต่ไม่ใช่ภาพอื่น ๆ การใช้แอคชั่นคอนโทรลเลอร์เพื่อให้บริการช่วยให้คุณสามารถ จำกัด การเข้าถึงได้
Russ Cam

8
ดังที่คนอื่น ๆ ได้กล่าวไว้โปรดใช้ความระมัดระวังในการสร้างเส้นทางของคุณดังที่ฉันได้เห็นรหัสการผลิตจริงที่อนุญาตให้ผู้ใช้นำทางขึ้นไดเรกทอรีด้วย POST หรือสตริงการสืบค้นที่สร้างขึ้นอย่างระมัดระวัง: /../../../danger/someFileTheyTHoughtWasInaccessible
AaronLS

128

การใช้ MVC รุ่นที่วางจำหน่ายนี่คือสิ่งที่ฉันทำ:

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

เห็นได้ชัดว่าฉันมีบางสิ่งเฉพาะแอปพลิเคชันในที่นี่เกี่ยวกับการสร้างเส้นทาง แต่การกลับมาของ FileStreamResult นั้นดีและเรียบง่าย

ฉันทำการทดสอบประสิทธิภาพบางอย่างเกี่ยวกับการกระทำนี้เทียบกับการโทรหาภาพในชีวิตประจำวันของคุณ (ผ่านตัวควบคุม) และความแตกต่างระหว่างค่าเฉลี่ยอยู่ที่ประมาณ 3 มิลลิวินาทีเท่านั้น (คอนโทรลเลอร์เฉลี่ยคือ 68ms ไม่ใช่คอนโทรลเลอร์ 65 มิลลิวินาที)

ฉันได้ลองวิธีอื่น ๆ ที่กล่าวถึงในคำตอบที่นี่และประสิทธิภาพในการทำงานนั้นน่าทึ่งมาก ... คำตอบหลายคำตอบนั้นเท่า 6x ที่ไม่ใช่คอนโทรลเลอร์ (คอนโทรลเลอร์อื่น ๆ เฉลี่ย 340ms ไม่ใช่คอนโทรลเลอร์ 65ms)


12
รูปภาพไม่ได้รับการแก้ไข FileStreamResult ควรส่ง 304 เมื่อไม่มีการแก้ไขอิมเมจตั้งแต่การร้องขอครั้งล่าสุด
dariol

คุณสามารถใช้Path.Combineแทน concat สำหรับรหัสที่ปลอดภัยและอ่านง่ายขึ้น
Marcell Toth

101

หากต้องการอธิบายการตอบสนองของ Dyland เล็กน้อย:

สามคลาสใช้คลาสFileResult :

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

พวกเขาล้วนอธิบายตนเองอย่างเป็นธรรม:

  • สำหรับการดาวน์โหลดพา ธ ไฟล์ที่มีไฟล์อยู่บนดิสก์ให้ใช้ FilePathResult - นี่เป็นวิธีที่ง่ายที่สุดและหลีกเลี่ยงการใช้ Streams
  • สำหรับไบต์ [] อาร์เรย์ (คล้ายกับ Response.BinaryWrite) ให้ใช้ FileContentResultการใช้งาน
  • สำหรับไบต์ [] อาร์เรย์ที่คุณต้องการให้ไฟล์ดาวน์โหลด (content-disposition: Attach) ใช้FileStreamResultในวิธีที่คล้ายกับด้านล่าง แต่ใช้ a MemoryStreamและการใช้GetBuffer()และการใช้
  • สำหรับการใช้งานStreams FileStreamResultมันเรียกว่า FileStreamResult แต่มันต้องใช้เวลาStreamดังนั้นฉันคิดว่าMemoryStreamมันทำงานร่วมกับ

ด้านล่างเป็นตัวอย่างของการใช้เทคนิคการจัดการเนื้อหา (ไม่ได้ทดสอบ):

    [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
ส่วนการจัดการเนื้อหาของโพสต์นี้มีประโยชน์มาก
Diego

VS กำลังบอกฉันว่า FileStream () เกินพิกัดนี้ล้าสมัยแล้ว
MrBoJangles

1
สิ่งที่ควรทราบ: หากคุณมีเครื่องหมายจุลภาคในชื่อไฟล์ Chrome จะปฏิเสธด้วยข้อผิดพลาด "ได้รับส่วนหัวมากเกินไป" ดังนั้นแทนที่เครื่องหมายจุลภาคทั้งหมดด้วย "-" หรือ ""
Chris S

เราจะทำสิ่งนี้โดยใช้ตัวควบคุมเว็บ api เท่านั้น
Zapnologica

74

สิ่งนี้อาจมีประโยชน์หากคุณต้องการแก้ไขภาพก่อนส่งคืน:

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
ขอบคุณ. นี่เหมาะสำหรับสถานการณ์ที่จำเป็นต้องใช้พร็อกซีเพื่อดาวน์โหลดภาพที่ต้องการการตรวจสอบความถูกต้องที่ไม่สามารถทำได้ในฝั่งไคลเอ็นต์
Hong

1
คุณลืมที่จะกำจัดวัตถุพื้นเมือง 3 อัน ได้แก่ Font, SolidBrush และ Image
Wout

3
การปรับปรุงที่แนะนำที่นี่: คุณสร้างสตรีมหน่วยความจำเขียนข้อมูลแล้วสร้างผลลัพธ์ไฟล์ด้วยข้อมูลโดยใช้. ToArray () คุณสามารถโทร ms.Seek (0, SeekOrigin.Begin) แล้วส่งคืนไฟล์ (ms, " image / png ") // คืนค่ากระแสเอง
Quango

12

คุณสามารถสร้างส่วนขยายของคุณเองและทำเช่นนี้

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

คุณสามารถเขียนโดยตรงไปยังคำตอบ แต่ก็ไม่สามารถทดสอบได้ ขอแนะนำให้ส่งคืน ActionResult ที่มีการดำเนินการรอการตัดบัญชี นี่คือ 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);
    }
}

9

ทำไมไม่ลองใช้ตัว~ดำเนินการtilde ดูล่ะ?

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

6

UPDATE: มีตัวเลือกที่ดีกว่าคำตอบเดิมของฉัน วิธีนี้ใช้งานได้นอก MVC ค่อนข้างดี แต่จะดีกว่าหากใช้วิธีส่งคืนเนื้อหาภาพในตัว ดูคำตอบที่ได้รับการโหวต

คุณสามารถทำได้อย่างแน่นอน ลองทำตามขั้นตอนเหล่านี้:

  1. โหลดอิมเมจจากดิสก์ลงในอาร์เรย์ไบต์
  2. แคชรูปภาพในกรณีที่คุณคาดหวังว่าจะมีคำขอเพิ่มเติมสำหรับรูปภาพและไม่ต้องการดิสก์ I / O (ตัวอย่างของฉันไม่ได้แคชด้านล่าง)
  3. เปลี่ยนประเภท mime ผ่านทาง Response.ContentType
  4. Response.BinaryWrite ออกอาร์เรย์ภาพ image

นี่คือตัวอย่างรหัสบางส่วน:

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

หวังว่าจะช่วย!


4
และสิ่งนี้จะดูในการกระทำของตัวควบคุม?
CRice

5

โซลูชันที่ 1: เมื่อต้องการแสดงรูปภาพในมุมมองจาก URL รูปภาพ

คุณสามารถสร้างวิธีการขยายของคุณเอง:

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

จากนั้นใช้มันเหมือน:

@Html.Image(@Model.ImagePath);

โซลูชันที่ 2: การแสดงรูปภาพจากฐานข้อมูล

สร้างวิธีการควบคุมที่ส่งคืนข้อมูลภาพเหมือนด้านล่าง

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

และใช้ในมุมมองเช่น:

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

ในการใช้รูปภาพที่แสดงผลจากแอ็คชันนี้ใน HTML ใด ๆ ให้ใช้

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

5

สิ่งนี้ใช้ได้สำหรับฉัน เนื่องจากฉันเก็บภาพไว้ในฐานข้อมูล 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;
    }

ในตัวอย่างด้านบน_db.ImageFiles.Find(uuid)กำลังค้นหาระเบียนไฟล์ภาพใน db (บริบทของ EF) มันส่งคืนวัตถุ FileImage ซึ่งเป็นเพียงคลาสที่กำหนดเองที่ฉันทำสำหรับรูปแบบและจากนั้นใช้มันเป็น FileContentResult

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

4

คุณสามารถใช้ไฟล์เพื่อส่งคืนไฟล์เช่นมุมมองเนื้อหา ฯลฯ

 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

ดู ContentResult สิ่งนี้ส่งคืนสตริง แต่สามารถใช้เพื่อสร้างคลาส BinaryResult ที่เหมือนคุณได้


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

SomeHelper.EmptyImageResult() ควรกลับมา FileResultพร้อมกับภาพที่มีอยู่ (ตัวอย่างเช่น 1x1 โปร่งใส)

วิธีนี้เป็นวิธีที่ง่ายที่สุดถ้าคุณมีไฟล์เก็บไว้ในไดรฟ์ในเครื่อง หากไฟล์เป็นbyte[]หรือstream- ให้ใช้FileContentResultหรือFileStreamResultตามคำแนะนำของดีแลน


1

ฉันเห็นสองตัวเลือก:

1) ใช้ IViewEngine ของคุณเองและตั้งค่าคุณสมบัติ ViewEngine ของคอนโทรลเลอร์ที่คุณใช้กับ ImageViewEngine ในวิธี "image" ที่คุณต้องการ

2) ใช้มุมมอง :-) เพียงแค่เปลี่ยนประเภทเนื้อหาเป็นต้น


1
อาจเป็นปัญหาเนื่องจากพื้นที่ว่างเพิ่มเติมหรือ CRLF ในมุมมอง
Elan Hasson

2
ผมผิดในโพสต์ล่าสุดของฉัน ... msdn.microsoft.com/en-us/library/... คุณสามารถใช้ระดับ WebImage และ WebImage.Write ในมุมมอง :)
Elan Hasson

1

คุณสามารถใช้ HttpContext.Response และเขียนเนื้อหาโดยตรง (WriteFile () อาจเหมาะกับคุณ) จากนั้นส่งคืน ContentResult จากการกระทำของคุณแทน ActionResult

ข้อจำกัดความรับผิดชอบ: ฉันไม่ได้ลองสิ่งนี้มันขึ้นอยู่กับการดู API ที่มีให้ :-)


1
ใช่ฉันเพิ่งสังเกตเห็นว่า ContentResult รองรับเฉพาะสตริง แต่มันง่ายพอที่จะทำให้คลาสของคุณเป็นพื้นฐานของ ActionResult
leppie

1

โค้ดด้านล่างใช้System.Drawing.Bitmapในการโหลดภาพ

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

ฉันยังพบความต้องการที่คล้ายกัน

ดังนั้นในกรณีของฉันฉันขอให้ผู้ควบคุมด้วยเส้นทางโฟลเดอร์รูปภาพซึ่งเป็นการส่งกลับวัตถุ ImageResult

ตัวอย่างรหัสต่อไปนี้แสดงให้เห็นถึงการทำงาน:

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

และในตัวควบคุมจากเส้นทางภาพฉันผลิตภาพและส่งกลับไปยังผู้โทร

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

อ่านภาพแปลงเป็นbyte[]แล้วส่งกลับFile()ด้วยประเภทเนื้อหา

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

นี่คือการใช้งาน:

using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using Microsoft.AspNetCore.Mvc;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.