Um controlador ASP.NET MVC pode retornar uma imagem?

455

Posso criar um controlador que simplesmente retorna um ativo de imagem?

Gostaria de rotear essa lógica por meio de um controlador, sempre que uma URL como a seguinte for solicitada:

www.mywebsite.com/resource/image/topbanner

O controlador procurará topbanner.pnge enviará a imagem diretamente de volta para o cliente.

Eu já vi exemplos disso em que você precisa criar uma View - não quero usar uma View. Eu quero fazer tudo com apenas o controlador.

Isso é possível?

Jonathan
fonte
1
Fiz uma pergunta semelhante aqui /programming/155906/creating-a-private-photo-gallery-using-aspnet-mvc e acabei encontrando um ótimo guia para fazer isso. Criei uma classe ImageResult seguindo este guia. https://blog.maartenballiauw.be/post/2008/05/13/aspnet-mvc-custom-actionresult.html
Vyrotek 22/10/2008
2
Se você deseja modificar a imagem, use o ImageResizing.Net HttpModule para obter o melhor desempenho. Caso contrário, um FilePathResult adiciona apenas alguns por cento da sobrecarga. A reescrita de URL adiciona um pouco menos.
Lilith River
1
Por que não usar o WebApi Controller em vez do MVC? ApiController class
A-Sharabiani

Respostas:

534

Use o método de arquivo dos controladores base.

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, isso parece ser bastante eficiente. Fiz um teste em que solicitei a imagem através do controller ( http://localhost/MyController/Image/MyImage) e através da URL direta ( http://localhost/Images/MyImage.jpg) e os resultados foram:

  • MVC: 7,6 milissegundos por foto
  • Direto: 6,7 milissegundos por foto

Nota: este é o tempo médio de uma solicitação. A média foi calculada fazendo milhares de solicitações na máquina local; portanto, os totais não devem incluir problemas de latência ou largura de banda da rede.

Brian
fonte
10
Para aqueles que estão entrando nessa questão agora, essa foi a solução que melhor funcionou para mim.
Clarence Klopfstein 25/12/2009
177
Este não é um código seguro. Permitir que o usuário passe um nome de arquivo (caminho) como esse significa que eles poderiam acessar arquivos de qualquer lugar do servidor. Pode querer avisar as pessoas para não usá-lo como está.
Ian Mercer
7
A menos que você esteja construindo os arquivos em tempo real conforme necessário e armazenando em cache quando eles forem criados (é isso que fazemos).
27711 Brian
15
@ mare- você também pode fazer isso se estiver exibindo arquivos de um local restrito, por exemplo, você pode ter imagens App_Dataque devem ser assinadas por alguns usuários do seu aplicativo, mas não por outros. O uso de uma ação do controlador para atendê-los permite restringir o acesso.
Russ Cam
8
Como outros já mencionaram, seja cauteloso na criação de seu caminho, pois vi o código de produção real que permitia ao usuário navegar em um diretório com POST ou sequência de consulta cuidadosamente construída: /../../../danger/someFileTheyTHoughtWasInaccessible
AaronLS
128

Usando a versão do MVC, aqui está 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");
}

Obviamente, tenho algumas coisas específicas de aplicativos aqui relacionadas à construção do caminho, mas o retorno do FileStreamResult é agradável e simples.

Fiz alguns testes de desempenho em relação a essa ação em relação à sua chamada diária para a imagem (ignorando o controlador) e a diferença entre as médias foi de apenas cerca de 3 milissegundos (o valor médio do controlador foi de 68ms e o não-controlador foi de 65ms).

Eu tentei alguns dos outros métodos mencionados nas respostas aqui e o desempenho foi muito mais dramático ... várias das respostas das soluções chegaram a 6x o não-controlador (outros controladores avg 340ms, non-controller 65ms).

Vela Judô
fonte
12
E a imagem não é modificada? FileStreamResult deve enviar 304 quando a imagem não é modificada desde a última solicitação.
dariol 03/10/10
Você pode usar em Path.Combinevez da concat para obter um código mais seguro e legível.
Marcell Toth
101

Para explorar um pouco a resposta de Dyland:

Três classes implementam a classe FileResult :

System.Web.Mvc.FileResult
      System.Web.Mvc.FileContentResult
      System.Web.Mvc.FilePathResult
      System.Web.Mvc.FileStreamResult

Eles são todos bastante auto-explicativos:

  • Para downloads de caminhos de arquivos onde o arquivo existe no disco, use FilePathResult- esta é a maneira mais fácil e evita que você precise usar o Streams.
  • Para matrizes de bytes [] (semelhante a Response.BinaryWrite), use FileContentResult.
  • Para matrizes de bytes [] nas quais você deseja fazer o download do arquivo (disposição do conteúdo: anexo), use de FileStreamResultmaneira semelhante à abaixo, mas com a MemoryStreame usando GetBuffer().
  • Para Streamsuso FileStreamResult. É chamado de FileStreamResult, mas é preciso um, Streamentão eu acho que funciona com um MemoryStream.

Abaixo está um exemplo de uso da técnica de disposição de 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;
    }
Chris S
fonte
2
A parte de disposição do conteúdo deste post foi extremamente útil
Diego
O VS está me dizendo que essa sobrecarga de FileStream () é obsoleta.
precisa saber é o seguinte
1
Algo a ser observado: se você tiver uma vírgula no nome do arquivo, o Chrome a rejeitará com o erro "muitos cabeçalhos recebidos". Portanto, substitua todas as vírgulas por um "-" ou "".
18712 Chris S
Como alguém faria isso usando apenas controladores de API da Web?
Zapnologica
74

