ASP.NET MVC denetleyicisi bir Görüntü döndürebilir mi?


455

Bir görüntü varlığını döndüren bir Denetleyici oluşturabilir miyim?

Aşağıdaki gibi bir URL istendiğinde, bu mantığı bir denetleyici aracılığıyla yönlendirmek istiyorum:

www.mywebsite.com/resource/image/topbanner

Denetleyici topbanner.pngbu görüntüyü arar ve doğrudan istemciye geri gönderir.

Bir Görünüm oluşturmak zorunda olduğunuz bunun örneklerini gördüm - Bir Görünüm kullanmak istemiyorum. Hepsini sadece Kontrolör ile yapmak istiyorum.

Mümkün mü?


1
Benzer bir soruyu burada sordum /programming/155906/creating-a-private-photo-gallery-using-aspnet-mvc ve bunu yapmak için harika bir rehber buldum. Bu kılavuzu izleyerek bir ImageResult sınıfı oluşturdum. https://blog.maartenballiauw.be/post/2008/05/13/aspnet-mvc-custom-actionresult.html
Vyrotek

2
Görüntüyü değiştirmek istiyorsanız , en iyi performans için ImageResizing.Net HttpModule'ü kullanın . Bunu yapmazsanız, bir FilePathResult ek yükün yalnızca yüzde birkaçını ekler. URL yeniden yazma biraz daha az ekler.
Lilith River

1
Neden MVC yerine WebApi Denetleyicisini kullanmıyorsunuz? ApiController class
A-Sharabiani

Yanıtlar:


534

Temel denetleyicileri Dosya yöntemini kullanın.

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

