Чи може контролер 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
Виротек

2
Якщо ви хочете змінити зображення, використовуйте ImageResizing.Net HttpModule для найкращої продуктивності. Якщо цього не зробити, FilePathResult додає лише кілька відсотків накладних витрат. Переписування URL-адрес додає трохи менше.
Річка Ліліт

1
Чому б не використовувати контролер WebApi замість MVC? ApiController class
А-Шарабіані

Відповіді:


534

Використовуйте базові контролери Метод файлів.

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, коли зображення не змінено з останнього запиту.
dariol

Path.CombineДля безпечнішого і читабельнішого коду ви можете використовувати замість concat.
Марселл Тот

101

Щоб трохи пояснити відповідь Діланда:

Три класи реалізують клас 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аналогічно наведеному нижче, але з a MemoryStreamі using GetBuffer().
  • Для Streamsвикористання FileStreamResult. Це називається FileStreamResult, але це займає Streamтак, я б припустив, що він працює з a 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 відхилить його з помилкою "отримано занадто багато заголовків". Тому замініть всі коми на "-" або "".
Кріс S

Як би це зробити, використовуючи лише веб-контролери 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 рідні об’єкти: Font, SolidBrush та Image.
Wout

3
Тут пропонується вдосконалення: ви створюєте потік пам'яті, записуєте дані, а потім створюєте результат файлу з даними, використовуючи .ToArray () Ви також можете просто зателефонувати ms.Seek (0, SeekOrigin.Begin), а потім повернути файл (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

Чому б не піти просто і не скористатися ~оператором tilde ?

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

6

ОНОВЛЕННЯ: Є кращі варіанти, ніж моя оригінальна відповідь. Це працює поза MVC досить добре, але краще дотримуватися вбудованих методів повернення вмісту зображення. Дивіться відповіді, проголосовані вище.

Ви, звичайно, можете. Спробуйте виконати наступні дії:

  1. Завантажте зображення з диска в масив байтів
  2. кешуйте зображення у випадку, якщо ви очікуєте більше запитів до зображення та не хочете, щоб диск вводу / виводу (мій зразок не кешував його нижче)
  3. Змініть тип mime за допомогою типу Response.ContentType
  4. Response.BinaryWrite write масив байтів зображення

Ось приклад коду:

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)виконується пошук запису файлу зображень у db (контексті EF). Він повертає об'єкт FileImage, який є просто спеціальним класом, який я створив для моделі, а потім використовує його як FileContentResult.

public class FileImage {
   public string Uuid { get; set; }
   public byte[] Data { get; set; }
   public string ContentType { get; set; }
}

4

ви можете використовувати файл для повернення файлів, таких як "Перегляд", "Вміст" тощо

 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 із наявним зображенням (наприклад, 1х1 прозорий).

Це найпростіший спосіб, якщо у вас є файли, що зберігаються на локальному диску. Якщо файли є byte[]або stream- використовуйте FileContentResultабо так, FileStreamResultяк запропонував Ділан.


1

Я бачу два варіанти:

1) Реалізуйте свій власний IViewEngine та встановіть властивість ViewEngine контролера, який ви використовуєте, для вашого ImageViewEngine у ​​потрібному вами методі "image".

2) Використовуйте вид :-). Просто змініть тип вмісту тощо.


1
Це може бути проблемою через додаткові пробіли або CRLF у представленні.
Елан Хассон

2
Я помилявся у своєму останньому дописі ... msdn.microsoft.com/en-us/library/… Ви можете використовувати клас WebImage та WebImage.Написати у вигляді :)
Елан Хассон,

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.