Isso pode ser útil se você quiser modificar a imagem antes de devolvê-la:

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");
}
staromeste
fonte
1
Obrigado. Isso é perfeito para o cenário em que um proxy é necessário para baixar uma imagem que requer autenticação que não pode ser feita no lado do cliente.
Hong
1
Você está esquecendo de eliminar três objetos nativos: Font, SolidBrush e Image.
Wout
3
Melhoria sugerida aqui: você cria um fluxo de memória, grava os dados e cria um resultado de arquivo com os dados usando .ToArray (). Você também pode chamar ms.Seek (0, SeekOrigin.Begin) e retornar File (ms, " image / png ") // retorna o fluxo propriamente dito
Quango
12

Você pode criar sua própria extensão e fazer isso dessa maneira.

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")%>
Oleksandr Fentsyk
fonte
10

Você pode escrever diretamente na resposta, mas não pode ser testado. É preferível retornar um ActionResult que adiou a execução. Aqui está o meu StreamResult reutilizável:

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);
    }
}
JarrettV
fonte
9

Por que não simplificar e usar o ~operador til ?

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

ATUALIZAÇÃO: Existem opções melhores do que minha resposta original. Isso funciona muito bem fora do MVC, mas é melhor seguir os métodos internos de retorno do conteúdo da imagem. Veja as respostas votadas.

Você certamente pode. Experimente estes passos:

  1. Carregar a imagem do disco em uma matriz de bytes
  2. armazenar em cache a imagem no caso de você esperar mais solicitações para a imagem e não desejar a E / S do disco (minha amostra não a armazena em cache abaixo)
  3. Altere o tipo de mímica por meio do Response.ContentType
  4. Response.BinaryEscreva a matriz de bytes da imagem

Aqui está um exemplo de código:

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

Espero que ajude!

Ian Suttle
fonte
4
e como isso ficaria na ação do controlador?
CR7 07/04
5

Solução 1: para renderizar uma imagem em uma exibição a partir de um URL da imagem

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, use-o como:

@Html.Image(@Model.ImagePath);

Solução 2: para renderizar imagem do banco de dados

Crie um método de controlador que retorne dados de imagem 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 use-o em uma exibição como:

@ { Html.RenderAction("View","Image",new {id=@Model.ImageId})}

Para usar uma imagem renderizada a partir desta ação em qualquer HTML, use

<img src="http://something.com/image/view?id={imageid}>
Ajay Kelkar
fonte
5

Isso funcionou para mim. Desde que eu estou armazenando imagens em um banco de dados do 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)procure o registro do arquivo de imagem no banco de dados (contexto EF). Ele retorna um objeto FileImage, que é apenas uma classe personalizada que fiz para o modelo e, em seguida, o usa como FileContentResult.

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

você pode usar o arquivo para retornar um arquivo como exibição, 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>"); }

            }
Avinash Urs
fonte
3

Veja o ContentResult. Isso retorna uma string, mas pode ser usado para criar sua própria classe do tipo BinaryResult.

leppie
fonte
2
if (!System.IO.File.Exists(filePath))
    return SomeHelper.EmptyImageResult(); // preventing JSON GET/POST exception
else
    return new FilePathResult(filePath, contentType);

SomeHelper.EmptyImageResult()deve retornar FileResultcom a imagem existente (1x1 transparente, por exemplo).

Essa é a maneira mais fácil se você tiver arquivos armazenados na unidade local. Se os arquivos forem byte[]ou stream- use FileContentResultou FileStreamResultcomo sugerido por Dylan.

Victor Gelmutdinov
fonte
1

Eu vejo duas opções:

1) Implemente seu próprio IViewEngine e defina a propriedade ViewEngine do Controller que você está usando para o ImageViewEngine no método "image" desejado.

2) Use uma visão :-). Basta alterar o tipo de conteúdo etc.

Matt Mitchell
fonte
1
Isso pode ser um problema devido a espaços extras ou CRLFs na exibição.
Elan Hasson
2
Eu estava errado no meu último post ... msdn.microsoft.com/en-us/library/... Você pode usar a classe WebImage e WebImage.Write em uma visão :)
Elan Hasson
1

Você pode usar o HttpContext.Response e gravar diretamente o conteúdo nele (WriteFile () pode funcionar para você) e, em seguida, retornar ContentResult de sua ação em vez de ActionResult.

Isenção de responsabilidade: eu não tentei isso, é baseado nas APIs disponíveis. :-)

Franci Penov
fonte
1
Sim, acabei de notar que o ContentResult suporta apenas strings, mas é fácil o suficiente criar sua própria classe baseada em ActionResult.
leppie
1

O código abaixo utiliza System.Drawing.Bitmappara carregar a imagem.

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");
}
Youngjae
fonte
0

Eu também encontrei um requisito semelhante,

Portanto, no meu caso, faço uma solicitação ao Controller com o caminho da pasta de imagens, que em troca envia um objeto ImageResult.

O trecho de código a seguir ilustra o trabalho:

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>";

E no Controller do caminho da imagem eu produzo a imagem e a devolvo ao chamador

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();
}
Shriram Navaratnalingam
fonte
0

Leia a imagem, converta-a em byte[]e retorne a File()com um tipo de conteúdo.

public ActionResult ImageResult(Image image, ImageFormat format, string contentType) {
  using (var stream = new MemoryStream())
    {
      image.Save(stream, format);
      return File(stream.ToArray(), contentType);
    }
  }
}

Aqui estão os usos:

using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using Microsoft.AspNetCore.Mvc;
facepalm42
fonte