Bir not olarak, bu oldukça etkili görünmektedir. Denetleyici ( http://localhost/MyController/Image/MyImage) ve doğrudan URL ( http://localhost/Images/MyImage.jpg) aracılığıyla görüntü istediğim bir test yaptım ve sonuçlar:

  • MVC: fotoğraf başına 7,6 milisaniye
  • Doğrudan: fotoğraf başına 6,7 ​​milisaniye

Not: Bu, bir isteğin ortalama süresidir. Ortalama, yerel makinede binlerce istek yapılarak hesaplanmıştır, bu nedenle toplamlar ağ gecikmesini veya bant genişliği sorunlarını içermemelidir.


10
Şimdi bu soruya gelenler için, bu benim için en uygun çözümdü.
Clarence Klopfstein

177
Bu güvenli bir kod değil. Kullanıcının bunun gibi bir dosya adı (yolu) geçirmesine izin vermek, sunucudaki herhangi bir yerden dosyalara erişebilecekleri anlamına gelir. İnsanları olduğu gibi kullanmamaları konusunda uyarmak isteyebilirsiniz.
Ian Mercer

7
Dosyaları gerektiği gibi anında oluşturmazsanız ve oluşturulduktan sonra önbelleğe almazsanız (yaptığımız budur).
Brian

15
@ mare- bunu, kısıtlı bir konumdan dosya sunuyorsanız da yapabilirsiniz; örneğin App_Data, uygulamanızın bazı kullanıcıları tarafından imzalanması gereken resimler olabilir, ancak diğerleri değil. Bunları sunmak için bir denetleyici eylemi kullanmak, erişimi kısıtlamanıza olanak tanır.
Russ Cam

8
Diğerleri de belirtildiği gibi, kullanıcının dikkatli bir şekilde oluşturulmuş POST veya sorgu dizesi ile bir dizinde yukarı /../../../danger/someFileTheyTHoughtWasInaccessible
gitmesine

128

MVC yayın sürümünü kullanarak, işte ne yapacağım:

[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");
}

Açıkçası burada yol inşaatı ile ilgili bazı uygulamaya özgü şeyler var, ancak FileStreamResult döndürme güzel ve basit.

Bu eylemle ilgili olarak, görüntüye olan günlük çağrınıza (denetleyiciyi atlayarak) karşı bazı performans testleri yaptım ve ortalamalar arasındaki fark sadece 3 milisaniyeydi (denetleyici avg 68ms, denetleyici olmayan 65ms idi).

Buradaki cevaplarda bahsedilen diğer yöntemlerden bazılarını denedim ve performans isabet çok daha dramatikti ... çözümlerin bir kısmı kontrolör olmayan 6x kadar (diğer kontrolörler avg 340ms, kontrolör olmayan 65ms) kadar.


12
Görüntü değiştirilmemeye ne dersiniz? Son istekten sonra resim değiştirilmediğinde FileStreamResult 304 göndermelidir.
dariol

Daha Path.Combinegüvenli ve daha okunabilir kod için concat yerine kullanabilirsiniz .
Marcell Toth

101

Dyland'ın tepkisini biraz hafifletmek için:

Üç sınıf FileResult sınıfını uygular :

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

Hepsi oldukça açıklayıcıdır:

  • Dosyanın diskte bulunduğu dosya yolu indirmeleri için şunu kullanın FilePathResult- bu en kolay yoldur ve Akışları kullanmaktan kaçınır.
  • Bayt [] dizileri için (Response.BinaryWrite'a benzer) kullanın FileContentResult.
  • Dosyanın indirilmesini istediğiniz byte [] dizileri için (içerik-bırakma: ek), FileStreamResultaşağıdakine benzer şekilde kullanın, ancak MemoryStreamve ile kullanın GetBuffer().
  • İçin Streamskullanım FileStreamResult. Bu bir FileStreamResult denilen ama bir yapımı ayakkabıları Streamben diye, sanırım bu bir ile çalışır MemoryStream.

Aşağıda içerik düzenleme tekniğini (test edilmemiştir) kullanma örneği verilmiştir:

    [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
Bu yazının içerik düzenleme kısmı son derece yardımcı oldu
Diego

VS bana bu FileStream () aşırı yüklenmesinin eski olduğunu söylüyor.
MrBoJangles

1
Dikkat edilmesi gereken bir nokta: dosya adınızda virgül varsa, Chrome bunu "çok fazla başlık alındı" hatasıyla reddeder. Bu nedenle, tüm virgülleri "-" veya "" ile değiştirin.
Chris S

Bunu sadece web api denetleyicilerini kullanarak nasıl yaparsınız?
Zapnologica

74

Görüntüyü geri göndermeden önce değiştirmek isterseniz bu yararlı olabilir:

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
Teşekkür ederim. Bu, istemci tarafında gerçekleştirilemeyen kimlik doğrulama gerektiren bir görüntüyü indirmek için bir proxy'nin gerekli olduğu senaryo için mükemmeldir.
Hong

1
Okkalı 3 yerel nesneyi atmayı unutuyorsunuz: Font, SolidBrush ve Image.
Wout

3
Burada önerilen iyileştirme: bir bellek akışı oluşturun, verileri yazın ve .ToArray () kullanarak verilerle bir Dosya sonucu oluşturun. Ayrıca ms.Seek (0, SeekOrigin.Begin) öğesini çağırabilir ve sonra Dosya (ms, " image / png ") // akışın kendisini döndürür
Quango

12

Kendi uzantınızı oluşturabilir ve bu şekilde yapabilirsiniz.

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

Doğrudan yanıta yazabilirsiniz ancak daha sonra test edilemez. Ertelenmiş yürütme olan bir ActionResult döndürmek tercih edilir. İşte benim yeniden kullanılabilir 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

Neden basitleşip tilde ~operatörünü kullanmıyorsunuz ?

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

6

GÜNCELLEME: Orijinal cevabımdan daha iyi seçenekler var. Bu, MVC'nin dışında oldukça iyi çalışır, ancak görüntü içeriğini döndürmek için yerleşik yöntemlere bağlı kalmak daha iyidir. En çok oy alan cevapları görün

Kesinlikle yapabilirsiniz. Şu adımları deneyin:

  1. Görüntüyü diskten bir bayt dizisine yükle
  2. görüntü için daha fazla istek bekliyorsanız ve disk G / Ç'sini istemiyorsanız görüntüyü önbelleğe al (örneğim aşağıda önbelleğe almıyor)
  3. Mime türünü Response.ContentType aracılığıyla değiştirme
  4. Response.BinaryGörüntü bayt dizisini yazın

İşte bazı örnek kod:

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

Umarım yardımcı olur!


4
ve bu kontrolörün eyleminde nasıl görünürdü?
CRice

5

1.Çözüm: Bir görüntüyü resim URL'sinden görüntülemek için

Kendi uzantı yönteminizi oluşturabilirsiniz:

public static MvcHtmlString Image(this HtmlHelper helper,string imageUrl)
{
   string tag = "<img src='{0}'/>";
   tag = string.Format(tag,imageUrl);
   return MvcHtmlString.Create(tag);
}

Sonra şöyle kullanın:

@Html.Image(@Model.ImagePath);

Çözüm 2: Görüntüyü veritabanından oluşturmak için

Aşağıdaki gibi görüntü verilerini döndüren bir denetleyici yöntemi oluşturun

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);
  }
}

