1

I have the following controller, which upon receiveing a file from the view generates a thumbnail and saves both, image abd thumbnail to the database:

public async Task<ActionResult> FileUpload(HttpPostedFileBase file)
    {
        if (file != null && file.ContentLength > 0)
        {
            var logo = new Logo() { LogoGuid = Guid.NewGuid() };
            var clinica = await GetClinicaAsync();

            if (IsClinicNonexistent(clinica))
            {
                db.Logos.Add(logo);
                clinica.LogoGuid = logo.LogoGuid;
            }

            else
                logo = await db.Logos.FirstOrDefaultAsync(l => l.LogoGuid == clinica.LogoGuid);

            logo.LogoImage = GetImage(file);
            logo.Thumbnail = GetThumbnail(file);

            db.SaveChanges();
        }

        return RedirectToAction("Arquivos");
    }

Which, for the matters of this error calls a method:

private byte[] GetThumbnail(HttpPostedFileBase file)
    {
        var image = Image.FromStream(file.InputStream);
        var thumbnailSize = GetThumbnailSize(image);
        var memoryStream = new MemoryStream();
        var thumbnail = image.GetThumbnailImage(thumbnailSize.Width, thumbnailSize.Height, null, IntPtr.Zero);
        image.Save(memoryStream, ImageFormat.Jpeg);

        return memoryStream.ToArray();
    }

That configuration of code produces an erro while running the line

var image = Image.FromStream(file.InputStream);

The error is: "System.ArgumentException: Parameter is not valid."

The error produced

All documentation that I could refer to, makes me believe that it's all sintatically and logically correct, yet there is the error...

But, tne funny thing is, if a move the conversion line which produces error, do the calling method, specifically to the begining as:

public async Task<ActionResult> FileUpload(HttpPostedFileBase file)
    {
        if (file != null && file.ContentLength > 0)
        {
            var logo = new Logo() { LogoGuid = Guid.NewGuid() };
            var clinica = await ClinicaAsync();

            var image = Image.FromStream(file.InputStream);

There will be no error. Please notice that the convertion line is in the last line of the code just above, thus in the begining of the controller. Again, if I move that line further down, but still in the controller, there will be the error again...

How can I keep the line where it logically belongs, thus in the GetThumbnail method?

Just as an additional reference, the GetImage method is:

private byte[] GetImage(HttpPostedFileBase file)
    {
        using (var br = new BinaryReader(file.InputStream))
            return br.ReadBytes(file.ContentLength);
    }
Sergio Di Fiore
  • 446
  • 1
  • 8
  • 22
  • What does `GetImage` do? Is it possible the input file is altered in any way? (i.e. stream position moved) – Lennart Stoop Nov 03 '18 at 15:14
  • @Lennart Stoop I've posted at the end the GetImage, but it only reads the HttpPostedFileBase, doesn't touch it... – Sergio Di Fiore Nov 03 '18 at 17:02
  • Well, when the `BinaryReader` is disposed of (after the `using` statement) the underlying stream will be closed too. That's causing the issue you're having. – Lennart Stoop Nov 03 '18 at 20:55

1 Answers1

2

The first thing you should know is Image.FromStream() requires a stream (MemoryStream, InputStream, etc.) that hasn't been already read, or the position is reset to beginning using Seek() method.

First, try to reset the pointer before calling GetThumbnail() method by using Position property or Seek():

// alternative 1
logo.LogoImage = GetImage(file);
file.InputStream.Position = 0;
logo.Thumbnail = GetThumbnail(file);

// alternative 2
logo.LogoImage = GetImage(file);
file.InputStream.Seek(0, SeekOrigin.Begin);
logo.Thumbnail = GetThumbnail(file);

If this doesn't work, the possible cause is you're calling Image.FromStream() after HttpPostedFileBase.InputStream has already used (and disposed) by BinaryReader which calls Dispose() (the using statement automatically called it), thus the exception is thrown.

You can create a copy of MemoryStream from HttpPostedFileBase.InputStream using Stream.CopyTo() like below example:

public async Task<ActionResult> FileUpload(HttpPostedFileBase file)
{
    if (file != null && file.ContentLength > 0)
    {
        var logo = new Logo() { LogoGuid = Guid.NewGuid() };
        var clinica = await GetClinicaAsync();

        var ms = new MemoryStream();
        file.InputStream.CopyTo(ms); // copy HTTP stream to memory stream

        if (IsClinicNonexistent(clinica))
        {
            db.Logos.Add(logo);
            clinica.LogoGuid = logo.LogoGuid;
        }

        logo.LogoImage = GetImage(file);
        ms.Position = 0; // make sure the stream is read from beginning
        logo.Thumbnail = GetThumbnail(ms);

        db.SaveChanges();
    }

    return RedirectToAction("Arquivos");
}

Then GetThumbnail() parameter should be changed to accept MemoryStream copied from HTTP stream:

// get image from memory stream
private byte[] GetThumbnail(MemoryStream file)
{
    var image = Image.FromStream(ms);
    var thumbnailSize = GetThumbnailSize(image);
    var thumbnail = image.GetThumbnailImage(thumbnailSize.Width, thumbnailSize.Height, null, IntPtr.Zero);
    image.Save(file, ImageFormat.Jpeg);

    return file.ToArray();
}

Note: You can also save the stream as image file on the disk and then read it with Server.MapPath() to generate thumbnail.

Related issues:

Image.FromStream(PostedFile.InputStream) Fails. (Parameter is not valid.) (AsyncFileUpload))

How do you convert a HttpPostedFileBase to an Image?

Tetsuya Yamamoto
  • 24,297
  • 8
  • 39
  • 61