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

386

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

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

www.mywebsite.com/resource/image/topbanner

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

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

Возможно ли это?

Показать ещё 1 комментарий
Теги:
asp.net-mvc
image
controller

16 ответов

462
Лучший ответ

Используйте базовый контроллер Файловый метод.

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 миллисекунды за фотографию
  • Direct: 6.7 миллисекунды за фотографию

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

  • 9
    Для тех, кто сейчас сталкивается с этим вопросом, это решение оказалось лучшим для меня.
  • 2
    Отличный ответ Брайан. Во-первых, параметр contentType в вызове метода File () в вашем примере должен быть «image / jpeg». В противном случае IE8 открывает диалоговое окно загрузки. Также этот ответ должен быть выбран.
Показать ещё 12 комментариев
116

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

  • 12
    Как насчет изображения не изменяется? FileStreamResult должен отправить 304, когда изображение не было изменено с момента последнего запроса.
  • 0
    Вы можете использовать Path.Combine вместо Path.Combine для более безопасного и удобочитаемого кода.
100

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

Три класса реализуют класс 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;
    }
  • 2
    Часть содержания этого поста была чрезвычайно полезна
  • 0
    VS говорит мне, что эта перегрузка FileStream () устарела.
Показать ещё 2 комментария
68

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

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
    Спасибо. Это идеально подходит для сценария, когда прокси-сервер необходим для загрузки изображения, требующего аутентификации, которую нельзя выполнить на стороне клиента.
  • 0
    Вы забыли избавиться от колоссальных 3 нативных объектов: Font, SolidBrush и Image.
Показать ещё 1 комментарий
10

Вы можете создать собственное расширение и сделать это.

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")%>
9

Я задал аналогичный вопрос здесь Создание частной фотогалереи с использованием Asp.Net MVC и в итоге нашел отличное руководство для этого.

После этого руководства я создал класс ImageResult. https://blog.maartenballiauw.be/post/2008/05/13/aspnet-mvc-custom-actionresult.html

8

Вы можете напрямую написать ответ, но тогда это невозможно проверить. Предпочтительно возвращать ActionResult с отложенным исполнением. Вот мой resusable 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);
    }
}
7

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

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

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

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

  • Загрузите изображение с диска в массив байтов
  • кэшируйте изображение в случае, если вы ожидаете большего количества запросов для изображения и не хотите ввода/вывода на диске (мой пример не кэширует его ниже)
  • Измените тип mime с помощью Response.ContentType
  • Response.BinaryЗарегистрируйте массив байтов изображений

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

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

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

  • 3
    и как это будет выглядеть в действии контроллера?
2

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

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

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

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

1

Это сработало для меня. Поскольку я храню изображения в базе данных 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; }
}
1
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, как предложил Дилан.

0

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

            }
0

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

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

  • 0
    Да, я только что заметил, что ContentResult поддерживает только строки, но достаточно просто создать свой собственный класс на основе ActionResult.
0

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

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

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

  • 0
    Это может быть проблемой из-за лишних пробелов или CRLF в представлении.
  • 1
    Я был неправ в своем последнем посте ... msdn.microsoft.com/en-us/library/… Вы можете использовать класс WebImage и WebImage.Write в представлении :)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню