Pode retornar um controlador ASP.NET MVC uma imagem?
-
06-07-2019 - |
Pergunta
Posso criar um controlador que simplesmente retorna um recurso de imagem?
Gostaria de rota essa lógica através de um controlador, sempre que uma URL como a seguinte é solicitada:
www.mywebsite.com/resource/image/topbanner
O controlador irá procurar topbanner.png
e enviar a imagem diretamente de volta para o cliente.
Eu vi exemplos disso onde você tem que criar uma View - Eu não quero usar um View. Quero fazer tudo isso com apenas o Controller.
Isso é possível?
Solução
Use o método controladores de base do arquivo.
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");
}
Como uma nota, este parece ser bastante eficiente. Eu fiz um teste onde eu solicitado a imagem através do controlador (http://localhost/MyController/Image/MyImage
) e através do URL direto (http://localhost/Images/MyImage.jpg
) e os resultados foram os seguintes:
- MVC: 7,6 milissegundos por foto
- Direto: 6.7 milissegundos por foto
Nota: este é o tempo médio de um pedido. A média foi calculada, fazendo milhares de solicitações na máquina local, então os totais não deve incluir latência ou largura de banda problemas de rede.
Outras dicas
Usando a versão do MVC, aqui é o que eu faço:
[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");
}
Eu, obviamente, têm algum material específico aplicativo aqui sobre a construção caminho, mas o retorno da FileStreamResult é agradável e simples.
Eu fiz algumas desempenho testar em relação a esta ação contra a sua chamada todos os dias para a imagem (ignorando o controlador) e a diferença entre as médias foi apenas cerca de 3 milissegundos (controlador avg foi 68ms, não-controlador foi 65ms).
Eu tinha tentado alguns dos outros métodos mencionados nos respostas aqui e o impacto no desempenho foi muito mais dramática ... várias das respostas soluções foram, tanto quanto 6x não-controlador (outros controladores avg 340ms, 65ms não-controlador ).
Para expland sobre a resposta da Dyland ligeiramente:
aulas Três implementar o href="http://msdn.microsoft.com/en-us/library/system.web.mvc.fileresult.aspx" rel="noreferrer"> FileResult classe :
System.Web.Mvc.FileResult
System.Web.Mvc.FileContentResult
System.Web.Mvc.FilePathResult
System.Web.Mvc.FileStreamResult
Eles são todos bastante auto-explicativo:
- Para downloads caminho do arquivo onde o arquivo existe no disco, o uso
FilePathResult
-. Esta é a maneira mais fácil e evita que você tenha que usar Streams - Para byte [] matrizes (semelhante ao Response.BinaryWrite), uso
FileContentResult
. - Para byte [] matrizes onde você deseja que o arquivo para download (content-disposition: attachment)., O uso
FileStreamResult
de uma forma semelhante ao abaixo, mas com umMemoryStream
e usandoGetBuffer()
- Para uso
Streams
FileStreamResult
. É chamado de FileStreamResult mas é preciso umStream
então eu palpite ele funciona com umMemoryStream
.
Segue-se um exemplo do uso da técnica de disposição do conteúdo (não testada):
[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;
}
Isso pode ser útil se você gostaria de modificar a imagem antes de devolvê-lo:
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");
}
Você pode criar seu próprio ramal e fazer desta forma.
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")%>
Você pode escrever diretamente para a resposta, mas então não é testável. É preferível para retornar um ActionResult que adiou a execução. Aqui é a minha StreamResult resusable:
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);
}
}
Por que não ir simples e usar o operador til ~
?
public FileResult TopBanner() {
return File("~/Content/images/topbanner.png", "image/png");
}
UPDATE: Existem opções melhores do que minha resposta inicial. Isso funciona fora do MVC muito bem, mas é melhor ficar com o built-in métodos de retornar o conteúdo da imagem. Veja as respostas up-votado.
Você certamente pode. Experimente estes passos:
- Coloque a imagem do disco para uma matriz de bytes
- cache de imagem no caso que você espera mais pedidos para a imagem e não quer que o disco I / O (minha amostra não armazena em cache-lo abaixo)
- Alterar o tipo de mime através da Response.ContentType
- Response.BinaryWrite a matriz da imagem byte
Aqui está um código de exemplo:
string pathToFile = @"C:\Documents and Settings\some_path.jpg";
byte[] imageData = File.ReadAllBytes(pathToFile);
Response.ContentType = "image/jpg";
Response.BinaryWrite(imageData);
Espero que ajude!
Solução 1: Para renderizar uma imagem em uma exibição de uma imagem URL
Você pode criar seu próprio método de extensão:
public static MvcHtmlString Image(this HtmlHelper helper,string imageUrl)
{
string tag = "<img src='{0}'/>";
tag = string.Format(tag,imageUrl);
return MvcHtmlString.Create(tag);
}
Em seguida, usá-lo como:
@Html.Image(@Model.ImagePath);
Solução 2: Para rendem a imagem do banco de dados
Crie um método controlador que dados de imagem retorna como abaixo
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);
}
}
E usá-lo em uma visão como:
@ { Html.RenderAction("View","Image",new {id=@Model.ImageId})}
Para usar uma imagem renderizada a partir deste ActionResult em qualquer HTML, uso
<img src="http://something.com/image/view?id={imageid}>
Isso funcionou para mim. Desde que eu estou armazenar imagens em um banco de dados 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;
}
No trecho acima _db.ImageFiles.Find(uuid)
está buscando o registro arquivo de imagem no db (contexto EF). Ele retorna um objeto FileImage que é apenas uma classe personalizada que fiz para o modelo e, em seguida, usa-lo como FileContentResult.
public class FileImage {
public string Uuid { get; set; }
public byte[] Data { get; set; }
public string ContentType { get; set; }
}
Olhe para ContentResult. Isso retorna uma string, mas pode ser usado para fazer o seu próprio BinaryResult-like classe.
Você pode usar Arquivo para retornar um arquivo como View, conteúdo etc
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>"); }
}
Eu vejo duas opções:
1) implementar seu próprio IViewEngine e defina a propriedade ViewEngine do controlador que você está usando para seu ImageViewEngine em seu método desejado "imagem".
2) Use uma visão :-). Basta alterar o tipo de conteúdo etc.
Você pode usar o HttpContext.Response e directamente escrever o conteúdo para ele (WriteFile () pode funcionar para você) e, em seguida, retornar ContentResult de sua ação em vez de ActionResult.
Disclaimer: Eu não tentei isso, ele é baseado em olhar para as APIs disponíveis. : -)
if (!System.IO.File.Exists(filePath))
return SomeHelper.EmptyImageResult(); // preventing JSON GET/POST exception
else
return new FilePathResult(filePath, contentType);
SomeHelper.EmptyImageResult()
deve retornar FileResult
com imagem existente (1x1 transparente, por exemplo).
Esta é a maneira mais fácil se você tem arquivos armazenados na unidade local.
Se os arquivos são byte[]
ou stream
-., Em seguida, FileContentResult
uso ou FileStreamResult
como Dylan sugeriu