Reliable image handling on Azure platform / Dotnet core - c#

I'm struggling a little with images on the Azure platform under dotnet core and I'm hoping someone can make a sensible suggestion.
Simple enough premise: user uploads image, saved in a database as base64 (about to move to Azure storage blob, but that's irrelevant to this). Later on, site owner comes along and clicks a button to get all these images down in a ZIP file. In the old days of .net framework this was easy. Now I seem to be hitting a set of 'yes, but' comments.
Yes, there's system.drawing.image but you can't use that because it's not in dotnet core (until recently).
Yes, you can use CoreCompat but it doesn't work on Azure because in Web Applications there's no support for GDI+.
Yes, even if I could, I'm developing on a Mac so it won't work locally as far as I can see.
I have tried beta4 of ImageSharp without a lot of success. It's random - sometimes it works, sometimes it just throws OutOfMemoryException.
I have tried SkiaSharp but similar results; sometimes it works, sometimes it spits out random error messages.
I'm not doing anything fancy in terms of processing, no resizing or anything. It should be a case of load file to byte array from Convert.FromBase64String, create Zip file entry, ultimately spit out zip file. The ZIP portion is fine, but I need something decent that can do the image work.
Here's a bit of code:
if(!string.IsNullOrEmpty(del.Headshot))
{
var output=SKImage.FromBitmap(SKBitmap.Decode(Convert.FromBase64String(del.Headshot)));
MemoryStream savedFile=new MemoryStream();
output.Encode(SKEncodedImageFormat.Jpeg, 100).SaveTo(savedFile);
string name=$"{del.FirstName} {del.LastName} - {del.Company}".Trim(Path.GetInvalidFileNameChars()) + "_Headshot.jpg";
ZipArchiveEntry entry=zip.CreateEntry(name);
using(Stream entryStream=entry.Open())
{
entryStream.Write(savedFile.ToArray(), 0, Convert.ToInt32(savedFile.Length));
}
output.Dispose();
savedFile.Dispose();
}
Can anyone give me a sensible suggestion for a library that can handle images, cross-platform and on Azure, before I pull out what little hair remains!
Thanks
EDIT: The first answer is technically correct, I don't need anything else. However, I might have been a bit wrong when I said I wasn't doing any image manipulation. Because it's all base64 without a filename being stored anywhere, I've no idea what sort of file it is. I'm therefore saving each one as JPEG to ensure that I can always output that file type and extension. Users I guess could be uploading JPG / PNG or even GIF.

Technically you do not need any of those other imaging (unless you are doing more that just zipping the content). Convert the base64 to byte array and pass that to the zip file. No need to save to disk just to read it back again for zipping.
//...
if(!string.IsNullOrEmpty(del.Headshot)) {
var imageBytes = Convert.FromBase64String(del.Headshot);
string name = $"{del.FirstName} {del.LastName} - {del.Company}".Trim(Path.GetInvalidFileNameChars()) + "_Headshot.jpg";
ZipArchiveEntry entry = zip.CreateEntry(name);
using(Stream entryStream = entry.Open()) {
entryStream.Write(imageBytes, 0, imageBytes.Length));
}
}
//...
Also using a minor hack for known image types when converted to base64
public static class ImagesUtility {
static IDictionary<string, string> mimeMap =
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "IVBOR", "png" },
{ "/9J/4", "jpg" },
//...add others
};
/// <summary>
/// Extract image file extension from base64 string.
/// </summary>
/// <param name="base64String">base64 string.</param>
/// <returns>file extension from string.</returns>
public static string GetFileExtension(string base64String) {
var data = base64String.Substring(0, 5);
var extension = mimeMap[data.ToUpper()];
return extension;
}
}
You could try to determine the file extension from its prefix
if(!string.IsNullOrEmpty(del.Headshot)) {
var imageBytes = Convert.FromBase64String(del.Headshot);
var ext = ImagesUtility.GetFileExtension(del.Headshot) ?? "jpg";
string name = $"{del.FirstName} {del.LastName} - {del.Company}".Trim(Path.GetInvalidFileNameChars()) + $"_Headshot.{ext}";
ZipArchiveEntry entry = zip.CreateEntry(name);
using(Stream entryStream = entry.Open()) {
entryStream.Write(imageBytes, 0, imageBytes.Length));
}
}
Now ideally, if you are able to control the type of image uploaded, then you should also validate and do what ever image processing when the data is being saved along with any needed metadata (ie content type). That way when extracting it from storage, you can be confident that it is the correct type and size. That saves you having to worry about that later on.

