ASP.NET MVC控制器可以返回一个Image吗?

Can an ASP.NET MVC controller return an Image?

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

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

1
www.mywebsite.com/resource/image/topbanner

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

我看到过这样的例子,你必须创建一个视图-我不想使用一个视图。我只想用控制器来完成这一切。

这有可能吗?


使用基本控制器文件方法。

1
2
3
4
5
6
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毫秒

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


使用MVC的发布版本,我要做的是:

1
2
3
4
5
6
7
[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倍(其他控制器平均340ms,非控制器65ms)。


稍微解释一下迪兰的反应:

三个类实现fileresult类:

1
2
3
4
System.Web.Mvc.FileResult
      System.Web.Mvc.FileContentResult
      System.Web.Mvc.FilePathResult
      System.Web.Mvc.FileStreamResult

它们都是不言自明的:

  • 对于文件存在于磁盘上的文件路径下载,使用FilePathResult–这是最简单的方法,避免了必须使用流。
  • 对于byte[]数组(类似于response.binarywrite),使用FileContentResult
  • 对于要下载文件的byte[]数组(内容处置:附件),使用FileStreamResult,方法与下面的方法类似,但使用MemoryStreamGetBuffer()
  • 对于Streams使用FileStreamResult。它被称为filestreamresult,但它需要一个Stream,所以我想它可以与一个MemoryStream一起工作。

下面是使用内容处置技术(未测试)的示例:

1
2
3
4
5
6
7
8
9
10
    [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;
    }


如果您想在返回图像之前对其进行修改,这可能会有所帮助:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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")%>

为什么不简单地使用tilde ~操作符?

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


您可以直接写入响应,但之后它就不可测试了。最好返回延迟执行的actionResult。以下是我的可恢复流结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);
    }
}

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

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

  • 将图像从磁盘加载到字节数组中
  • 缓存该映像,以防您对该映像有更多请求,并且不需要磁盘I/O(我的示例不在下面缓存它)
  • 通过response.contenttype更改mime类型
  • response.binary写出图像字节数组
  • 下面是一些示例代码:

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

    希望有帮助!


    这对我很有用。因为我在SQL Server数据库中存储图像。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
        [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)正在数据库(ef上下文)中搜索图像文件记录。它返回一个FileImage对象,它只是我为模型创建的一个自定义类,然后将其用作FileContentResult。

    1
    2
    3
    4
    5
    public class FileImage {
       public string Uuid { get; set; }
       public byte[] Data { get; set; }
       public string ContentType { get; set; }
    }

    解决方案1:从图像URL在视图中呈现图像

    您可以创建自己的扩展方法:

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

    然后像这样使用:

    1
    @Html.Image(@Model.ImagePath);

    解决方案2:从数据库渲染图像

    创建一个控制器方法,返回如下图像数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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);
      }
    }

    在以下视图中使用它:

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

    若要在任何HTML中使用从此操作结果呈现的图像,请使用

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

    您可以使用文件返回文件,如视图、内容等

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
     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(" Patient Clinical Document Not Uploaded"); }

                }


    看看contentresult。这将返回一个字符串,但可以用于生成自己的类似于binaryResult的类。


    1
    2
    3
    4
    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,那么使用FileContentResultFileStreamResult,正如dylan建议的那样。


    您可以使用httpContext.response并直接将内容写入其中(writeFile()可能对您有效),然后从操作返回contentResult而不是actionResult。

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


    我看到两种选择:

    1)实现自己的iviewEngine,并在所需的"image"方法中将所用控制器的viewEngine属性设置为imageviewEngine。

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