22

I am returning a dataset from my MS SQL 2008R2 database that contains a datatable that I am already getting data from on my Razor view. I added a byte[] field that contains an image thumbnail that I am trying to display on that view.

Since the byte array is relatively tiny, I figured I would try displaying the byte array inline. However, after reading there may be some browser-related issues, I abandoned this.

Creating a controller method seemed the way to go, (both methods found here), however I already have the byte array ready to go on my view, and don't need to make another db call to get it based on an ID.

Doing this, doesn't work for obvious reasons:

... displaying other data on the page ....

@if (Model.dsResults.Tables[0].Rows[i]["ImageBytes"] == null)
{
    <img src="@Url.Content("~/Content/Images/NoPhoto.png")" border="0" />
}
else
{
    <img src="@Url.Action("GetImage", "SearchResults", new { imageBytes = Model.dsResults.Tables[0].Rows[i]["ImageBytes"] })" alt="Product Image" />
}

...

Controller method:

[Authorize]
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult GetImage(byte[] imageBytes)
{
    byte[] byteArray = imageBytes;
    return new FileContentResult(byteArray, "image/jpeg");
}

... as this essentially is attempting to send the byte array over http.

So, my question is, since I already have the byte array on my Razor view, how do I display it?

-- Update --

I realize that doing processing in the view isn't recommended, but since the data is already there, can't something like this be done:

Response.Write((byte[])Model.dsResults.Tables[0].Rows[i]["ImageBytes"]);

Or...

Stream s = new MemoryStream(((byte[])Model.dsResults.Tables[0].Rows[i]["ImageBytes"]));
System.Drawing.Image img = new System.Drawing.Bitmap(s);

In other postings I've read that you an do a Response.Write(byte[]...)) but this doesn't work in this case.

-- UPADATE --

In my ongoing search for efficiency (not having to make another request to the db) the WebImage helper seems to be a good candidate. One of its constructors will accept a byte array to initialize the class, then using the .Write("jpeg") method, I can see the image.

<td>
    @{
        WebImage webImage = new WebImage(((byte[])Model.dsResults.Tables[0].Rows[i]["ImageBytes"]));
        webImage.Write("jpeg");
    }
</td>

The problem with using WebImage.Write() is that once used, the image is the only thing that renders on the page. Is there a way to render this directly to a "control" on a Razor view page?

-- UPDATE --

This continues to bug me... so I tried the following, which I figured may work, since this is what we're doing from the Action...

if (Model.dsResults.Tables[0].Rows[i]["ImageBytes"] != DBNull.Value)
{
    var img1 = new FileContentResult(((byte[])Model.dsResults.Tables[0].Rows[i]["ImageBytes"]), "image/jpeg");
    <text>
        <img src="@new FileContentResult(((byte[])Model.dsResults.Tables[0].Rows[i]["ImageBytes"]), "image/jpeg")" />
    </text>
}

... doesn't work.

Community
  • 1
  • 1
ElHaix
  • 12,846
  • 27
  • 115
  • 203
  • I've yet to see an example of the image being taken from the model/viewmodel and displayed (except my answer using TempData). You'd think it would be a common requirement, but either it's so simple no-one asks or it's so difficult no-one bothers. Did you try the TempData solution? – K. Bob Dec 12 '11 at 15:46
  • I know it's not my question but... What was the down vote for? This is a good question - so good that no-one has a solution that doesn't involve extra DB trips or browser tom-foolery. Downvoters ought to at least leave a comment. – K. Bob Dec 12 '11 at 22:29
  • Yeah, seriously - because I'm not happy with an inefficient solution? :) It seems highly inefficient to have to make another round trip to the database since I have all my data (image byes and all) ready to go on the view. I'm trying to propose a few different avenues, and using the WebImage helper has been been the closest I got - it actually shows the image, but is the only content on the page. – ElHaix Dec 13 '11 at 01:12
  • I'm going through this exact pain atm. My problem is made worse by the fact I allow users to upload up to 10 images at once then display them in the Details view after saving. – Ciarán Bruen Oct 17 '12 at 21:34

7 Answers7

20

Model.Content is a byte[] image.

@{
    string imageBase64 = Convert.ToBase64String(Model.Content);
    string imageSrc = string.Format("data:image/gif;base64,{0}", imageBase64);
}

Used like so:

<img src="@imageSrc" alt="@Model.Name" width="100" height="100" />
MoLow
  • 3,056
  • 2
  • 21
  • 41
lbancarz
  • 201
  • 2
  • 3
9

Eventually, I went for the two trips route. Highly inefficient, as the image bytes were already ready to go on the view. There should be a web helper image control that will render the image directly from the image's byte array (or image type). Maybe there is and I missed it.

To get the bytes (yes, all over again):

    [AcceptVerbs(HttpVerbs.Get)]
    public ActionResult GetThumbnailImage(string itemListID)
    {
        byte[] imageBytes = null;

        client = new FeederServiceClient();
        imageBytes = client.GetItemThumbnail( itemListID );

        if (imageBytes == null)
        {
            return new FilePathResult("~/Content/Images/NoPhoto.png", "image/png");
        }
        else
        {
            return new FileContentResult(imageBytes, "image/jpeg");
        }
    }

Then to display the image:

<img src="@Url.Content("~/Thumbnails/" + @Model.dsResults.Tables[0].Rows[i]["ItemListID"] )" />

With the following route placed in Global.asax:

