Может ли контроллер ASP.NET MVC вернуть изображение?

Могу ли я создать контроллер, который просто возвращает ресурс изображения?

Я хотел бы направить эту логику через контроллер всякий раз, когда запрашивается URL-адрес, подобный следующему:

www.mywebsite.com/resource/image/topbanner

Контроллер найдет topbanner.png и отправит это изображение прямо обратно клиенту.

Я видел примеры этого, когда вам нужно создать представление - я не хочу использовать представление. Я хочу делать все это только с помощью контроллера.

Это возможно?


person Jonathan    schedule 09.10.2008    source источник
comment
Я задал аналогичный вопрос здесь https://stackoverflow.com/questions/155906/creating-a-private-photo-gallery-using-aspnet-mvc и в итоге нашел для этого отличное руководство. Следуя этому руководству, я создал класс ImageResult. https://blog.maartenballiauw.be/post/2008/05/13/aspnet-mvc-custom-actionresult.html   -  person Vyrotek    schedule 22.10.2008
comment
Если вы хотите изменить изображение, используйте ImageResizing.Net HttpModule для повышения производительности. Если вы этого не сделаете, FilePathResult добавляет лишь несколько процентов накладных расходов. Перезапись URL добавляет немного меньше.   -  person Lilith River    schedule 16.10.2011
comment
Почему бы не использовать WebApi Controller вместо MVC? ApiController class   -  person A-Sharabiani    schedule 02.06.2016


Ответы (19)


Используйте метод 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 миллисекунды на фото.

Примечание: это среднее время запроса. Среднее значение было рассчитано путем выполнения тысяч запросов на локальном компьютере, поэтому итоговые значения не должны включать в себя задержку в сети или проблемы с пропускной способностью.

person Brian    schedule 28.08.2009
comment
Для тех, кто сейчас задается этим вопросом, это решение, которое лучше всего сработало для меня. - person Clarence Klopfstein; 25.12.2009
comment
Отличный ответ, Брайан. Во-первых, параметр contentType в вызове метода File () в вашем примере должен быть image / jpeg. В противном случае IE8 выдает диалоговое окно загрузки. И этот ответ следует выбрать. - person Jason Slocomb; 26.05.2010
comment
Это небезопасный код. Разрешение пользователю передавать имя файла (путь) таким образом означает, что он потенциально может получить доступ к файлам из любого места на сервере. Можно предупредить людей, чтобы они не использовали его как есть. - person Ian Mercer; 28.01.2011
comment
Этот ответ не имеет смысла. Если вы работаете с файлами, которые сохранены на сервере в какой-либо папке в корне вашего веб-приложения, лучше и быстрее просто использовать помощники Views и Html или Url MVC, чтобы построить путь к изображениям для тега ‹img› или создайте собственных помощников для полной рендеринга тега ‹img›. Единственный раз, когда имеет смысл использовать FileResult, - это когда у вас есть файлы, изображения или любой другой вид содержимого byte [], сохраненный в хранилище данных. - person mare; 26.06.2011
comment
Если только вы не создаете файлы на лету по мере необходимости и не кешируете их после создания (это то, что мы делаем). - person Brian; 27.06.2011
comment
Я использовал это, чтобы сделать то же самое с PDF. +1 Спасибо - person Matt; 13.10.2011
comment
@ mare - вы также можете сделать это, если вы обслуживаете файлы из ограниченного места, например у вас могут быть изображения в App_Data, которые должны быть подписаны одними пользователями вашего приложения, но не другими. Использование действия контроллера для их обслуживания позволяет ограничить доступ. - person Russ Cam; 19.10.2011
comment
Я не уверен, но быстрый тест этого решения показал, что оно игнорирует заголовки If-Modified-Since и всегда возвращает файл. В некоторых ситуациях это может быть желательно, но это приведет к обходу кэширования на стороне клиента и ухудшит производительность. - person RonnBlack; 08.06.2012
comment
Как уже упоминалось, будьте осторожны при построении пути, поскольку я видел фактический производственный код, который позволял пользователю перемещаться вверх по каталогу с тщательно созданной строкой запроса POST или запроса: /../../../danger/someFileTheyTHoughtWasInaccessible - person AaronLS; 30.04.2014

Используя релизную версию 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 миллисекунд (среднее значение контроллера - 68 мс, неконтроллер - 65 мс).

Я пробовал некоторые другие методы, упомянутые в ответах здесь, и падение производительности было гораздо более драматичным ... ответы на некоторые решения были в 6 раз больше, чем неконтроллер (другие контроллеры в среднем 340 мс, неконтроллер 65 мс).

person Sailing Judo    schedule 15.04.2009
comment
Что с изображением не модифицируется? FileStreamResult должен отправить 304, если изображение не изменилось с момента последнего запроса. - person dariol; 03.10.2010
comment
Вы можете использовать Path.Combine вместо concat для более безопасного и читаемого кода. - person Marcell Toth; 07.11.2018

Чтобы немного пояснить ответ Дайланда:

Три класса реализуют класс FileResult:

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

Все они говорят сами за себя:

  • Для загрузки по пути к файлу, когда файл существует на диске, используйте FilePathResult - это самый простой способ, позволяющий избежать использования потоков.
  • Для массивов byte [] (похожих на Response.BinaryWrite) используйте FileContentResult.
  • Для массивов byte [], в которые вы хотите загрузить файл (content-disposition: attachment), используйте 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;
    }
