Bộ điều khiển ASP.NET MVC có thể trả về một hình ảnh không?


455

Tôi có thể tạo Trình điều khiển chỉ trả về một tài sản hình ảnh không?

Tôi muốn định tuyến logic này thông qua bộ điều khiển, bất cứ khi nào một URL như sau được yêu cầu:

www.mywebsite.com/resource/image/topbanner

Bộ điều khiển sẽ tra cứu topbanner.pngvà gửi hình ảnh đó trực tiếp trở lại máy khách.

Tôi đã thấy các ví dụ về điều này khi bạn phải tạo Chế độ xem - Tôi không muốn sử dụng Chế độ xem. Tôi muốn làm tất cả chỉ với Bộ điều khiển.

Điều này có thể không?


1
Tôi đã hỏi một câu hỏi tương tự ở đây /programming/155906/creating-a-private-photo-gallery-USE-aspnet-mvc và cuối cùng đã tìm thấy một hướng dẫn tuyệt vời để làm điều này. Tôi đã tạo một lớp ImageResult theo hướng dẫn này. https://blog.maartenballiauw.be/post/2008/05/13/aspnet-mvc-custom-actionresult.html
Vyrotek

2
Nếu bạn muốn sửa đổi hình ảnh, hãy sử dụng ImageMesule ImageResizing.Net để có hiệu suất tốt nhất. Nếu bạn không, FilePathResult chỉ thêm một vài phần trăm chi phí. Viết lại URL thêm ít hơn một chút.
Sông Lilith

1
Tại sao không sử dụng Trình điều khiển WebApi thay vì MVC? ApiController class
A-Sharabiani

Câu trả lời:


534

Sử dụng bộ điều khiển cơ sở Phương pháp tệp.

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

