5

I'm programming an ASP.NET MVC4 application which stores uploaded images as byte[] in a database (with Entity framework) and then displays them. To display the images I'm using this code in the view:

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

But each time I refresh the page I see that the browser does not cache the image and just renders it again causing unecessary bandwith usage.

Maybe there's a more bandwith friendlier way to display the image? Maybe the idea to store uploaded image as 'byte[]' was stupid in the first place (my application is just a simple web page which stores articles about psychology :D with an admin panel to achieve this) and I should just store images in a folder?

Thanks

Nmktronas
  • 237
  • 3
  • 12
  • as a side note. dont store images as blobs in the database, it makes your database slow and completely inefficient. only store the filenames of the images in the database and store the files in a directory on disk – bizzehdee Aug 09 '13 at 21:06
  • @bizzehdee there are times when what you say is true. For small amounts of data I think it's an acceptable usage to store images in a db. As long as you realize that it isn't really suitable for a medium to large system – Richard Harrison Aug 09 '13 at 21:10
  • @bizzehdee - there are already many questions on "files in DB" subject (like http://stackoverflow.com/questions/3748/storing-images-in-db-yea-or-nay/3751#3751) with generally less absolute black and whit approach. I.e. "upload user image to site with 5 front end machines" - you get significantly simple system with images in DB compared to custom-build file storage, not necessary faster but it is rarely the main goal. – Alexei Levenkov Aug 09 '13 at 21:30

3 Answers3

5

Storing the image in the database is one possible option. There are times when it is a good idea, and times when a bad idea. Generally if dealing with large amounts or large sized images you may be advised to reconsider using a different storage method.

Effectively what you're currently doing is embedding the image in the HTML; this can be good I've used it before when it would take around 5 seconds to calculate the data for a graph and the same data is required for the page, but in general usage you'd be better off serving the image from an action.

So what we need to do is to provide an action in a controller to get the image; probably based on id - but that I'll leave up to you.

[OutputCache(Duration = 3600, VaryByParam = "id")]
public ActionResult GetImage(int Id)
{
    // 1. provide connection to entity framework
    var dbc = new DatabaseContext();
    var item = dbc.FindItem(Id);// call to get the image from EF (2)
    var ms = new MemoryStream(tem.ImageByte);    
    FileStreamResult result = new FileStreamResult(ms, "image/png");
    result.FileDownloadName = item.ImageName; // or item.Id or something (3)
    return result;
}

in the cshtml

<img src="@Url.Action("GetImage", "Controller", new {Id = Model.ImageId})" />

So in the above code you need to provide

  1. The connection to EF
  2. the call to locate the image
  3. something for the image name
  4. possibly change the output cache
Richard Harrison
  • 19,247
  • 4
  • 40
  • 67
  • 1
    the times that it is a good idea are: "never" and "sometime after hell freezes over". times when its a bad idea are "now" and "always" – bizzehdee Aug 09 '13 at 21:08
  • 3
    @bizzehdee For small sites it's probably a bad idea, but "images shouldn't go in databases" is far from a universally true statement. – Timothy Shields Aug 09 '13 at 21:15
3

In your HomeController add a function like this:

[HttpGet]
public FileResult GetImage(string id)
{
    byte[] fileContents = ...; // load from database or file system
    string contentType = "image/jpeg";
    return File(fileContents, contentType);
}

Register a route to this handler in Global.asax.cs:

routes.MapRoute(
    "GetImage",
    "img/{id}",
    new { controller = "Home", action = "GetImage" });

In your webpage, use a src pointing to this action:

<img src="@Url.Action("GetImage", "Home", new { id = "logo.jpg" })" />

which will resolve to

<img src="/img/logo.jpg" />
Timothy Shields
  • 75,459
  • 18
  • 120
  • 173
  • Thanks. I've implemented this approach without registering the route- I don't know the purpose of routing in this scenario (I'm new on MVC). – Nmktronas Aug 10 '13 at 17:44
  • 1
    @Nmktronas Glad it worked for you. The purpose of registering the route is *purely* to make the URL prettier. There's zero value in terms of functionality. That being said, it is a good habit to get into, because it helps to decouple the API you expose to users (in terms of URLs and actions on URLs) from the controllers, actions, etc. If you use the default routing, the URL will be `/Home/GetImage/logo.jpg`, and if you want to, say, change the name of the `GetImage` method, you'll be changing the API too. – Timothy Shields Aug 10 '13 at 18:09
2

add Generic Handler

public class Handler : IHttpHandler
{

    public void ProcessRequest(HttpContext context)
    {
        long id = Convert.ToInt64(System.Web.HttpContext.Current.Request.QueryString["id"]);
        string model = (System.Web.HttpContext.Current.Request.QueryString["model"]);
        DbContext Db = new DbContext();
        byte[] picture = new byte[0];

        switch (model)
        {
            case "News":
                NameSpace.Models.News news = Db.Newss.Single<NameSpace.Models.News>(n => n.ID == id);
                picture = news.Picture;
                break;

            case "Article":
                NameSpace.Models.Article article = Db.Articles.Single<NameSpace.Models.Article>(a => a.ID == id);
                picture = article.Picture;
                break;
        }

        context.Response.Clear();
        context.Response.ContentType = "image/jpeg";
        context.Response.BinaryWrite(picture);
        context.Response.Flush();
        context.Response.End();
    }

    public bool IsReusable
    {
        get
        {
            return false;
        }
    }
}

*and in view use this sorce for image img src="/Handlers/Handler.ashx?id=@item.ID&model=News"

Saman Qaydi
  • 171
  • 2
  • 7
  • I was just typing a similar solution when I saw Saman's answer pop up. An http handler is the way to go. The only thing I'd add is that you'll have to do some routing work to get MVC to ignore calls to Handler.ashx. – rhoadsce Aug 09 '13 at 21:02
  • 1
    Why is this getting so many upvotes? This is anything but the MVC 3+ way to do things... – Timothy Shields Aug 09 '13 at 21:17
  • 1
    +0: while it is valid approach dropping all features MVC gives you (much better testability, like model binding, action attributes/global filters, ease of dependency injection...) is questionable. – Alexei Levenkov Aug 09 '13 at 21:19