Может ли контроллер ASP.NET MVC вернуть изображение?
Могу ли я создать контроллер, который просто возвращает актив изображения?
Я хотел бы направить эту логику через контроллер, всякий раз, когда запрашивается такой URL-адрес:
www.mywebsite.com/resource/image/topbanner
Контроллер будет искать topbanner.png
и отправьте это изображение обратно клиенту.
Я видел примеры этого, где вы должны создать представление - я не хочу использовать представление. Я хочу сделать все это только с помощью контроллера.
Это возможно?
20 ответов
Используйте метод 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 миллисекунд на фото
Примечание: это среднее время запроса. Среднее значение было рассчитано на основе тысяч запросов на локальном компьютере, поэтому в общее число не должны входить задержки сети или проблемы с пропускной способностью.
Используя релизную версию 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 мс).
Чтобы немного расширить ответ Диланда:
Три класса реализуют класс 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;
}
Это может быть полезно, если вы хотите изменить изображение перед его возвратом:
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");
}
Вы можете создать собственное расширение и сделать это так.
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")%>
Почему бы не пойти просто и использовать тильду ~
оператор?
public FileResult TopBanner() {
return File("~/Content/images/topbanner.png", "image/png");
}
Вы можете написать непосредственно в ответ, но тогда это не проверяется. Предпочтительно возвращать 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);
}
}
Ниже код использует 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");
}
ОБНОВЛЕНИЕ: Есть лучшие варианты, чем мой первоначальный ответ. Это хорошо работает вне 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);
Надеюсь, это поможет!
Решение 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}>
Это сработало для меня. Так как я храню изображения в базе данных 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; }
}
Вы можете использовать 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>"); }
}
Посмотрите на ContentResult. Это возвращает строку, но может использоваться для создания вашего собственного класса, похожего на BinaryResult.
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
как предположил Дилан.
Прочтите изображение, преобразуйте его в 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;
Да, вы можете вернуть изображение
public ActionResult GetImage(string imageFileName)
{
var path = Path.Combine(Server.MapPath("/Images"), imageFileName + ".jpg");
return base.File(path, "image/jpeg");
}
(Не забудьте отметить это как ответ)
Я вижу два варианта:
1) Реализуйте свой собственный IViewEngine и установите свойство ViewEngine используемого вами контроллера в ImageViewEngine желаемым методом "image".
2) Используйте представление:-). Просто измените тип контента и т. Д.
I also encountered similar requirement,
So in my case I make a request to Controller with the image folder path, which in return sends back a ImageResult object.
Following code snippet illustrate the work:
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>";
And in the Controller from the the image path I produce the image and return it back to the caller
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();
}
Вы можете использовать HttpContext.Response и напрямую записать в него содержимое (WriteFile() может работать для вас), а затем вернуть ContentResult из вашего действия вместо ActionResult.
Отказ от ответственности: я не пробовал это, он основан на просмотре доступных API.:-)
Из byte[] в Core 3.2 вы можете использовать:
public ActionResult Img(int? id) {
MemoryStream ms = new MemoryStream(GetBytes(id));
return new FileStreamResult(ms, "image/png");
}