person Chris S    schedule 29.09.2009
comment
Часть этого сообщения, посвященная размещению контента, была чрезвычайно полезной. - person Diego; 28.10.2010
comment
VS сообщает мне, что эта перегрузка FileStream () устарела. - person MrBoJangles; 24.01.2012
comment
На заметку: если у вас есть запятая в имени файла, Chrome отклонит его с ошибкой получения слишком большого количества заголовков. Поэтому замените все запятые на - или. - person Chris S; 19.04.2012

Это может быть полезно, если вы хотите изменить изображение перед его возвратом:

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");
}
person staromeste    schedule 28.03.2011
comment
Спасибо. Это идеально подходит для сценария, когда требуется прокси-сервер для загрузки изображения, требующего аутентификации, которая не может быть выполнена на стороне клиента. - person Hong; 24.03.2012
comment
Вы забываете избавиться от колоссальных трех собственных объектов: Font, SolidBrush и Image. - person Wout; 25.09.2012
comment
Предлагаемое улучшение здесь: вы создаете поток памяти, записываете данные, а затем создаете результат File с данными, используя .ToArray (). Вы также можете просто вызвать ms.Seek (0, SeekOrigin.Begin), а затем вернуть File (ms, image / png) // возвращаем сам поток - person Quango; 13.12.2012

Вы можете создать собственное расширение и поступить таким образом.

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")%>
person Oleksandr Fentsyk    schedule 09.06.2011

Почему бы не пойти проще и не использовать оператор тильды ~?

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

Вы можете написать прямо в ответ, но тогда его нельзя будет проверить. Предпочтительно возвращать 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);
    }
}
person JarrettV    schedule 09.10.2008

ОБНОВЛЕНИЕ: есть варианты получше, чем мой первоначальный ответ. Это неплохо работает вне MVC, но лучше придерживаться встроенных методов возврата содержимого изображения. Просмотрите ответы, получившие одобрение.

Вы, конечно, можете. Попробуйте выполнить следующие действия:

  1. Загрузите изображение с диска в массив байтов
  2. кэшировать изображение в случае, если вы ожидаете большего количества запросов на изображение и не хотите дискового ввода-вывода (мой образец не кэширует его ниже)
  3. Измените тип пантомимы через Response.ContentType.
  4. Response.Binary Записать байтовый массив изображения

Вот пример кода:

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

Надеюсь, это поможет!

person Ian Suttle    schedule 09.10.2008
comment
и как это будет выглядеть в действии контроллера? - person CRice; 08.04.2011

Решение 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 {[email protected]})}

Чтобы использовать изображение, отображаемое в результате этого действия, в любом HTML, используйте

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

Это сработало для меня. Поскольку я храню изображения в базе данных 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) ищет запись файла изображения в базе данных (контекст EF). Он возвращает объект FileImage, который является просто настраиваемым классом, который я создал для модели, а затем использует его как FileContentResult.

public class FileImage {
   public string Uuid { get; set; }
   public byte[] Data { get; set; }
   public string ContentType { get; set; }
}
person hmojica    schedule 10.04.2018

вы можете использовать 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>"); }

            }
person Avinash Urs    schedule 16.03.2016

В приведенном ниже коде используется 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();
    bitmap.Save(ms, ImageFormat.Jpeg);
    ms.Position = 0;
    return new FileStreamResult(ms, "image/jpeg");
}
person Youngjae    schedule 15.10.2019
comment
Протестировано, но исправлена ​​небольшая ошибка в этом примере кода. Используется Edit .. Результат должен быть растровым изображением в операторе Save. Действительно полезный пример, спасибо! +1 - person Goodies; 19.07.2020

Посмотрите ContentResult. Это возвращает строку, но может использоваться для создания вашего собственного класса, подобного BinaryResult.

person leppie    schedule 09.10.2008

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, как предложил Дилан.

person Victor Gelmutdinov    schedule 18.02.2010

Я вижу два варианта:

1) Внедрите свой собственный IViewEngine и установите свойство ViewEngine контроллера, который вы используете, для вашего ImageViewEngine в желаемом методе «изображения».

2) Используйте вид :-). Просто измените тип контента и т. Д.

person Matt Mitchell    schedule 09.10.2008
comment
Это может быть проблемой из-за лишних пробелов или CRLF в представлении. - person Elan Hasson; 12.09.2011
comment
Я ошибся в своем последнем сообщении ... msdn.microsoft.com/en-us/library/ Вы можете использовать класс WebImage и WebImage.Write в представлении :) - person Elan Hasson; 12.09.2011

Вы можете использовать HttpContext.Response и напрямую записывать в него контент (WriteFile () может работать для вас), а затем возвращать ContentResult из вашего действия вместо ActionResult.

Отказ от ответственности: я не пробовал это, это основано на просмотре доступных API. :-)

person Franci Penov    schedule 09.10.2008
comment
Да, я только что заметил, что ContentResult поддерживает только строки, но достаточно легко создать свой собственный класс на основе ActionResult. - person leppie; 09.10.2008

Я также столкнулся с аналогичным требованием,

Поэтому в моем случае я делаю запрос к контроллеру с путем к папке с изображениями, который, в свою очередь, отправляет обратно объект 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();
}
person Shriram Navaratnalingam    schedule 20.08.2019

Прочтите изображение, преобразуйте его в 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;
person mekb    schedule 21.12.2019

Да, вы можете вернуть изображение

public ActionResult GetImage(string imageFileName)
{
    var path = Path.Combine(Server.MapPath("/Images"), imageFileName + ".jpg"); 
    return base.File(path, "image/jpeg");
}

(Не забудьте отметить это как ответ)

person Imran    schedule 17.09.2020