Một lưu ý, điều này có vẻ là khá hiệu quả. Tôi đã thực hiện một thử nghiệm trong đó tôi yêu cầu hình ảnh thông qua bộ điều khiển ( http://localhost/MyController/Image/MyImage) và thông qua URL trực tiếp ( http://localhost/Images/MyImage.jpg) và kết quả là:

  • MVC: 7.6 mili giây trên mỗi bức ảnh
  • Trực tiếp: 6,7 mili giây trên mỗi bức ảnh

Lưu ý: đây là thời gian trung bình của một yêu cầu. Trung bình được tính bằng cách thực hiện hàng ngàn yêu cầu trên máy cục bộ, do đó tổng số không được bao gồm các vấn đề về độ trễ mạng hoặc băng thông.


10
Đối với những người đang đến với câu hỏi này bây giờ, đây là giải pháp phù hợp nhất với tôi.
Clarence Klopfstein

177
Đây không phải là mã an toàn. Để người dùng vượt qua tên tệp (đường dẫn) như thế này có nghĩa là họ có khả năng truy cập các tệp từ bất kỳ đâu trên máy chủ. Có thể muốn cảnh báo mọi người không sử dụng nó như vốn có.
Ian Mercer

7
Trừ khi bạn đang xây dựng các tệp một cách nhanh chóng khi chúng cần thiết và lưu trữ chúng một khi chúng được tạo (đó là những gì chúng tôi làm).
Brian

15
@ mare- bạn cũng có thể làm điều này nếu bạn đang phục vụ các tệp từ một vị trí bị hạn chế, ví dụ: bạn có thể có hình ảnh trong App_Datađó phải được ký bởi một số người dùng ứng dụng của bạn chứ không phải những người khác. Sử dụng một hành động điều khiển để phục vụ chúng cho phép bạn hạn chế quyền truy cập.
Nga Cam

8
Như những người khác đã đề cập, hãy thận trọng trong việc xây dựng đường dẫn của bạn, vì tôi đã thấy mã sản xuất thực tế cho phép người dùng điều hướng một thư mục với chuỗi POST hoặc truy vấn được xây dựng cẩn thận: /../../../danger/someFileTheyTHoughtWasInaccessible
AaronLS

128

Sử dụng phiên bản phát hành của MVC, đây là những gì tôi làm:

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

Tôi rõ ràng có một số công cụ cụ thể cho ứng dụng ở đây liên quan đến việc xây dựng đường dẫn, nhưng sự trở lại của FileStreamResult rất hay và đơn giản.

Tôi đã thực hiện một số thử nghiệm hiệu suất liên quan đến hành động này đối với cuộc gọi hàng ngày của bạn đến hình ảnh (bỏ qua bộ điều khiển) và sự khác biệt giữa mức trung bình chỉ khoảng 3 mili giây (bộ điều khiển avg là 68ms, không phải bộ điều khiển là 65ms).

Tôi đã thử một số phương pháp khác được đề cập trong các câu trả lời ở đây và hiệu năng đạt được ấn tượng hơn nhiều ... một số câu trả lời của giải pháp có độ phân giải cao gấp 6 lần bộ điều khiển (các bộ điều khiển khác avg 340ms, không điều khiển 65ms).


12
Những gì về hình ảnh không được sửa đổi? FileStreamResult sẽ gửi 304 khi hình ảnh không được sửa đổi kể từ yêu cầu cuối cùng.
dariol

Bạn có thể sử dụng Path.Combinethay vì concat cho mã an toàn hơn và dễ đọc hơn.
Marcell Toth

101

Để mở rộng về phản ứng của Dyland một chút:

Ba lớp thực hiện lớp FileResult :

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

Tất cả đều khá tự giải thích:

  • Đối với tải xuống đường dẫn tệp nơi tệp tồn tại trên đĩa, hãy sử dụng FilePathResult- đây là cách dễ nhất và tránh bạn phải sử dụng Luồng.
  • Đối với mảng byte [] (gần giống với Feedback.BinaryWrite), hãy sử dụng FileContentResult.
  • Đối với mảng byte [] nơi bạn muốn tệp tải xuống (bố trí nội dung: tệp đính kèm), hãy sử dụng FileStreamResulttheo cách tương tự như bên dưới, nhưng với a MemoryStreamvà sử dụng GetBuffer().
  • Dành cho Streams sử dụng FileStreamResult. Nó được gọi là FileStreamResult nhưng phải mất một thời gian Streamnên tôi đoán nó hoạt động với a MemoryStream.

Dưới đây là một ví dụ về việc sử dụng kỹ thuật xử lý nội dung (không được kiểm tra):

    [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
Phần bố trí nội dung của bài đăng này cực kỳ hữu ích
Diego

VS đang nói với tôi rằng sự quá tải này của FileStream () đã lỗi thời.
MrBoJangles

1
Một điều cần lưu ý: nếu bạn có dấu phẩy trong tên tệp của mình, Chrome sẽ từ chối nó với lỗi "quá nhiều tiêu đề nhận được". Vì vậy, thay thế tất cả dấu phẩy bằng dấu "-" hoặc "".
Chris S

Làm thế nào một người sẽ làm điều này bằng cách chỉ sử dụng bộ điều khiển api web?
Zapnologica

74

Điều này có thể hữu ích nếu bạn muốn sửa đổi hình ảnh trước khi trả lại:

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
Cảm ơn bạn. Điều này là hoàn hảo cho kịch bản cần có proxy để tải xuống một hình ảnh yêu cầu xác thực không thể được thực hiện ở phía máy khách.
Hồng

1
Bạn đang quên loại bỏ 3 đối tượng gốc: Font, SolidBrush và Image.
Wout

3
Đề xuất cải tiến ở đây: bạn tạo một luồng bộ nhớ, ghi dữ liệu và sau đó tạo kết quả Tệp với dữ liệu bằng .ToArray () Bạn cũng có thể chỉ cần gọi ms.Seek (0, SeekOrigin.Begin) và sau đó trả về Tệp (ms, " image / png ") // trả lại luồng chính
Quango

12

Bạn có thể tạo phần mở rộng của riêng bạn và làm theo cách này.

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

Bạn có thể viết trực tiếp vào phản hồi nhưng sau đó không thể kiểm tra được. Nên trả lại một ActionResult đã hoãn thực thi. Đây là StreamResult có thể tái sử dụng của tôi:

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

Tại sao không đi đơn giản và sử dụng ~toán tử dấu ngã ?

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

6

CẬP NHẬT: Có những lựa chọn tốt hơn câu trả lời ban đầu của tôi. Điều này hoạt động bên ngoài MVC khá tốt nhưng tốt hơn là nên sử dụng các phương thức tích hợp để trả về nội dung hình ảnh. Xem câu trả lời bỏ phiếu.

Bạn chắc chắn có thể. Hãy thử các bước sau:

  1. Tải hình ảnh từ đĩa vào một mảng byte
  2. lưu trữ hình ảnh trong trường hợp bạn mong đợi nhiều yêu cầu hơn cho hình ảnh và không muốn đĩa I / O (mẫu của tôi không lưu vào bộ đệm bên dưới)
  3. Thay đổi loại mime thông qua Phản hồi.ContentType
  4. Phản hồi.BinaryWrite ra mảng byte hình ảnh

Đây là một số mã mẫu:

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

Mong rằng sẽ giúp!


4
và cái này sẽ trông như thế nào trong hành động của bộ điều khiển?
CRice

5

Giải pháp 1: Để hiển thị hình ảnh trong chế độ xem từ URL hình ảnh

Bạn có thể tạo phương thức mở rộng của riêng mình:

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

Sau đó sử dụng nó như:

@Html.Image(@Model.ImagePath);

Giải pháp 2: Để kết xuất hình ảnh từ cơ sở dữ liệu

Tạo một phương thức điều khiển trả về dữ liệu hình ảnh như bên dưới

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

Và sử dụng nó trong một khung nhìn như:

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

Để sử dụng hình ảnh được hiển thị từ hành động này trong bất kỳ HTML nào, hãy sử dụng

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

5

Điều này làm việc cho tôi. Vì tôi đang lưu trữ hình ảnh trên cơ sở dữ liệu 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;
    }

Trong đoạn trích ở trên _db.ImageFiles.Find(uuid)là tìm kiếm bản ghi tệp hình ảnh trong db (ngữ cảnh EF). Nó trả về một đối tượng FileImage chỉ là một lớp tùy chỉnh mà tôi đã tạo cho mô hình và sau đó sử dụng nó làm FileContentResult.

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

4

bạn có thể sử dụng Tệp để trả về một tệp như Xem, Nội dung, v.v.

 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

Nhìn vào ContentResult. Điều này trả về một chuỗi, nhưng có thể được sử dụng để tạo lớp giống như BinaryResult của riêng bạn.


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

SomeHelper.EmptyImageResult()sẽ trở lại FileResultvới hình ảnh hiện tại (ví dụ 1x1 trong suốt).

Đây là cách dễ nhất nếu bạn có tệp được lưu trữ trên ổ đĩa cục bộ. Nếu các tệp là byte[]hoặc stream- sau đó sử dụng FileContentResulthoặc FileStreamResultnhư Dylan đề xuất.


1

Tôi thấy hai lựa chọn:

1) Triển khai IViewEngine của riêng bạn và đặt thuộc tính ViewEngine của Trình điều khiển bạn đang sử dụng cho ImageViewEngine theo phương thức "hình ảnh" mong muốn của bạn.