Aspose.Drawing and Aspose.Imaging can handle images and cross-platform running on .NET Core (I'm one of the developers).

Related

How can I get image content as pixel and create image from it in ASP.NET Core 6 Web API?

I try to save my images on my server, but I can't let my server save file and virus because of that I want to get image content as pixels of rgb and after that I create image by myself.
I can't use bitmap (or other type in C# like bitmapImage, ... etc) and I don't know how I can do this with sixlabors.ImageSharp.
I have some code that I tried but I can't implement the exact logic that I want (code shown here):
[HttpPost("[action]")]
public async Task<IActionResult> Get([FromForm] ImageFormat file)
{
await using var memoryStream = new MemoryStream();
await file.File.CopyToAsync(memoryStream);
IImageFormat format;
using (var image = Image.Load(memoryStream.ToArray(), out format))
{
using (var output = new MemoryStream())
{
image.Save(output, format);
var responseType = format.Name.ToLower();
return File(output.ToArray(), "application/octet-stream", file.File.FileName);
}
}
return null;
}
Can anybody help me with this problem?
i don't see a reason to convert image into image: there are several format zip-algorythms etc.wich you have to support in that case. example jpg is not bitmap, there is convertion issue - quality of image becomes less each conversion time. Image itself is not executable - it can be used only as container for virus body, can't harm your OSystem itself, another executable part should works somewhere.
But even if you would like to store images on disk, in other format - you can convert image to base64 text (one line of code, like example) - it less harmful and well known way to work with any file type. you can zip image by cszip, you can change file name and extension to hide file type.
I don't see a reasson to convert one image to another for this scenario/task.

Overwrite Existing Jpeg File/Replace Existing Jpeg File with edited jpeg file

I have build a program which allows me to insert comment and the title of an Image through System.Image.Drawing so right now, I have trouble trying to overwrite the existing Jpeg file with the one that has comment and title added into it, but I am encountering error while deleting the file, so I'm not sure what to do as I have tried disposing the file but I cant saved it in that case, due to the fact that I disposed it too early, but I cant saved it because the existing file name is not deleted so I'm kinda stuck in the middle right now.
Here are my codes for it:
public string EditComment(string OriginalFilepath, string newFilename)
{
image = System.Drawing.Image.FromFile(OriginalFilepath);
PropertyItem propItem = image.PropertyItems[0];
using (var file = System.Drawing.Image.FromFile(OriginalFilepath))
{
propItem.Id = 0x9286; // this is the id for 'UserComment'
propItem.Type = 2;
propItem.Value = System.Text.Encoding.UTF8.GetBytes("HelloWorld\0");
propItem.Len = propItem.Value.Length;
file.SetPropertyItem(propItem);
PropertyItem propItem1 = file.PropertyItems[file.PropertyItems.Count() - 1];
file.Dispose();
image.Dispose();
string filepath = Filepath;
if (File.Exists(#"C:\Desktop\Metadata"))
{
System.IO.File.Delete(#"C:\Desktop\Metadata");
}
string newFilepath = filepath + newFilename;
file.Save(newFilepath, ImageFormat.Jpeg);//error appears here
return filepath;
}
}
The Error shown are:
An exception of type 'System.ArgumentException' occurred in System.Drawing.dll but was not handled in user code
Additional information: Parameter is not valid.
The problem is that opening an image from file locks the file. You can get around that by reading the file into a byte array, creating a memory stream from that, and then opening the image from that stream:
public string EditComment(string originalFilepath, string newFilename)
{
Byte[] bytes = File.ReadAllBytes(originalFilepath);
using (MemoryStream stream = new MemoryStream(bytes))
using (Bitmap image = new Bitmap(stream))
{
PropertyItem propItem = image.PropertyItems[0];
// Processing code
propItem.Id = 0x9286; // this is the id for 'UserComment'
propItem.Type = 2;
propItem.Value = System.Text.Encoding.UTF8.GetBytes("HelloWorld\0");
propItem.Len = propItem.Value.Length;
image.SetPropertyItem(propItem);
// Not sure where your FilePath comes from but I'm just
// putting it in the same folder with the new name.
String newFilepath;
if (newFilename == null)
newFilepath = originalFilePath;
else
newFilepath = Path.Combine(Path.GetDirectory(originalFilepath), newFilename);
image.Save(newFilepath, ImageFormat.Jpeg);
return newFilepath;
}
}
Make sure you do not dispose your image object inside the using block as you did in your test code. Not only does the using block exist exactly so you don't have to dispose manually, but it's also rather hard to save an image to disk that no longer exists in memory. Similarly, you seem to open the image from file twice. I'm just going to assume all of those were experiments to try to get around the problem, but do make sure to clean those up.
The basic rules to remember when opening images are these:
An Image object created from a file will lock the file during the life cycle of the image object, preventing the file from being overwritten or deleted until the image is disposed.
An Image object created from a stream will need the stream to remain open for the entire life cycle of the image object. Unlike with files, there is nothing actively enforcing this, but after the stream is closed, the image will give errors when saved, cloned or otherwise manipulated.
Contrary to what some people believe, a basic .Clone() call on the image object will not change this behaviour. The cloned object will still keep the reference to the original source.
Note, if you actually want a usable image object that is not contained in a using block, you can use LockBits and Marshal.Copy to copy the byte data of the image object into a new image with the same dimensions and the same PixelFormat, effectively making a full data clone of the original image. (Note: I don't think this works on animated GIF files) After doing that, you can safely dispose the original and just use the new cleanly-cloned version.
There are some other workarounds for actually getting the image out, but most of them I've seen aren't optimal. These are the two most common other valid workarounds for the locking problem:
Create a new Bitmap from an image loaded from file using the Bitmap(Image image) constructor. This new object will not have the link to that file, leaving you free to dispose the one that's locking the file. This works perfectly, but it changes the image's colour depth to 32-bit ARGB, which might not be desired. If you just need to show an image on the UI, this is an excellent solution, though.
Create a MemoryStream as shown in my code, but not in a using block, leaving the stream open as required. Leaving streams open doesn't really seem like a good idea to me. Though some people have said that, since a MemoryStream is just backed by a simple array, and not some external resource, the garbage collector apparently handles this case fine...
I've also seen some people use System.Drawing.ImageConverter to convert from bytes, but I looked into the internals of that process, and what it does is actually identical to the last method here, which leaves a memory stream open.

ASP MVC Getting image from SQLite db vs. file system

I have two identical images. One is saved as a .png in a folder, the other is saved as a SQLite blob in a database. I'm trying to figure out a good way to persist these images and show them in my view when needed.
I have two methods for trying to get these images into my view. The first one is reading the image files directly from the file system, converting them into a base64 string which I shove into a ViewBag. This method works fine.
The other method is trying to load the image blob directly from the database and then fetch the image from the Model in the view. This doesn't work so well.
Through a couple of breakpoints I've found out that there's a huge different between the base64 strings, depending on my method. I'm not sure why and what the difference between the SQLite image blob and the .png in my file system is, and any help would be appreciated since saving images in a folder seems like extra work, when I could just keep them in my database.
Loading from the file system:
public string FirstImagePath(string slideid, int well)
{
var firstPath = HttpContext.Current.Server.MapPath(slideid);
byte[] firstImageByteData = File.ReadAllBytes(firstPath + "_first" + well + ".png");
string firstImageBase64Data = Convert.ToBase64String(firstImageByteData);
string firstImageDataUrl = string.Format("data:image/png;base64,{0}", firstImageBase64Data);
return firstImageDataUrl;
}
Loading from SQLite db:
public byte[] FirstImage { get; set; }
public string FirstBase64Image
{
get { return ConvertImage(FirstImage);}
}
public string ConvertImage(byte[] imageBytes)
{
byte[] imageArray = imageBytes;
string imageBase64String = Convert.ToBase64String(imageBytes);
string imageDataString = string.Format("data:image/png;base64,{0}", imageBase64String);
return imageDataString;
}
Both methods have their advantages and disadvantages.
If you store your images in a DB it's very easy to move the application across different platforms without having to copy a lot of images. On the other hand your DB is going to get very large over time.
Storing images on disk allows you to overwrite them easily with a copy / paste job etc. However, if you have multiple web servers, you're going to have to make this change several times.
However, this isn't the main issue that I have with your approach. The problem is that you get the images, convert them to base64 and embed them in your output. This is bad practice for many reasons. Firstly, browsers are smart enough to load in multiple resources at a time. For instance, your browser can download multiple images at a time which will drastically increase page load speed. If you have the images embeded it becomes the responsibility of the html parser to render these images which in turn slows the overall download of the html document as it's larger.
I would recommend a hybrid of the two. Upload images to your database. Create an image handler in your application that retrieves an image from the DB and attempts to save it to the file system. If it's successful return this image. The next time you try to get this image (and it's not cached on the users machine) your handler can check the file system to see if it has already been created and return it.
This method gives you the advantages of both the DB and file structure methods. If you decide you need 10 web servers, all of your images will be stored in the DB, and the file system will be automatically populated with your images as and when you need them depending on which server receives the request. I recommend using a GUID or some unique id for your images.
You could even extend this further and pass height and width parameters to the image handler that will resize the image for you before sending it back to the client.

Prevent an exe from being uploaded even after renaming its extention

I am working on asp.net with c#.
There is a file upload control in my form. Everything is working fine.
The problem is that an .exe file can be uploaded by just renaming it. I would also like to restrict the size.
best way in your case is check the first bytes of the file to determine what they are.
you should use FindMimeFromData function to determines the MIME type from the data provided.
Have a look at this file signatures table
and at this SO answer that shows you how get mime type without using extension.
Here there is a table with List of file signatures
exe files have hex signature 4D 5A (In ASCII representation, 0x5A4D is MZ)
from this point we can do this function
public static bool IsExecutable(string filePath)
{
var firstBytes = new byte[2];
using (var fileStream = File.Open(filePath, FileMode.Open))
{
fileStream.Read(firstBytes, 0, 2);
}
return Encoding.UTF8.GetString(firstBytes) == "MZ";
}

Can't convert image to bytes[] C#

I'm very new to this stuff of saving images to the DB, and even when I thought it was very straight forward, it wasn't. What I'm trying to do is read and image file from the same computer in any format, display it in a picture box, and then convert the image to bytes to save it in the DB. Until now, I can display the image in the picture box, but I can't convert the image to bytes. Here's my code:
private void DisplayImage()
{
if (openFileDialog.ShowDialog(this) == DialogResult.OK)
{
try
{
Stream file;
if ((archivo = openFileDialog.OpenFile()) != null)
{
using (file)
{
pictureBox.Image = Image.FromStream(file);
}
}
}
catch (Exception ex)
{
...
}
}
}
That's a simple method that just displays the image in the picture box. The real problem is with the following method:
public static byte[] ConvertImageToBytes(Image image)
{
if (image != null)
{
MemoryStream ms = new MemoryStream();
using (ms)
{
image.Save(ms, ImageFormat.Bmp);
byte[] bytes = ms.ToArray();
return bytes;
}
}
else
{
return null;
}
}
When it tries to save the image to the memory stream, I get the error:
System.Runtime.InteropServices.ExternalException: A generic error occurred in GDI+.
Any ideas on what's happening?
You should use the RawFormat property of the original image as a parameter to the Save method, not default to a Bitmap. This will avoid image format type errors. eg:
image.Save(ms, image.RawFormat);
ms.Position = 0;
byte [] bytes=ms.ToArray();
I'd advise actually saving images to the file-system and simply storing the file path (preferably relative) in the database.
BLOBs (ie images etc) in a database cannot be indexed, are often stored in a secondary, slower access database area and will quickly blow out the size of the database (slower backups etc).
Cant you simply Read the file and load it to a byte[] using the File class:
byte[] imgData = System.IO.File.ReadAllBytes(#"C:\My Pic\Myfile.jpg");
You can pick the image path from your Open Dialog box.
That particular exception generally means that you are trying to save the image as the wrong format. In your code you specify ImageFormat.Bmp - is it actually a bitmap image, or did you perhaps load it from a JPEG or PNG? Attempting to save as a different format from the one you loaded will fail with ExternalException, as specified in the documentation.
Incidentally, I don't recommend storing images in a database and I believe most people here will agree. Databases may be able to handle this task but they are not optimized for it, and you end up hurting the performance of both your database and your application. Unless you are using SQL Server 2008 FILESTREAM columns, it is more efficient to store images on the file system.
It may be stupid to answer my own question, but I just found out that if I want to convert the Image object to bytes, I have to leave the original stream open. I saw this issue in another page I can't quite remember, and I tested it by leaving the stream open and it's true. So the format wasn't the problem. But I will take the advice of all of you and store the images in a separate directory. Thanks for your help guys!
The problem with this is that stream must be open during the lifetime of of the image otherwise will fail.
One solution that worked for me is just to create a copy of the image like this:
using (var ms = new MemoryStream(bytes))
{
_image = new Bitmap(Image.FromStream(ms));
}

Categories

Resources