ASP.NET MVCコントローラーは画像を返すことができますか?


455

画像アセットを返すだけのコントローラーを作成できますか?

次のようなURLが要求されたときはいつでも、このロジックをコントローラー経由でルーティングしたいと思います。

www.mywebsite.com/resource/image/topbanner

コントローラはtopbanner.pngその画像を検索して直接クライアントに送信します。

ビューの作成が必要になるこの例を見てきました。ビューを使いたくありません。コントローラーだけですべてやりたいです。

これは可能ですか?


1
ここで同様の質問を/programming/155906/creating-a-private-photo-gallery-using-aspnet-mvcと尋ねたところ、これを行うための優れたガイドが見つかりました。このガイドに従ってImageResultクラスを作成しました。https://blog.maartenballiauw.be/post/2008/05/13/aspnet-mvc-custom-actionresult.html
Vyrotek

2
画像を変更する場合は、最高のパフォーマンスを得るためにImageResizing.Net HttpModule使用します。そうしないと、FilePathResultが追加するオーバーヘッドは数パーセントだけになります。URLの書き換えにより、追加が少し少なくなります。
リリスリバー

1
MVCの代わりにWebApiコントローラーを使用しないのはなぜですか?ApiController class
A-Sharabiani 16年

回答:


534

基本コントローラの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ミリ秒

注:これはリクエストの平均時間です。平均は、ローカルマシンで数千のリクエストを実行することによって計算されたため、合計にはネットワーク遅延や帯域幅の問題を含めないでください。


10
現在この質問に参加している人にとって、これは私にとって最も効果的な解決策でした。
クラレンスクロプシュタイン

177
これは安全なコードではありません。このようにユーザーにファイル名(パス)を渡させると、サーバー上のどこからでもファイルにアクセスできる可能性があります。そのまま使用しないように警告したい場合があります。
Ian Mercer

7
必要に応じてファイルをオンザフライで構築し、作成後にそれらをキャッシュする場合を除き(それが私たちの仕事です)。
ブライアン

15
@ mare-制限された場所からファイルを提供している場合にもこれを行うことができます。たとえばApp_Data、アプリケーションの一部のユーザーだけが署名する必要がある画像が含まれている場合があります。それらを提供するためにコントローラーアクションを使用すると、アクセスを制限できます。
Russ Cam

8
他が述べたように、私は許可されたユーザは、慎重に構築POSTまたはクエリ文字列を使用してディレクトリをナビゲートすることを実際の生産コードを見てきたように、パスの建物の中に慎重に: /../../../danger/someFileTheyTHoughtWasInaccessible
AaronLS

128

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倍にもなりました(他のコントローラーは平均340ms、非コントローラー65ms)。


12
画像は変更されませんか?最後のリクエスト以降に画像が変更されていない場合、FileStreamResultは304を送信する必要があります。
dariol

Path.Combineより安全で読みやすいコードには、連結の代わりに使用できます。
Marcell Toth

101

Dylandの応答を少し詳しく説明するには:

3つのクラスがFileResultクラスを実装します

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

それらはすべてかなり自明です:

  • ディスク上にファイルが存在するファイルパスのダウンロードには、FilePathResultこれを使用します。これが最も簡単な方法であり、Streamsを使用する必要がありません。
  • byte []配列(Response.BinaryWriteと同様)の場合は、を使用しますFileContentResult
  • ファイルをダウンロードするbyte []配列(content-disposition:attachment)の場合FileStreamResultは、以下と同じように使用しますが、a 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;
    }

2
この投稿のコンテンツ配置の部分は非常に役に立ちました
Diego

VSは、FileStream()のこのオーバーロードは廃止されていると言っています。
MrBoJangles 2012年

1
注意点:ファイル名にカンマが含まれている場合、Chromeでは「受信したヘッダーが多すぎます」というエラーで拒否されます。したがって、すべてのコンマを「-」または「」に置き換えます。
Chris S

