Can an ASP.NET kontroler MVC zwraca obraz?

Czy mogę utworzyć kontroler, który po prostu zwraca zasób obrazu?

Chciałbym przekierować tę logikę przez kontroler, za każdym razem, gdy wymagany jest adres URL, taki jak poniżej:

www.mywebsite.com/resource/image/topbanner

Kontroler wyszukuje topbanner.png i odeśle obraz bezpośrednio do klienta.

Widziałem przykłady tego, gdzie trzeba utworzyć widok - nie chcę go używać. Chcę to wszystko zrobić tylko za pomocą kontrolera.

Czy to możliwe?
Author: Peter Mortensen, 2008-10-09

15 answers

Użyj metody pliku kontrolerów bazowych.

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

To wydaje się być dość skuteczne. Wykonałem test, w którym zażądałem obrazu przez kontroler (http://localhost/MyController/Image/MyImage) i przez bezpośredni adres URL (http://localhost/Images/MyImage.jpg), a wyniki były następujące:

  • MVC: 7,6 milisekundy na zdjęcie
  • bezpośredni: 6,7 milisekundy na zdjęcie

Uwaga: Jest to średni czas żądania. Średnia została obliczona poprzez złożenie tysięcy wniosków na lokalnym maszyny, więc sumy nie powinny obejmować opóźnienia sieci lub problemów z przepustowością.

 492
Author: Brian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-22 16:17:36

Używając wersji release MVC, oto co robię:

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

Oczywiście mam tutaj pewne specyficzne dla aplikacji rzeczy dotyczące budowy ścieżki, ale zwracanie FileStreamResult jest ładne i proste.

Zrobiłem kilka testów wydajności w odniesieniu do tej akcji przeciwko codziennym wywołaniu obrazu (omijając kontroler) i różnica między średnimi wynosiła tylko około 3 milisekund (avg kontrolera było 68ms, Nie-kontroler był 65ms).

Próbowałem innych metod wymienionych w odpowiedziach tutaj i hit wydajności był znacznie bardziej dramatyczny... kilka rozwiązań odpowiedzi było aż 6x Nie-kontroler (inne kontrolery avg 340ms, Nie-kontroler 65ms).

 118
Author: Sailing Judo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-10 23:50:47

Do rozwinięcia odpowiedzi Dylanda:

Trzy klasy implementują klasę FileResult:

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

Wszystkie są dość oczywiste:

  • do pobierania ścieżek plików, gdzie plik istnieje na dysku, użyj FilePathResult - jest to najprostszy sposób i pozwala uniknąć konieczności używania strumieni.
  • dla tablic bajtowych [] (zbliżonych do odpowiedzi.BinaryWrite), użyj FileContentResult.
  • dla tablic byte [], do których chcesz pobrać plik (Content-disposition: załącznik), używać FileStreamResult w podobny sposób jak poniżej, ale z MemoryStream i używać GetBuffer().
  • do Streams Użyj FileStreamResult. Nazywa się to FileStreamResult, ale wymaga Stream, więc zgaduję , że działa z MemoryStream.

Poniżej znajduje się przykład użycia techniki Content-disposition (nie testowane):

    [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;
    }
 96
Author: Chris S,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-11 14:19:05

To może być pomocne, jeśli chcesz zmodyfikować obraz przed zwróceniem go:

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");
}
 66
Author: staromeste,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-04 15:45:16

Możesz utworzyć własne rozszerzenie i zrobić to w ten sposób.

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
Author: Sasha Fentsyk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-07 10:19:32

Możesz pisać bezpośrednio do odpowiedzi, ale wtedy nie da się jej przetestować. Preferowane jest zwrócenie wyniku działania, który odroczył wykonanie. Oto mój resusable 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);
    }
}
 8
Author: JarrettV,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-09 20:22:52

Dlaczego nie przejść prosto i użyć operatora tyldy ~?

public FileResult TopBanner() {
  return File("~/Content/images/topbanner.png", "image/png");
}
 7
