I have the following file upload handler:
public class FileUploader : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
HttpRequest request = context.Request;
context.Response.ContentType = "text/html";
context.Response.ContentEncoding = System.Text.Encoding.UTF8;
context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
var tempPath = request.PhysicalApplicationPath + "\\Files\\TempFiles\\";
byte[] buffer = new byte[request.ContentLength];
using (BinaryReader br = new BinaryReader(request.InputStream))
{
br.Read(buffer, 0, buffer.Length);
}
var tempName = WriteTempFile(buffer, tempPath);
context.Response.Write("{\"success\":true}");
context.Response.End();
}
public bool IsReusable
{
get { return true; }
}
private string WriteTempFile(byte[] buffer, string tempPath)
{
var fileName = GetUniqueFileName(tempPath);
File.WriteAllBytes(tempPath + fileName, buffer);
return fileName;
}
private string GetUniqueFileName(string tempPath)
{
var guid = Guid.NewGuid().ToString().ToUpper();
while (File.Exists(tempPath + guid))
{
guid = Guid.NewGuid().ToString().ToUpper();
}
return guid;
}
}
When I upload large files, this is causing OutOfMemoryException. Could someone tell what's right way to upload large files using such a handler?
There is no need to load a file into memory to write it to somewhere. You should be using a small buffer (maybe 8k), and looping over the streams. Or, with 4.0, the CopyTo method. For example:
using(var newFile = File.Create(tempPath)) {
request.InputStream.CopyTo(newFile);
}
(which does the small-buffer/loop for you, using a 4k buffer by default, or allowing a custom buffer-size to be passed via an overload)
You get an OutOfMemoryException because you load your uploaded file into memory.
Avoid this by writing your stream directly to a file.
public void ProcessRequest(HttpContext context)
{
const int BufferSize = 4096;
HttpRequest request = context.Request;
context.Response.ContentType = "text/html";
context.Response.ContentEncoding = System.Text.Encoding.UTF8;
context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
var tempFilePath = Path.GetTempFileName();
using (Stream fs = File.OpenWrite(tempFilePath));
{
byte[] buffer = new byte[BufferSize];
int read = -1;
while(read = request.InputStream.Read(buffer, 0, buffer.Length) > 0)
{
fs.Write(buffer, 0, buffer.Length);
}
}
context.Response.Write("{\"success\":true}");
context.Response.End();
}
edit: removed binaryreader
Related
I use IHttpHandler to Download a file from Server.Everything works fine.
But it shows the data on browser instead of download it. I need to download the file from the server like we do download from other servers.
Could anyone suggest me, what should I do to download the file,
Or what is the convenient way to download a file(pdf,mp4 etc).
public void ProcessRequest(HttpContext context)
{
string strPathName = "";
if (context.Request.QueryString["fileName"] != null)
{
strPathName = context.Request.QueryString["fileName"].ToString();
}
string filename = context.Server.MapPath("~/MyPath/" + strPathName);
System.IO.Stream oStream = null;
oStream =
new System.IO.FileStream
(path: filename,
mode: System.IO.FileMode.Open,
share: System.IO.FileShare.Read,
access: System.IO.FileAccess.Read);
try
{
context.Response.ClearHeaders();
context.Response.Buffer = false;
context.Response.ContentType = "application/octet-stream";
context.Response.AddHeader("Content-Disposition", "attachment");
long lngFileLength = oStream.Length;
context.Response.AddHeader("Content-Length", lngFileLength.ToString());
long lngDataToRead = lngFileLength;
while (lngDataToRead > 0)
{
if (context.Response.IsClientConnected)
{
int intBufferSize = 8 * 1024;
byte[] bytBuffers =
new System.Byte[intBufferSize];
int intTheBytesThatReallyHasBeenReadFromTheStream =
oStream.Read(buffer: bytBuffers, offset: 0, count: intBufferSize);
context.Response.OutputStream.Write
(buffer: bytBuffers, offset: 0,
count: intTheBytesThatReallyHasBeenReadFromTheStream);
context.Response.Flush();
lngDataToRead =
lngDataToRead - intTheBytesThatReallyHasBeenReadFromTheStream;
}
else
{
lngDataToRead = -1;
}
}
}
catch { }
finally
{
if (oStream != null)
{
oStream.Close();
oStream.Dispose();
oStream = null;
}
context.Response.Close();
}
}
You can't directly download a file via ajax, it only returns the data into a JS variable in the page's code, instead of triggering a traditional request and download.
Your button needs to make a standard HTTP request, not an ajax call.
I'm working on a project where I need to send large audio files via streams from a client to a server. I'm using the ASP.NET Web Api to communicate between client and server. My client has a "SendFile" method which I believe works fine, but I don't know how to make my server receive the data I'm sending via a stream. My client code looks like this so far:
private const int MAX_CHUNK_SIZE = (1024 * 5000);
private HttpWebRequest webRequest = null;
private FileStream fileReader = null;
private Stream requestStream = null;
public bool SendAudio(string uri, string file)
{
byte[] fileData;
fileReader = new FileStream(file, FileMode.Open, FileAccess.Read);
webRequest = (HttpWebRequest)WebRequest.Create(uri);
webRequest.Method = "POST";
webRequest.ContentLength = fileReader.Length;
webRequest.Timeout = 600000;
webRequest.Credentials = CredentialCache.DefaultCredentials;
webRequest.AllowWriteStreamBuffering = false;
requestStream = webRequest.GetRequestStream();
long fileSize = fileReader.Length;
long remainingBytes = fileSize;
int numberOfBytesRead = 0, done = 0;
while (numberOfBytesRead < fileSize)
{
SetByteArray(out fileData, remainingBytes);
done = WriteFileToStream(fileData, requestStream);
numberOfBytesRead += done;
remainingBytes -= done;
}
fileReader.Close();
return true;
}
public int WriteFileToStream(byte[] fileData, Stream requestStream)
{
int done = fileReader.Read(fileData, 0, fileData.Length);
requestStream.Write(fileData, 0, fileData.Length);
return done;
}
private void SetByteArray(out byte[] fileData, long bytesLeft)
{
fileData = bytesLeft < MAX_CHUNK_SIZE ? new byte[bytesLeft] : new byte[MAX_CHUNK_SIZE];
}
My server looks like this:
[HttpPost]
[ActionName("AddAudio")]
public async Task<IHttpActionResult> AddAudio([FromUri]string name)
{
try
{
isReceivingFile = true;
byte[] receivedBytes = await Request.Content.ReadAsByteArrayAsync();
if (WriteAudio(receivedBytes, name) == true)
{
isReceivingFile = false;
return Ok();
}
else
{
isReceivingFile = false;
return BadRequest("ERROR: Audio could not be saved on server.");
}
}
catch (Exception ex)
{
isReceivingFile = false;
return BadRequest("ERROR: Audio could not be saved on server.");
}
}
public bool WriteAudio(byte[] receivedBytes, string fileName)
{
string file = Path.Combine(#"C:\Users\username\Desktop\UploadedFiles", fileName);
using (FileStream fs = File.Create(file))
{
fs.Write(receivedBytes, 0, receivedBytes.Length);
}
return true;
}
The server code has the original code I wrote for it, before deciding to try and make it work with streams. The server code still works if I send a small file (under 30 MB), but if I send a large file my server gets a "outofmemoryexception". I can't figure out how to make the server take in the data via a stream. In my search for solutions I've come across a lot of examples with sockets and TCPClient, but that's not how we want to do it on this project. Can anybody help, please?
if I send a large file my server gets a "outofmemoryexception"
Well, it's reading the entire stream into memory right here:
byte[] receivedBytes = await Request.Content.ReadAsByteArrayAsync();
What you want to do is copy the stream from one location to another, without loading it all into memory at once. Something like this should work:
[HttpPost]
[ActionName("AddAudio")]
public async Task<IHttpActionResult> AddAudio([FromUri]string name)
{
try
{
string file = Path.Combine(#"C:\Users\username\Desktop\UploadedFiles", fileName);
using (FileStream fs = new FileStream(file, FileMode.Create, FileAccess.Write,
FileShare.None, 4096, useAsync: true))
{
await Request.Content.CopyToAsync(fs);
}
return Ok();
}
catch (Exception ex)
{
return BadRequest("ERROR: Audio could not be saved on server.");
}
}
I have a processes I made that has been working well for several months now. The process recursively zips up all files and folders in a given directory and then uploads the zip file to an FTP server. Its been working, but now, the zip file is exceeding 2gb and its erroring out. Can someone please help me figure out how to get around this 2gb limit? I commented the offending line in the code. Here is the code:
class Program
{
// Location of upload directory
private const string SourceFolder = #"C:\MyDirectory";
// FTP server
private const string FtpSite = "10.0.0.1";
// FTP User Name
private const string FtpUserName = "myUserName";
// FTP Password
private const string FtpPassword = "myPassword";
static void Main(string[] args)
{
try
{
// Zip everything up using SharpZipLib
string tmpFile = Path.GetTempFileName();
var zip = new ZipOutputStream(File.Create(tmpFile));
zip.SetLevel(8);
ZipFolder(SourceFolder, SourceFolder, zip);
zip.Finish();
zip.Close();
// Upload the zip file
UploadFile(tmpFile);
// Delete the zip file
File.Delete(tmpFile);
}
catch (Exception ex)
{
throw ex;
}
}
private static void UploadFile(string fileName)
{
string remoteFileName = "/ImagesUpload_" + DateTime.Now.ToString("MMddyyyyHHmmss") + ".zip";
var request = (FtpWebRequest)WebRequest.Create("ftp://" + FtpSite + remoteFileName);
request.Credentials = new NetworkCredential(FtpUserName, FtpPassword);
request.Method = WebRequestMethods.Ftp.UploadFile;
request.KeepAlive = false;
request.Timeout = -1;
request.UsePassive = true;
request.UseBinary = true;
// Error occurs in the next line!!!
byte[] b = File.ReadAllBytes(fileName);
using (Stream s = request.GetRequestStream())
{
s.Write(b, 0, b.Length);
}
using (var resp = (FtpWebResponse)request.GetResponse())
{
}
}
private static void ZipFolder(string rootFolder, string currentFolder, ZipOutputStream zStream)
{
string[] subFolders = Directory.GetDirectories(currentFolder);
foreach (string folder in subFolders)
ZipFolder(rootFolder, folder, zStream);
string relativePath = currentFolder.Substring(rootFolder.Length) + "/";
if (relativePath.Length > 1)
{
var dirEntry = new ZipEntry(relativePath) {DateTime = DateTime.Now};
}
foreach (string file in Directory.GetFiles(currentFolder))
{
AddFileToZip(zStream, relativePath, file);
}
}
private static void AddFileToZip(ZipOutputStream zStream, string relativePath, string file)
{
var buffer = new byte[4096];
var fi = new FileInfo(file);
string fileRelativePath = (relativePath.Length > 1 ? relativePath : string.Empty) + Path.GetFileName(file);
var entry = new ZipEntry(fileRelativePath) {DateTime = DateTime.Now, Size = fi.Length};
zStream.PutNextEntry(entry);
using (FileStream fs = File.OpenRead(file))
{
int sourceBytes;
do
{
sourceBytes = fs.Read(buffer, 0, buffer.Length);
zStream.Write(buffer, 0, sourceBytes);
} while (sourceBytes > 0);
}
}
}
You are trying to allocate an array possessing more than 2billion elements. .NET limits the maximum size of an array is System.Int32.MaxValue i.e. 2Gb is the upper bound.
You're better off reading the file in pieces an uploading it in pieces; e.g using a loop reading:
int buflen = 128 * 1024;
byte[] b = new byte[buflen];
FileStream source = new FileStream(fileName, FileMode.Open);
Stream dest = request.GetRequestStream();
while (true) {
int bytesRead = source.Read(buf, 0, buflen);
if (bytesRead == 0) break;
dest.Write(buf, 0, bytesRead);
}
The problem isn't in the zip, but in the File.ReadAllBytes call, which returns an array which has the default size limit of 2GB.
It is possible to disable this limit, as detailed here. I'm assuming you're already compiling this specifically for 64 bit to handle these kind of file sizes. Enabling this option switches .NET over to using 64 bit addresses for arrays instead of the default 32 bit addresses.
It would probably be better to split the archive into parts and upload them separately however. As far as I know the built in ZipFile class doesn't support multi-part archives, but several of the third party libraries do.
Edit: I was thinking about the resulting zip output, rather than the input. To load a huge amount of data INTO the ZipFile, you should use the Buffer based approach suggested by Petesh and philip.
I have a C# windows form application which downloads file from a url(asp.net application) but it is not returning full image lets say image is of 780kb the file that windows form creates is 381 bytes exactly.
I am not able to figure out the issue. Please help.
The code i am using for download is:
public bool getFileFromURL(string url, string filename)
{
long contentLength = 0;
Stream stream = null;
try
{
WebRequest req = WebRequest.Create(url);
WebResponse response = req.GetResponse();
stream = response.GetResponseStream();
contentLength = response.ContentLength;
// Transfer the file
byte[] buffer = new byte[10 * 1024]; // 50KB at a time
int numBytesRead = 0;
long totalBytesRead = 0;
using (FileStream fileStream = new FileStream(filename, FileMode.Create))
{
using (BinaryWriter fileWriter = new BinaryWriter(fileStream))
{
while (stream.CanRead)
{
numBytesRead = stream.Read(buffer, 0, buffer.Length);
if (numBytesRead == 0) break;
totalBytesRead += numBytesRead;
fileWriter.Write(buffer, 0, numBytesRead);
}
fileWriter.Close();
}
fileStream.Close();
}
stream.Close();
response.Close();
req.Abort();
return true;
}
catch (Exception)
{
return false;
}
}
This is my asp.net app code:
using (PortalEntities db = new PortalEntities())
{
PortalModel.Command command = db.Commands.SingleOrDefault(c => c.id == id);
var filePath = Server.MapPath("~/Uploads/"+command.arguments);
if (!File.Exists(filePath))
return;
var fileInfo = new System.IO.FileInfo(filePath);
Response.ContentType = "image/jpg";
Response.AddHeader("Content-Disposition", String.Format("attachment;filename=\"{0}\"", filePath));
Response.AddHeader("Content-Length", fileInfo.Length.ToString());
Response.WriteFile(filePath);
Response.End();
}
That's an awful lot of code to write some bytes out to a file from a web response. How about something like this (.NET 4+):
public static bool GetFileFromURL(string url, string filename)
{
try
{
var req = WebRequest.Create(url);
using (Stream output = File.OpenWrite(filename))
using (WebResponse res = req.GetResponse())
using (Stream s = res.GetResponseStream())
s.CopyTo(output);
return true;
}
catch
{
return false;
}
}
You can download image in more elegant way, it was discussed before here Unable to locate FromStream in Image class
And use File.WriteAllBytes Method to save the byte array as a file, more info at
http://msdn.microsoft.com/en-us/library/system.io.file.writeallbytes(v=vs.110).aspx
So all your client code can be replaced with
public void getFileFromURL(string url, string filename)
{
using (var webClient = new WebClient())
{
File.WriteAllBytes(filename,webClient.DownloadData(url));
}
}
Dude, why are you not using WebClient.DownloadFileAsync?
private void DownloadFile(string url, string path)
{
using (var client = new System.Net.WebClient())
{
client.DownloadFileAsync(new Uri(url), path);
}
}
That's pretty much it, but this method can't download over 2GB. But i don't think the image is that big xD.
Hope it helps!
UPDATE: Got it! Answer posted below... I will clean up the question so it may be more useful to someone else.
I have a service that uses a library to generate thumbnails from html pages using a WebBrowser control. That works fine but I need to create a WCF service that allows you to pass in a uri, then the service converts that to a thumbnail and returns it. I've tried it as a Stream and a byte[]. It creates the image in the service, I save it as a file just to prove it, but when I consume it I get data (more than I send) and I am unable to save it as a viewable image. Any suggestions would be appreciated. I am not a WCF expert so I'm hoping I've just missed something that will be easy to spot.
Here is the service, it's hosted in a console app and the Main() procedure is included.
namespace RawImageService
{
[ServiceContract]
public interface IImageServer
{
[OperationContract]
[WebInvoke(Method = "GET", UriTemplate = "/image/?uri={uri}")]
Stream HtmlToImage(string uri);
}
public class Service : IImageServer
{
public Stream HtmlToImage(string uri)
{
string path = #"D:\Templates\HtmlServiceImage.bmp";
if( File.Exists(path) )
File.Delete(path);
if (string.IsNullOrEmpty(uri))
{
return null;
}
else
{
if ((uri.IndexOf("file:", System.StringComparison.Ordinal) < 0) &&
(uri.IndexOf("http", System.StringComparison.Ordinal) < 0))
uri = "http://" + uri;
Thumbnail.Uri = uri;
try
{
Bitmap bitmap =
HtmlToThumbnail.WebsiteThumbnail.GetThumbnail(Thumbnail.Uri, Thumbnail.Width,
Thumbnail.Hight, Thumbnail.ThumbWidth,
Thumbnail.ThumbHight);
using (MemoryStream ms = new MemoryStream())
{
bitmap.Save(ms, ImageFormat.Jpeg);
bitmap.Save(path, ImageFormat.Jpeg);
ms.Position = 0;
WebOperationContext.Current.OutgoingResponse.ContentType = "image/jpeg";
return ms;
}
}
catch (Exception)
{
throw;
}
return null;
}
}
static void Main(string[] args)
{
string baseAddress = "http://" + Environment.MachineName + ":8000/";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
host.AddServiceEndpoint(typeof(IImageServer), new WebHttpBinding(), "").Behaviors.Add(new WebHttpBehavior());
host.Open();
Console.WriteLine("Service is running");
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}
}
Here is my attempt to consume the service:
private static void Main(string[] args)
{
string uri = string.Concat("http://localhost:8000",
string.Format("/image/?uri={0}", "file:///D:/Templates/Test4.htm"));
tryThis(uri);
}
public static void tryThis(string uri)
{
HttpWebRequest request = WebRequest.Create(uri) as HttpWebRequest;
request.Method = "GET";
// Get response
using (WebResponse response = request.GetResponse() as WebResponse)
{
using (Stream stream = response.GetResponseStream())
{
byte[] buffer = new byte[response.ContentLength];
MemoryStream ms = new MemoryStream();
int bytesRead, totalBytesRead = 0;
do
{
bytesRead = stream.Read(buffer, 0, buffer.Length);
totalBytesRead += bytesRead;
ms.Write(buffer, 0, bytesRead);
} while (bytesRead > 0);
string path = #"D:/templates/fs.jpg";
if (File.Exists(path))
File.Delete(path);
var fs = new FileStream(path, FileMode.Create);
fs.Write(ms.ToArray(), 0, totalBytesRead);
fs.Flush();
fs.Close();
}
}
}
static byte[] GetBytes(string str)
{
byte[] bytes = new byte[str.Length * sizeof(char)];
System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
return bytes;
}
There is nothing additional in the .config files. I'm going to start looking into that but my understanding is I can configure everything in the app itself.
I don't care which way I get it working, I just need to get it working. I appreciate any suggestions. Thanks.
I got the Stream method working. Here is the code. I can access it like this:
http://localhost:8000/image/?uri=file:///D:/Templates/Test4.htm
Here is the new method. If you replace the code above with this it works.
public Stream HtmlToImage(string uri)
{
string path = #"D:\Templates\HtmlServiceImage.bmp";
if( File.Exists(path) )
File.Delete(path);
if (string.IsNullOrEmpty(uri))
{
return null;
}
else
{
if ((uri.IndexOf("file:", System.StringComparison.Ordinal) < 0) &&
(uri.IndexOf("http", System.StringComparison.Ordinal) < 0))
uri = "http://" + uri;
Thumbnail.Uri = uri;
try
{
Bitmap bitmap =
HtmlToThumbnail.WebsiteThumbnail.GetThumbnail(Thumbnail.Uri, Thumbnail.Width,
Thumbnail.Hight, Thumbnail.ThumbWidth,
Thumbnail.ThumbHight);
MemoryStream ms = new MemoryStream();
bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
ms.Position = 0;
WebOperationContext.Current.OutgoingResponse.ContentType = "image/jpeg";
return ms;
}
catch (Exception)
{
throw;
}
return null;
}
}