Ve şöyle bir görünümde kullanın:

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

Herhangi bir HTML'de bu işlem sonucundan oluşturulan bir resmi kullanmak için

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

5

Bu benim için çalıştı. Görüntüleri bir SQL Server veritabanında sakladığımdan beri.

    [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;
    }

Yukarıdaki kod parçasında _db.ImageFiles.Find(uuid)db (EF bağlamı) görüntü dosyası kaydı aranmaktadır. Model için yaptığım özel bir sınıf olan FileImage nesnesini döndürür ve daha sonra FileContentResult olarak kullanır.

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

4

Görünüm, İçerik vb. gibi bir dosyayı döndürmek için Dosya'yı kullanabilirsiniz.

 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 sayfasına bakın. Bu bir dize döndürür, ancak kendi BinaryResult benzeri sınıfınızı oluşturmak için kullanılabilir.


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

SomeHelper.EmptyImageResult()FileResultmevcut görüntü ile dönmelidir (örneğin, 1x1 saydam).

Yerel sürücüde depolanmış dosyalarınız varsa, bu en kolay yoldur. Dosyalar byte[]veya stream- ise , Dylan'ın önerdiği gibi FileContentResultveya öğesini kullanın FileStreamResult.


1

İki seçenek görüyorum:

1) Kendi IViewEngine uygula ve kullandığınız Denetleyicinin ViewEngine özelliğini ImageViewEngine'a istediğiniz "image" yönteminde ayarlayın.

2) Bir görünüm kullanın :-). İçerik türünü vb. Değiştirmeniz yeterlidir.


1
Görünümdeki fazladan boşluk veya CRLF'ler nedeniyle bu sorun olabilir.
Elan Hasson

2
Son yazımda yanılmışım ... msdn.microsoft.com/tr-tr/library/… WebImage sınıfını ve WebImage.Write'ı bir görünümde kullanabilirsiniz :)
Elan Hasson

1

HttpContext.Response'yi kullanabilir ve içeriği doğrudan ona yazabilirsiniz (WriteFile () sizin için işe yarayabilir) ve ardından ContentResult'u ActionResult yerine eyleminizden döndürebilirsiniz.

Feragatname: Bunu denemedim, mevcut API'lara bakmaya dayanıyor. :-)


1
Evet, ContentResult'un yalnızca dizeleri desteklediğini fark ettim, ancak kendi ActionResult tabanlı sınıfınızı oluşturacak kadar kolay.
leppie

1

Aşağıdaki kod System.Drawing.Bitmapgörüntüyü yüklemek için kullanılır.

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

Benzer bir gereksinimle de karşılaştım,

Yani benim durumumda, ImageResult nesnesini geri gönderen resim klasörü yolu ile Controller'a bir istekte bulunuyorum.

Aşağıdaki kod snippet'i işi gösterir:

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

Ve görüntü yolundaki Denetleyicide görüntüyü üretiyorum ve arayan kişiye geri döndürüyorum

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

Görüntüyü okuyun, dönüştürün ve byte[]ardından File()bir içerik türüyle a döndürün.

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

İşte kullanımlar:

using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using Microsoft.AspNetCore.Mvc;
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.