Может ли контроллер 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
Если вы хотите изменить изображение, используйте HttpModule ImageResizing.Net для лучшей производительности. Если вы этого не сделаете, FilePathResult добавляет только несколько процентов накладных расходов. Перезапись URL добавляет немного меньше.
Река Лилит

1
Почему бы не использовать WebApi Controller вместо MVC? ApiController class
A-Sharabiani

Ответы:


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
Если вы не создаете файлы на лету, когда они нужны, и кэшируете их, когда они создаются (это то, что мы делаем).
Брайан

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

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


12
Как насчет изображения не модифицируется? FileStreamResult должен отправить 304, когда изображение не было изменено с момента последнего запроса.
Дариол

Вы можете использовать Path.Combineвместо concat более безопасный и читаемый код.
Марсель Тот

101

Чтобы немного расширить ответ Диланда:

Три класса реализуют класс 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: attachment), используйте FileStreamResultаналогично приведенному ниже, но с a MemoryStreamи using 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
Содержательная часть этого поста была чрезвычайно полезна
Диего

VS говорит мне, что эта перегрузка FileStream () устарела.
MrBoJangles

1
Обратите внимание: если в имени файла есть запятая, Chrome отклонит ее с ошибкой «слишком много заголовков получено». Поэтому замените все запятые на «-» или «».
Крис С.

Как можно было бы сделать это, используя только контроллеры веб-API?
Запнологика

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 нативных объектов: Font, SolidBrush и Image.
Wout

3
Предлагаемое улучшение: вы создаете поток памяти, записываете данные, а затем создаете результат File с данными, используя .ToArray (). Вы также можете просто вызвать ms.Seek (0, SeekOrigin.Begin) и затем вернуть File (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

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

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

6

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

Вы, конечно, можете. Попробуйте эти шаги:

  1. Загрузить изображение с диска в байтовый массив
  2. кэшируйте изображение в случае, если вы ожидаете больше запросов на изображение и не хотите дискового ввода-вывода (мой пример не кэширует его ниже)
  3. Измените тип MIME через 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);

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


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)выполняется поиск записи файла изображения в БД (контекст 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- тогда используйте FileContentResultили, FileStreamResultкак предложил Дилан.


1

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

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

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


1
Это может быть проблемой из-за лишних пробелов или CRLF в представлении.
Элан Хассон

2
Я был неправ в своем последнем посте ... msdn.microsoft.com/en-us/library/… Вы можете использовать класс WebImage и WebImage.Write в виде :)
Elan Hasson

1

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

Отказ от ответственности: я не пробовал это, он основан на просмотре доступных 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

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

Поэтому в моем случае я делаю запрос в Controller с путем к папке с изображениями, который в свою очередь отправляет обратно объект 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;
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.