Web APIコントローラのみを使用してこれをどのように実行しますか?
Zapnologica 2015年

74

これは、返す前に画像を変更したい場合に役立ちます。

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
ありがとうございました。これは、クライアント側では実行できない認証が必要なイメージをダウンロードするためにプロキシが必要なシナリオに最適です。
ホン

1
フォント、SolidBrush、画像の3つのネイティブオブジェクトを破棄するのを忘れています。
Wout

3
ここで推奨される改善点:メモリストリームを作成し、データを書き込んでから、.ToArray()を使用してデータを含むファイル結果を作成します。ms.Seek(0、SeekOrigin.Begin)を呼び出して、File(ms、 "を返すこともできます。 image / png ")//ストリーム自体を返す
Quango

12

独自の拡張機能を作成して、このようにすることができます。

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

応答に直接書き込むことはできますが、テストすることはできません。実行を延期した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);
    }
}

9

簡単にして、チルダ~演算子を使用してみませんか?

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

6

更新:私の元の答えよりも良いオプションがあります。これはMVCの外でも十分機能しますが、画像コンテンツを返す組み込みメソッドを使用することをお勧めします。投票した回答をご覧ください。

確かにできます。次の手順を試してください。

  1. イメージをディスクからバイト配列にロードします
  2. イメージのリクエストが増えることが予想され、ディスクI / Oが必要ない場合に備えて、イメージをキャッシュします(サンプルでは、​​イメージをキャッシュしていません)。
  3. Response.ContentTypeを介してMIMEタイプを変更する
  4. Response.Binary画像のバイト配列を書き出す

ここにいくつかのサンプルコードがあります:

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

お役に立てば幸いです。


4
そして、これはコントローラーのアクションでどのように見えますか?
CRice

5

解決策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}>

5

これでうまくいきました。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)では、db(EFコンテキスト)で画像ファイルレコードを検索しています。これは、モデル用に作成したカスタムクラスであるFileImageオブジェクトを返し、それをFileContentResultとして使用します。

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

4

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

            }

3

ContentResultを見てください。これは文字列を返しますが、独自のBinaryResultのようなクラスを作成するために使用できます。


2
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[]orの場合stream- FileContentResultまたはFileStreamResultDylanの提案どおりにor を使用します。


1

2つのオプションが表示されます。

1)独自のIViewEngineを実装し、使用しているコントローラーのViewEngineプロパティを、目的の「イメージ」メソッドでImageViewEngineに設定します。

2)ビューを使用します:-)。コンテンツタイプなどを変更するだけです。


1
ビューに余分なスペースまたはCRLFがあるため、これは問題になる可能性があります。
Elan Hasson、2011

2
私の前回の投稿では間違っていました... msdn.microsoft.com/en-us/library/… ビューでWebImageクラスとWebImage.Writeを使用できます:)
Elan Hasson

1

HttpContext.Responseを使用してコンテンツを直接書き込み(WriteFile()でうまくいく場合があります)、ActionResultではなくアクションからContentResultを返すことができます。

免責事項:私はこれを試していません、それは利用可能なAPIを見ることに基づいています。:-)


1
うん、ContentResultは文字列のみをサポートしていることに気づきましたが、独自のActionResultベースのクラスを作成するのは簡単です。
leppie 2008年

1

以下のコードは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();
    result.Save(ms, ImageFormat.Jpeg);
    ms.Position = 0;
    return new FileStreamResult(ms, "image/jpeg");
}

0

私も同様の要件に遭遇しました、

したがって、私の場合は、イメージフォルダーパスを使用してコントローラーにリクエストを送信します。これにより、ImageResultオブジェクトが返されます。

次のコードスニペットは、作業を示しています。

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

そして、コントローラーで画像パスから画像を生成し、呼び出し元に返します

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

画像を読み取ってに変換し、コンテンツタイプとともにを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;
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.