ASP.NET MVC控制器可以返回图像吗?


455

我可以创建一个仅返回图像资产的控制器吗?

每当请求以下URL时,我都希望通过控制器路由此逻辑:

www.mywebsite.com/resource/image/topbanner

控制器将查找topbanner.png并将该图像直接发送回客户端。

我看过这样的示例,其中您必须创建一个View-我不想使用View。我想只用Controller来完成所有操作。

这可能吗?


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重写增加了一些。
莉莉丝河

1
为什么不使用WebApi Controller代替MVC?ApiController class
A-Sharabiani

Answers:


534

使用基本控制器的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");
}

需要注意的是,这似乎相当有效。我做了一个测试,我通过控制器(http://localhost/MyController/Image/MyImage)和直接URL(http://localhost/Images/MyImage.jpg)请求图像,结果是:

  • MVC:每张照片7.6毫秒
  • 直接:每张照片6.7毫秒

注意:这是请求的平均时间。平均值是通过在本地计算机上发出数千个请求来计算的,因此总数不应包含网络延迟或带宽问题。


10
对于那些现在遇到这个问题的人来说,这是最适合我的解决方案。
克拉伦斯·克洛普斯坦

177
这不是安全的代码。让用户传递这样的文件名(路径)意味着他们可以从服务器上的任何位置访问文件。可能要警告人们不要按原样使用它。
伊恩·默瑟

7
除非您根据需要动态构建文件并在文件创建后对其进行缓存(这就是我们要做的)。
布莱恩(Brian

15
@ mare-如果从受限制的位置提供文件,也可能会执行此操作,例如,您可能在App_Data其中包含应由应用程序的某些用户而非其他用户签名的图像。使用控制器动作为它们提供服务可以限制访问。
Russ Cam

8
正如其他人提到的那样,在构建路径时要谨慎,因为我已经看到了实际的生产代码,该代码允许用户使用精心构建的POST或查询字符串浏览目录:/../../../danger/someFileTheyTHoughtWasInaccessible
AaronLS 2014年

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,非控制器平均时间为65ms)。

我尝试了此处答案中提到的其他方法,性能受到的影响要大得多……一些解决方案的响应高达非控制器的6倍(其他控制器平均340毫秒,非控制器65毫秒)。


12
图像未修改怎么办?自上次请求以来未修改图像时,FileStreamResult应发送304。
dariol 2010年

您可以使用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。
  • 对于byte []数组(类似于Response.BinaryWrite),请使用FileContentResult
  • 对于要在其中下载文件的byte []数组(content-disposition:附件),请使用FileStreamResult与以下类似的方式,但要使用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 2012年

1
注意事项:如果文件名中包含逗号,Chrome会拒绝它,并显示“收到太多标头”错误。因此,请用“-”或“”替换所有逗号。
克里斯·S

仅使用Web 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
谢谢。这对于需要代理下载无法在客户端上进行身份验证的映像的情况非常理想。

1
您忘记了处理多达3个本机对象:字体,SolidBrush和图像。
Wout 2012年

3
建议的改进:创建一个内存流,写入数据,然后使用.ToArray()使用数据创建File结果。您也可以只调用ms.Seek(0,SeekOrigin.Begin),然后返回File(ms,“ image / png“)//返回流本身
Quango 2012年

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

为什么不简化并使用波浪号~运算符?

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

6

更新:有比我原始答案更好的选择。这在MVC之外效果很好,但是最好坚持使用内置的返回图像内容的方法。查看投票结果。

当然可以。尝试以下步骤:

  1. 将磁盘中的图像加载到字节数组中
  2. 在您希望获得更多图像请求并且不希望磁盘I / O的情况下缓存该图像(我的示例不在下面缓存它)
  3. 通过Response.ContentType更改MIME类型
  4. Response.Binary写出图像字节数组

这是一些示例代码:

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

希望有帮助!


4
在控制器的动作中会如何看待?
CRice 2011年

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

您可以使用File返回文件,例如View,Content等

 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-,则按照Dylan建议使用FileContentResultFileStreamResult


1

我看到两个选择:

1)实现自己的IViewEngine,并以所需的“图像”方法将要使用的Controller的ViewEngine属性设置为ImageViewEngine。

2)使用视图:-)。只需更改内容类型等。


1
由于视图中有多余的空间或CRLF,这可能会造成麻烦。
伊兰·哈森

2
我 在上一篇文章中写错了... msdn.microsoft.com/zh-cn/library / ... 您可以在视图中使用WebImage类和WebImage.Write :)
Elan Hasson

1

您可以使用HttpContext.Response并将内容直接写入其中(WriteFile()可能对您有用),然后从您的操作而不是ActionResult返回ContentResult。

免责声明:我没有尝试过,它基于查看可用的API。:-)


1
是的,我刚刚注意到ContentResult仅支持字符串,但创建基于ActionResult的类很容易。
嬉皮

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

然后在Controller中,通过图像路径生成图像并将其返回给调用者

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;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.