routes.MapRoute(name: "Thumbnails", url: "Thumbnails/{itemListID}", defaults: new { controller = "Results", action = "GetThumbnailImage" });
ElHaix
  • 12,846
  • 27
  • 115
  • 203
  • What you're asking for isn't how most web browsers work. An image tag links to an external document with its own URI. That is why you need to do it this way. If you are concerned about it, have the image saved to a file and have the database keep the URL to that file. Then you hit once. Alternatively, newer browsers support data URIs in some cases. See http://en.wikipedia.org/wiki/Data_URI_scheme for more information. – smelch Apr 13 '12 at 20:23
  • I know this is an older question but just to throw 2 cents in, another trip to the db should not be necessary if you are using caching on the web server. Cache the last db calls for a few minutes so that when the browser's nth calls to get the images come in, the data is fetched from the cache. Additionally, I would like to say thanks for the nifty example of using the FileContentResult. This helped me in my own app. So...Thanks! – GolfARama Feb 12 '13 at 04:59
  • 1
    this works for us @:Image Not Available as long as Razor is hosted in a asp MVC app (if you host it externally , say a win form no happy) – Eric Brown - Cal Mar 04 '14 at 19:35
5

I don't think having a byte array in your View will help you. You'll need to use a controller action as the img src as described in your linked answer.

[Authorize]
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Image(int id)
{
    byte[] byteArray = _imageRepository.GetImage(id);
    return new FileContentResult(byteArray, "image/jpeg");
}


<img src="Image/1234" alt="Image 1234"/>
Community
  • 1
  • 1
jrummell
  • 42,637
  • 17
  • 112
  • 171
  • Right, but as I mentioned, I already have the byte array. It seems inefficient to have to go back and get it, since I already have it. – ElHaix Dec 12 '11 at 13:32
  • Even if you already have it, you can't display an image in html with a byte array. The browser will need to make a separate request for each image. – jrummell Dec 12 '11 at 13:36
3

The following will work:

<img src="data:image/jpg;base64,@(Convert.ToBase64String(item.PhotoImage))" alt="@item.orientation" />

in my application, I defined the model on the RAZOR page as item, rather than model. The PhotoImage is my byte array (actually image field in SQL) and Orientation is just a string column that tells metadata about the photo.

I searched for a long time before I found this answer and it worked for me. Hope it helps.

Chris Schiffhauer
  • 17,102
  • 15
  • 79
  • 88
ron icard
  • 31
  • 2
  • 1
    I haven't had a chance to try it, but it looks like that will work - more efficiently than making an additional round trip to the db. – ElHaix Jan 17 '14 at 14:52
2

You can just do a Response.Write(your byte array) and set the Response.ContentType to image/jpeg in your Controller action. That will display it directly as an image.

UPDATE

The code could look something like this:

    [Authorize]
    [AcceptVerbs(HttpVerbs.Get)]
    public void GetImage(int id)
    {
        byte[] imageArray = GetImageBytesFromDb(id); // some method for returning the byte-array from db.
        Response.ContentType = "image/png";
        Response.Write(imageArray);       
    }

Now you would be able to do the following in your view:

 <img src="@Url.Action("GetImage", "SearchResults", new { id= Model.dsResults.Tables[0].Rows[i]["Id (or whatever the id of the row is)"] })" alt="Product Image" />

So, in other words instead of using the bytes directly in the view, you call a controller-action that returns the bytes from the database-row based on the Id of the row (or product, if that's the entity of the row)

It might not be exactly an answer to your question, but it will work.

Yngve B-Nilsen
  • 9,606
  • 2
  • 36
  • 50
  • There is already data that is displayed on the page other images, text, etc. The thumbnail image is part of a data table row, where the rest of the datatable is being displayed. I don't want to have to set the entire response content type to image/jpeg. – ElHaix Dec 12 '11 at 13:31
  • To clarify, what you are recommending is that for every image, call the GetImage() which will return the image, based on the ID, correct? – ElHaix Jan 09 '12 at 15:45
  • @ElHaix: yes, that's it. If you don't like hitting the db twice for the bytes, you can omit the bytes from the initial query, and select only the bytes in the GetImage action.. – Yngve B-Nilsen Jan 09 '12 at 17:47
  • Why was this downvoted? A comment together with the downvote it appreciated! – Yngve B-Nilsen May 23 '12 at 06:21
  • Probably the same hater that down-voted my initial question. I upvoted to neutralise. – ElHaix May 24 '12 at 13:26
  • Thanks! I repaid the favour :) – Yngve B-Nilsen May 24 '12 at 13:32
  • This is a helpful answer, but I don't think `Response.Write(imageArray)` will work in this case. I think you would need to use `Response.BinaryWrite(imageArray)`. – hmqcnoesy Apr 06 '15 at 13:25
0

This is one way to avoid the second unnecessary trip to the database. memorystream to Image but it comes with it's own problem when used in IE9 (all other browsers and IE versions seem to work OK).

Even more information about the problem can be found here More problem details.

In the end, for the sake of not having a hack for what I think is probably a bug in IE9, I went with the 'two' reads, but there shouldn't be a need.

Community
  • 1
  • 1
K. Bob
  • 2,668
  • 1
  • 18
  • 16
0

If you don't mind using Session, you can put your model into Session (heresy!), then render your view as normal but now where you want the images to appear you call the controller action with Url.Action passing it the Id of the image you want. Then pull your model back out of session, find the image in your model for that id, do the FiLeContentResult(array,imgtype) on your byte array and viola - images direct from your model onto your page. No need to go to the real database again BUT if you use Session SQL State then obv. you will be hitting the database much worse than before but if you are InProc.... well it's one way to do it. I'm not sure if IE9 still has a minor problem with caching, but you can always pass a unique id along with your real id to make sure that doesn't happen. It's a shabby solution but it does work.

K. Bob
  • 2,668
  • 1
  • 18
  • 16