Author: JustinStolle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-31 23:47:03

Aktualizacja: istnieją lepsze opcje niż moja oryginalna odpowiedź. To działa poza MVC całkiem dobrze, ale lepiej trzymać się wbudowanych metod zwracania zawartości obrazu. Zobacz głosowane odpowiedzi.

Z pewnością możesz. Wypróbuj te kroki:
  1. Załaduj obraz z dysku do tablicy bajtów
  2. buforuj obraz w przypadku, gdy oczekujesz więcej żądań dla obrazu i nie chcesz wejścia/Wyjścia dysku (moja próbka nie buforuje go poniżej)
  3. Zmień typ mime poprzez odpowiedź.ContentType
  4. odpowiedź.BinaryWrite out the image byte array
Oto przykładowy kod:
string pathToFile = @"C:\Documents and Settings\some_path.jpg";
byte[] imageData = File.ReadAllBytes(pathToFile);
Response.ContentType = "image/jpg";
Response.BinaryWrite(imageData);
Mam nadzieję, że to pomoże!
 4
Author: Ian Suttle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-08 21:44:12

Spójrz na ContentResult. Zwraca string, ale może być użyty do stworzenia własnej klasy podobnej do BinaryResult.

 2
Author: leppie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-09 06:27:57

Rozwiązanie 1: renderowanie obrazu w widoku z adresu URL obrazu

Możesz utworzyć własną metodę rozszerzenia:

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

Następnie użyj go tak:

@Html.Image(@Model.ImagePath);

Rozwiązanie 2: renderowanie obrazu z bazy danych

Utwórz metodę kontrolera, która zwróci dane obrazu jak poniżej

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

I użyj go w widoku:

@ { Html.RenderAction("View","Image",new {[email protected]})}

Aby użyć obrazu wyrenderowanego z tej akcji w dowolnym HTML, użyj

<img src="http://something.com/image/view?id={imageid}>
 2
Author: Ajay Kelkar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-19 18:41:35
if (!System.IO.File.Exists(filePath))
    return SomeHelper.EmptyImageResult(); // preventing JSON GET/POST exception
else
    return new FilePathResult(filePath, contentType);

SomeHelper.EmptyImageResult() powinien zwrócić FileResult z istniejącym obrazem (na przykład 1x1 przezroczysty).

Jest to najprostszy sposób, jeśli masz pliki przechowywane na dysku lokalnym. Jeśli pliki są byte[] lub stream - Użyj FileContentResult lub FileStreamResult zgodnie z sugestią Dylana.

 1
Author: Victor Gelmutdinov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-05 13:27:56

To mi pomogło. Ponieważ przechowuję obrazy w bazie danych 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;
    }

W powyższym fragmencie _db.ImageFiles.Find(uuid) szuka rekordu pliku obrazu w kontekście db (EF). Zwraca obiekt FileImage, który jest po prostu niestandardową klasą, którą zrobiłem dla modelu, a następnie używa go jako FileContentResult.

public class FileImage {
   public string Uuid { get; set; }
   public byte[] Data { get; set; }
   public string ContentType { get; set; }
}
 1
Author: hmojica,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-10 02:09:18

Widzę dwie opcje:

1) zaimplementuj własną IViewEngine i ustaw właściwość Viewengine kontrolera, którego używasz do ImageViewEngine w żądanej metodzie "image".

2) Użyj widoku : -). Wystarczy zmienić typ treści itp.

 0
Author: Matt Mitchell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-09 06:26:59

Możesz użyć HttpContext.Odpowiadaj i bezpośrednio zapisuj do niej zawartość (WriteFile () może działać dla Ciebie), a następnie zwracaj ContentResult z Twojej akcji zamiast ActionResult.

Zastrzeżenie: nie próbowałem tego, opiera się na patrzeniu na dostępne API. :-)

 0
Author: Franci Penov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-09 06:28:39

Możesz użyć File, aby zwrócić plik, taki jak widok, zawartość itp

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

            }
 0
Author: Avinash Urs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-16 14:23:33