My MVC app creates images at runtime that I do NOT need to save on the disk.
What's the best way to send those to the requesting browser?
Note that the images will never be the same, so there is no reason to save them on disk first.
The server will draw the random image and will return the image to the calling client. I'm trying to understand what the best format for this type of operation is (bitmap, image ...) so that the streaming back to the server is as smooth and as fast as possible.
If you are sending them in the same page where you create it, then you can modify your Response to send it directly.
//After having your bitmap created...
MemoryStream ms = new MemoryStream();
bmp.Save(ms, ImageFormat.PNG);
bmp.Dispose();
byte[] data = ms.ToArray();
Response.ContentType = "image/png";
Response.ContentLength = data.Length;
using(var str = Response.GetResponseStream())
str.Write(data, 0, data.Length);
Response.End();
If you can get the bytes of the bitmap, then it's easy to return it to the client
public ActionResult GetImage()
{
byte[] byteArray = MagicMethodToGetImageData();
return new FileContentResult(byteArray, "image/jpeg");
}
Moreover, if you want to return the image plus some data, you can encode the bytes as base64 and wrap it in a JSON like this:
public ActionResult GetImage()
{
byte[] byteArray = MagicMethodToGetImageData();
var results = new
{
Image = Convert.ToBase64String(byteArray),
OtherData = "some data"
};
return Json(results);
}
One possibility would be to use the FileContentResult class to read the file content and directly show it or offer to download. A solution could look like this:
private FileContentResult getFileContentResult(string name, bool download = true)
{
if (!string.IsNullOrEmpty(name))
{
// don't forget to set the appropriate image MIME type
var result = new FileContentResult(System.IO.File.ReadAllBytes(name), "image/png");
if (download)
{
result.FileDownloadName = Server.UrlEncode(name);
}
return result;
}
return null;
}
The use this method in some Action like this:
public ActionResult GetImage(string name)
{
return getFileContentResult(name, true);
// or use the image directly for example in a HTML img tag
// return getFileContentResult(name);
}
The class is pretty robust and fast - I have made good experience using it for exactly the same purpose.
Related
I serialize images using the following code:
public static string SerializeImage(Image image)
{
using (MemoryStream memoryStream = new MemoryStream())
{
image.Save(memoryStream, image.RawFormat);
return Convert.ToBase64String(memoryStream.ToArray());
}
}
and deserialize the images by doing the following
public static Image DeserializeImage(string serializedImage)
{
byte[] imageAsBytes = Convert.FromBase64String(serializedImage);
using (MemoryStream memoryStream = new MemoryStream(imageAsBytes, 0, imageAsBytes.Length))
{
memoryStream.Write(imageAsBytes, 0, imageAsBytes.Length);
return Image.FromStream(memoryStream, true);
}
}
If I have an image and does
string serializedImage1 = SerializeImage(image);
Image deserializedImage = DeserializeImage(serializedImage1);
string serializedImage2 = SerializeImage(deserializedImage );
Then
serializedImage1 == serializedImage2;
as expected. But it is not always the case.
If I serialize an image on Process 1, and then redeserialize and reserialize it on Process 2, then the result of the reserialization on Process 2 is not the same as on the Process 1. Everything works, but a few bytes in the beginning of the serialization are different.
Worst, if I do the same thing on 2 different dll (or thread, I'm not sure), it seems the serialization result is not the same too. Again, the serialization/deserialization works, but a few bytes are different.
The image the first time is loaded with the following function :
public static Image GetImageFromFilePath(string filePath)
{
var uri = new Uri(filePath);
var bitmapImage = new BitmapImage(uri);
bitmapImage.Freeze();
using (var memoryStream = new MemoryStream())
{
var pngBitmapEncoder = new PngBitmapEncoder();
pngBitmapEncoder.Frames.Add(BitmapFrame.Create(bitmapImage));
pngBitmapEncoder.Save(memoryStream);
Image image = Image.FromStream(memoryStream);
return image;
}
}
Note however that it happens even if the image is loaded twice with the DeserializeImage() function.
The tests I have done are with ImageFormat.Jpeg and ImageFormat.Png.
First question, why it does this ? I would have expected the result to be always the same, but I suppose some salt is used when doing the Image.Save().
Second question : I want to have a deterministic way to serialize an image, keeping the image format intact. The goal is to save the image in a DB and also to compare serialized images to know if it already exists in the system where this function is used.
Well, I discovered while trying to solve this that a png or jpeg Image object inside C# has some metadata associated to it and doing what I was doing is just not a reliable way to compare images.
The solution I used was derived from this link
https://insertcode.wordpress.com/2014/05/13/compare-content-of-two-files-images-in-c/
So what I do finally is save the images inside the system with the SerializeImage(Image image) function previously described, and when I want to consume it I deserialize it with the DeserializeImage(string serializedImage) function previously described. But when I want to compare images I use the following functions
public static bool ImagesAreEqual(Image image1, Image image2)
{
string image1Base64Bitmap = GetImageAsBase64Bitmap(image1);
string image2Base64Bitmap = GetImageAsBase64Bitmap(image2);
return image1Base64Bitmap.Equals(image2Base64Bitmap);
}
public static string GetImageAsBase64Bitmap(Image image)
{
using (var memoryStream = new MemoryStream())
{
using (var bitmap = new Bitmap(image))
{
bitmap.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Bmp);
}
return Convert.ToBase64String(memoryStream.ToArray());
}
}
That convert the image to raw bitmap before comparing them.
This does a perfect job for me in all my needed cases : the formats of the images are saved/restored correctly, and I can compare them between them to check if they are the same without having to bother with the possibly different serialization.
I am trying to return a PDF file from my ASP.NET Core 2 controller.
I have this code
(mostly borrowed from this SO question):
var net = new System.Net.WebClient();
//a random pdf file link
var fileLocation = "https://syntera.io/documents/T&C.pdf";/
var data = net.DownloadData(fileLocation);
MemoryStream content = null;
try
{
content = new MemoryStream(data);
return new FileStreamResult(content, "Application/octet-stream");
}
finally
{
content?.Dispose();
}
This code above is part of a service class that my controller calls. This is the code from my controller.
public async Task<IActionResult> DownloadFile(string fileName)
{
var result = await _downloader.DownloadFileAsync(fileName);
return result;
}
But I keep getting ObjectDisposedException: Cannot access a closed Stream.
The try and finally block was an attempt to fix it , from another SO question .
The main question is A) Is this the right way to send a PDF file back to the browser and B) if it isn't, how can I change the code to send the pdf to the browser?
Ideally , I don't want to first save the file on the server and then return it to the controller. I'd rather return it while keeping everything in memory.
The finally will always get called (even after the return) so it will always dispose of the content stream before it can be sent to the client, hence the error.
Ideally , I don't want to first save the file on the server and then return it to the controller. I'd rather return it while keeping everything in memory.
Use a FileContentResult class to take the raw byte array data and return it directly.
FileContentResult: Represents an ActionResult that when executed will write a binary file to the response.
async Task<IActionResult> DownloadFileAsync(string fileName){
using(var net = new System.Net.WebClient()) {
byte[] data = await net.DownloadDataTaskAsync(fileName);
return new FileContentResult(data, "application/pdf") {
FileDownloadName = "file_name_here.pdf"
};
}
}
No need for the additional memory stream
You must specify :
Response.AppendHeader("content-disposition", "inline; filename=file.pdf");
return new FileStreamResult(stream, "application/pdf")
For the file to be opened directly in the browser.
So, i am getting the byte array of a LongRaw image from Oracle...
I am using a webapi to this. After get the array, how i use it on the Client-side ?
Do Its better i convert to base64string and pass this value converting just at the client side ?
cmd.InitialLONGFetchSize = -1;
var reader = cmd.ExecuteReader();
if (reader.Read())
{
// Fetch the LONG RAW
OracleBinary imgBinary = reader.GetOracleBinary(0);
// Get the bytes from the binary obj
byte[] imgBytes = imgBinary.IsNull ? null : imgBinary.Value;
//var imgString = Uri.EscapeDataString(Convert.ToBase64String(imgBytes));
}
//CRIO A LISTA
lretorno.Load(reader, LoadOption.OverwriteChanges, "BUSCAFOTO");
reader.Close();
connection.Close();
connection.Dispose();
var teste = lretorno.Tables[0].AsEnumerable().Select(row => new FotoEnvolvido
{
FOTO = (byte[])(row["FOTO"]),
//FOTO = Convert.ToString(row["FOTO"]),
});
return teste;
You can write a Web API Controller that returns the binary data of an image. Base64 strings impose a overhead of the amount of bytes that have to be transmitted. Please avoid this.
A sample controller can look like this example:
public class WebApiController : ApiController
{
public async Task<HttpResponseMessage> Get(string id)
{
var bytes = await GetBytesFromDataLayerAsync(id);
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
var stream = new MemoryStream(bytes);
result.Content = new StreamContent(stream);
result.Content.Headers.ContentType =
new MediaTypeHeaderValue("image/jpeg");
return result;
}
private async Task<byte[]> GetBytesFromDataLayerAsync(string id)
{
// put your Oracle logic here
return ...
}
}
Depending on what your doing as rboe said writing the bytes directly to the client will save some data size(approx. 37%) and computing overhead. If your not only displaying jpeg images you should also set the mime-type to the correct value... take a look at this source for a rather complete set of extension to mime-type mappings. If you do not know the mime-type you can try "application/octet-stream" as that is the general mime-type for binary data.
If your displaying your content via web browser you could just use an <img> tag something like <img src="view_image.aspx?id=5"> you can even create the dynamically with javascript/jQuery.
If you really do want the image data embedded in a json request which might be useful if you have a lot of little icons and don't want a ton of requests (with http/2 I don't think this will matter) or another reason, then yes first encode the binary data using...
string base64EncodedData = Convert.ToBase64String(bytes);
If the client is javascript you can decode using the latest browsers native functions
var decodedImageData = window.atob(base64EncodedData);
See:
mozilla.org docs
This answer
This answer
If you are however just sending it to another c# endpoint you can use...
byte[] decodedImageData = Convert.FromBase64String(base64EncodedData);
And like I mentioned in the comment to ensure it's encrypted just make the site only support https:// and if you don't have a SSL cert you can get one for free from http://startssl.com
I need to return from web service an image that is stored at the disk.
In my controller I perform some search operations and send file. Here is my code.
public HttpResponseMessage Get([FromUri]ShowImageRequest req)
{
// .......................
// .......................
// load image file
var imgStream = new MemoryStream();
using (Image image = Image.FromFile(fullImagePath))
{
image.Save(imgStream, ImageFormat.Jpeg);
}
imgStream.Seek(0, SeekOrigin.Begin); // it does not work without this
var res = new HttpResponseMessage(HttpStatusCode.OK);
res.Content = new StreamContent(imgStream);
res.Content.Headers.ContentType = new ediaTypeHeaderValue("image/jpeg");
return res;
}
If I do not add this line, I see in fiddler response body length 0
imgStream.Seek(0, SeekOrigin.Begin);
Otherwise it works. What am I missing and why do I need to do this?
After saving the stream position is at the end. This means that reading from it returns no bytes.
Everyone runs into this exact issue once :)
I'm migrating to c# from VFP and need help with FileToStr(path), actually we have thousands of files stored into a database with this function, and for now we want to maintain compatibility with VFP.
FileToStr() help at VFP help library doesn't say anything about what kind of Encoding perform with the file just
FILETOSTR(cFileName) = Returns the contents of a file as a character string.
For now this is a Black Box. I have two questions:
There is a c# function that perform the same task as VFP FileToStr()?
What Kind of encoding perform FileToStr() to the file?
Actually I have this function to convert the file:
public async Task<string> ImageToStrAsync(string path)
{
string BitmapToImage = await Task.Factory.StartNew(() =>
{
Bitmap bm = new Bitmap(path);
TypeConverter cv = TypeDescriptor.GetConverter(typeof(Bitmap));
return Convert.ToBase64String(
(byte[])cv.ConvertTo(bm, typeof(byte[]))
);
});
return BitmapToImage;
}
Another had previously answered and then deleted it... Don't know why.
What you are looking for is...
string fileContent = System.IO.File.ReadAllText( someFileNameVariable );
OR
string fileContent = System.IO.File.ReadAllText( someFileNameVariable, System.Text.Encoding.UTF8 );
(or other encoding option... ASCII, UTF7, etc as enumerated option).
As for binary, such as for image files..
byte[] binaryContent = System.IO.File.ReadAllBytes( someBinaryFileName );
And to get a byte[] array that you have stored, such as a bitmap image into an actual image object, you could do the following.
BitmapImage yourBmp = new BitmapImage();
using (var ms = new System.IO.MemoryStream(binaryContent))
{
yourBmp.BeginInit();
yourBmp.CacheOption = BitmapCacheOption.OnLoad;
yourBmp.StreamSource = ms;
yourBmp.EndInit();
}
And converting a byte array to a string...
string result = System.Text.Encoding.UTF8.GetString(byteArray)