1

After creating a POST method for my API that allows me to upload images to the database where I hold them as bytes, I wanted to create a GET method that will allow me to get their information and eventually show them on a web page.

My model class looks like this:

public class Image
{
    public int recipeId { get; set; }
    public string format { get; set; }
    public string description { get; set; }
    public IFormFile image { get; set; }
}

IFormFile image is the image that gets uploaded from the front-end and gets converted using MemoryStream to fill the database like this:

enter image description here

This being said, below is my GET method:

[Route("v1/recipe/image/{recipeId}")]
[HttpGet()]
public Image GetImage(int recipeId)
{
    using (var con = _connFactory())
    {
        con.Open();
        return con.Query<Image>("SELECT * FROM RecipeImage WHERE RecipeId = @recipeId", new { recipeId }).FirstOrDefault();
    }
}

But I am getting the following error:

System.Data.DataException: 'Error parsing column 3 (Image=System.Byte[] - Object)'

InvalidCastException: Unable to cast object of type 'System.Byte[]' to type 'Microsoft.AspNetCore.Http.IFormFile'.

I understand what is going bad here, but I could not find any way around this. The only solution that I thought of was creating another model class for Image which instead of IFormFile has byte[], but I was wondering if there's a better way of solving this problem.

Questieme
  • 913
  • 2
  • 15
  • 34

2 Answers2

1

That's because you can't cast a Byte array to IFormFile.

If you want to get an Image object, you'd first load the Byte array into a MemoryStream then call Image.FromMemoryStream(stream)

using (var ms = new MemoryStream(byteArrayIn))
{
    return Image.FromStream(ms);
}

You might want to consider adding an additional property into your model that returns the Image type, and load the value into it when the byte array is being set i.e. inside the setter for the byte[].

public class Image
{
    public int recipeId { get; set; }
    public string format { get; set; }
    public string description { get; set; }
    private byte[] _image { get; set; }
    public byte[] image { get { return _image; } set { 
        _image = value; 
        using(var ms = new MemoryStream(byteArrayIn)) ...
            imageFile = Image.FromStream(ms);...
    }
    public Image imageFile { get; set; }
}
Adrian
  • 8,271
  • 2
  • 26
  • 43
  • Thanks for helping out! However, though, I have some slight issues. For example after copying your Image class code, I get errors such as like `'Image' does not contain a definition for 'FromStream'` and also that `byteArrayIn` does not exist in current context. Is there any package that I have to import? Also, what should be the content of my controller class, then? I know all these questions might sound stupid, hopefully you will be able to help me out. Cheers! – Questieme Oct 04 '19 at 11:18
  • You need to use the correct variables. I have only posted a sample, you need to make it fit to your existing class :) `byteArrayIn` should be `_image` and the Type `Image` needs to be imported from the same namespace as in your controller. Also note, where I've put the dots, that means I've been lazy and didn't finish putting the code in therefore you'll have to modify it slightly. Sorry :) – Adrian Oct 04 '19 at 11:23
  • Oh, that was a really silly question indeed, I should have figured that out! Thanks! – Questieme Oct 04 '19 at 11:29
1

One way would be to map the properties manually

using (var con = _connFactory())
{
    con.Open();
    return con.Query("SELECT * FROM RecipeImage WHERE RecipeId = @recipeId", new { recipeId })
                .Select(x => new Image() 
                {
                    recipeId = x.RecipeId,
                    format = x.Format,
                    description = x.Description,
                    image = new FormFile(new MemoryStream(x.Image), 0, x.Image.Length, null, "MyFile." + x.Format, "")
                })
                .FirstOrDefault();
}

But probably you want to send the file alone if you want to display it on the web. Try navigating to the v1/recipe/image/xxx endpoint in the browser to see if the image loads.

[Route("v1/recipe/image/{recipeId}")]
[HttpGet()]
public async Task<IActionResult> GetImage(int recipeId)  
  {  
      var data = await con.QueryAsync<byte[]>("SELECT Image FROM RecipeImage WHERE RecipeId = @recipeId", new { recipeId }) 

      return File(new MemoryStream(data), "image/jpeg", "SomeName.jpg");  
  }
Magnus
  • 45,362
  • 8
  • 80
  • 118
  • Thanks for helping out, the solution looks really nice. However, though, my returned JSON string looks like this: `{ "recipeId": 142, "format": "jpeg", "description": "RecipeName", "image": {` Any suggestion for a fix? I tried tweaking the `image` myself but I couldn't figure out anything. – Questieme Oct 04 '19 at 11:30
  • Are you sure you can send a serialize a FormFile to json? Probably you want to return the FormFile only from the endpoint – Magnus Oct 04 '19 at 11:37
  • Well, at this point I am not quite sure about anything as it's my first time trying this. The point would be to take the bytes array from the database and pass it to the front end where the image will then be supposed to show up. In this case, should I change my approach to the problem? – Questieme Oct 04 '19 at 11:41
  • I was getting an error for the `data` variable saying that it cannot convert `` to `byte[]` so I changed the method like this `[Route("v1/recipe/image/{recipeId}")] [HttpGet()] public async Task GetImage(int recipeId) { using (var con = _connFactory()) { byte[] data = await con.Query("SELECT Image FROM RecipeImage WHERE RecipeId = @recipeId", new { recipeId }).FirstOrDefault(); return File(new MemoryStream(data), "Image/jpg", "SomeName.jpg"); } }` -... – Questieme Oct 04 '19 at 11:57
  • -... but then the webpage would return an `Internal Server Error` – Questieme Oct 04 '19 at 11:58
  • Using my code, variable `data` is null. The exception is `{"Cannot perform runtime binding on a null reference"}` – Questieme Oct 04 '19 at 12:17