2) Sử dụng chế độ xem :-). Chỉ cần thay đổi loại nội dung, vv


1
Điều này có thể gặp sự cố do có thêm khoảng trắng hoặc CRLF trong Chế độ xem.
Elan Hasson

2
Tôi đã sai trong bài viết cuối cùng của mình ... msdn.microsoft.com/en-us/l Library / 'Bạn có thể sử dụng lớp WebImage và WebImage.Write trong một chế độ xem :)
Elan Hasson

1

Bạn có thể sử dụng HttpContext.Response và trực tiếp viết nội dung cho nó (WriteFile () có thể phù hợp với bạn) và sau đó trả lại ContentResult từ hành động của bạn thay vì ActionResult.

Tuyên bố miễn trừ trách nhiệm: Tôi chưa thử điều này, nó dựa trên việc xem xét các API có sẵn. :-)


1
Vâng tôi chỉ nhận thấy ContentResult chỉ hỗ trợ các chuỗi, nhưng đủ dễ để tạo lớp dựa trên ActionResult của riêng bạn.
leppie

1

Mã dưới đây sử dụng System.Drawing.Bitmapđể tải hình ảnh.

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

Tôi cũng gặp phải yêu cầu tương tự,

Vì vậy, trong trường hợp của tôi, tôi đưa ra yêu cầu Trình điều khiển với đường dẫn thư mục hình ảnh, đổi lại sẽ gửi lại một đối tượng ImageResult.

Đoạn mã sau minh họa công việc:

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

Và trong Trình điều khiển từ đường dẫn hình ảnh, tôi tạo ra hình ảnh và trả lại cho người gọi

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

Đọc hình ảnh, chuyển đổi nó thành byte[], sau đó trả về a File()với một loại nội dung.

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

Dưới đây là cách sử dụng:

using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using Microsoft.AspNetCore.Mvc;
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.