I'm working on a ASP.NET (3.5) website that contains a treeview; the nodes' values are set to a filepath (on the server) of a PDF file. When the user clicks a treenode, the server-side code gets the node value (file path), creates a FileInfo object, and (after setting all the header Content-type, Cache-Control, etc. correctly) calls Response.TransmitFile(xxxxpath) to send to the user's browser.
This works fine on the major browsers, on major devices (PCs, Macs, iOS devices). The file downloads correctly and opens on the user's machine. But on certain devices and certain browsers, the PDF file does not open. On Android devices, it appears that Firefox downloads and opens the PDFs correctly, but the stock Android browser does not. On a Kindle Fire, it appears the Silk browser downloads the file successfully, but when trying to open it, I see an error: "PDF trailer not found"....or it says the PDF is DRM-protected (which it is not). I haven't tried another browser (if there is one) on the Fire.
I've experimented using anchor links in static HTML markup, and the problem browsers appear to download and display the PDFs correctly when accessed this way. There seems to be an issue (inconsistency?) with the way ASP.NET sends the response to the browser when done via code. I've used Response.Flush, Response.WriteFile, Response.BinaryWrite, Response.Close, Response.End, etc., and they all produce the same result: MOST browsers handle the file, but SOME cannot handle the PDF.
So, is there some issue with the way ASP.NET constructs the Response object (especially when sending back PDFs) that some browsers don't like? TIA.
Quite simply, the answer to your question is "No." You may want to post your code if you have doubts about whether or not you're doing it correctly; otherwise: 'no'. :)
I would think the browsers you mentioned are much more suspect than something as simple and established as writing to the Response stream.
For reference here is a tried and true way I do it using iHttpHandler:
public void ProcessRequest(System.Web.HttpContext context)
{
string sFilePath = context.Server.MapPath(String.Concat("~/App_LocalResources", context.Request.QueryString["path"]));
try
{
context.Response.Clear();
context.Response.ContentType = "application/octet-stream";
int iReadLength = 16384;
int iLastReadLength = iReadLength;
byte[] buffer = new byte[iReadLength];
if (System.IO.File.Exists(sFilePath))
{
System.IO.FileInfo fInfo = new System.IO.FileInfo(sFilePath);
context.Response.AddHeader("Content-Length", fInfo.Length.ToString());
context.Response.AddHeader("Content-Disposition", String.Format("attachment; filename=\"{0}\"", fInfo.Name));
using (System.IO.FileStream input = new System.IO.FileStream(sFilePath, System.IO.FileMode.Open, System.IO.FileAccess.Read))
{
try
{
while (iLastReadLength > 0 && context.Response.IsClientConnected)
{
iLastReadLength = input.Read(buffer, 0, iLastReadLength);
if (iLastReadLength > 0)
{
context.Response.OutputStream.Write(buffer, 0, iLastReadLength);
context.Response.OutputStream.Flush();
}
}
context.Response.Flush();
}
catch
{
}
finally
{
input.Close();
}
}
}
}
catch
{
}
finally
{
}
}
Since you've indicated you are pulling the file from another place, here is how to write that to a memory stream. Just pull from your Response Stream from the remote server (or File Stream, Sql Binary Reader, w/e really), then reset your MemoryStream position and then use the functionality above to write to your Response stream to the client.
int iReadLength = 16384;
long lLastReadLength = iReadLength;
long lDataIndex = 0;
byte[] buffer = new byte[iReadLength];
using (System.IO.MemoryStream msTemp = new System.IO.MemoryStream())
{
while (lLastReadLength > 0)
{
lLastReadLength = reader.GetBytes(0, lDataIndex, buffer, 0, iReadLength);
lDataIndex += lLastReadLength;
if (lLastReadLength > 0)
{
msTemp.Write(buffer, 0, Convert.ToInt32(lLastReadLength));
msTemp.Flush();
}
}
// Reset Memory Position
msTemp.Position = 0;
// Now write to the Response Stream here
}
Related
I am doing video (and also photo) uploading to the server by using HttpURLConnection.
I have an Uri of a video. I open an InputStream this way:
InputStream inputStream = context.getContentResolver().openInputStream(uri);
As video file is pretty big, I can't buffer data while writing it into the outputStream. So I need to use setFixedLengthStreamingMode(contentLength) method of HttpURLConnection. But it requires "contentLength".
The question is, how to get the length of the video?
Please don't suggest getting filepath. On some devices it works, but it often fails (especially on Android 6). They say Uri doesn't necessarily represent a file.
I also stumbled onto situations when after opening device gallery (with Intent) I receive an Uri of a picture, but I fail trying to get filepath from it. So I believe it's not a good way to get filepath from Uri?
Try something like this:
void uploadVideo() {
InputStream inputStream = context.getContentResolver().openInputStream(uri);
// Your connection.
HttpURLConnection connection;
// Do connection setup, setDoOutput etc.
// Be sure that the server is able to handle
// chunked transfer encoding.
connection.setChunkedStreamingMode(0);
OutputStream connectionOs = connection.getOutputStream();
// Read and write a 4 KiB chunk a time.
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
connectionOs.write(buffer, 0, bytesRead);
}
// Close streams, do connection etc.
}
UPDATE: added setChunkedStreamingMode
I'm trying to upload a large file to a document library, but it fails after just a few seconds. The upload single document fails silently, upload multiple just shows a failed message. I've turned up the file size limit on the web application to 500MB, and the IIS request length to the same (from this blog), and increased the IIS timeout for good measure. Are there any other size caps that I've missed?
Update I've tried a few files of various sizes, anything 50MB or over fails, so I assume something somewhere is still set to the webapp default.
Update 2 Just tried uploading using the following powershell:
$web = Get-SPWeb http://{site address}
$folder = $web.GetFolder("Site Documents")
$file = Get-Item "C:\mydoc.txt" // ~ 150MB
$folder.Files.Add("SiteDocuments/mydoc.txt", $file.OpenRead(), $false)
and get this exception:
Exception calling "Add" with "3" argument(s): "<nativehr>0x80070003</nativehr><nativestack></nativestack>There is no file with URL 'http://{site address}/SiteDocuments/mydoc.txt' in this Web."
which strikes me as odd as of course the file wouldn't exist until it's been uploaded? N.B. while the document library has the name Site Documents, it has the URL SiteDocuments. Not sure why...
Are you sure you updated the right webapp? Is the filetype blocked by the server? Is there adequate space in your content database? I would check ULS logs after that and see if there is another error since it seems you hit the 3 spots you would need too update.
for uploading a large file, you can use the PUT method instead of using the other ways to upload a document.
by using a put method you will save the file into content database directly. see the example below
Note: the disadvantage of the code below is you cannot catch the object that is responsible for uploading directly, on other word, you cannot update the additional custom properties of the uploaded document directly.
public static bool UploadFileToDocumentLibrary(string sourceFilePath, string targetDocumentLibraryPath)
{
//Flag to indicate whether file was uploaded successfuly or not
bool isUploaded = true;
try
{
// Create a PUT Web request to upload the file.
WebRequest request = WebRequest.Create(targetDocumentLibraryPath);
//Set credentials of the current security context
request.Credentials = CredentialCache.DefaultCredentials;
request.Method = “PUT”;
// Create buffer to transfer file
byte[] fileBuffer = new byte[1024];
// Write the contents of the local file to the request stream.
using (Stream stream = request.GetRequestStream())
{
//Load the content from local file to stream
using (FileStream fsWorkbook = File.Open(sourceFilePath, FileMode.Open, FileAccess.Read))
{
//Get the start point
int startBuffer = fsWorkbook.Read(fileBuffer, 0, fileBuffer.Length);
for (int i = startBuffer; i > 0; i = fsWorkbook.Read(fileBuffer, 0, fileBuffer.Length))
{
stream.Write(fileBuffer, 0, i);
}
}
}
// Perform the PUT request
WebResponse response = request.GetResponse();
//Close response
response.Close();
}
catch (Exception ex)
{
//Set the flag to indiacte failure in uploading
isUploaded = false;
}
//Return the final upload status
return isUploaded;
}
and here are an example of calling this method
UploadFileToDocumentLibrary(#”C:\test.txt”, #”http://home-vs/Shared Documents/textfile.pdf”);
I mean, when a user chooses the video file from their system, have the web-page already show them the files they want to upload.
I'm already using image file to preview using FileAPI JS. The same I want to do with FileAPI JS for video file.
(So, It must be work within my client side)
Thanks & answers are appreciated :)
You can either use FileReader or createObjectURL. They'll both get the job done, but FileReader has slightly broader support in browsers.
createObjectURL will run synchronously and return a Blob URL, a short string referencing the file in memory. and you can free it up immediately after you're done using it.
FileReader will run asynchronously, requiring a callback, providing a Data URI, a much longer string representing the whole file. This can be very big and will be freed from memory in Javascript garbage collection.
Here's an example that first tries createObjectURL and falls back to FileReader. (Please provide your own error checking, etc.)
var video = document.getElementById('video'),
input = document.getElementById('input');
input.addEventListener('change', function (evt) {
var reader = new window.FileReader(),
file = evt.target.files[0],
url;
reader = window.URL || window.webKitURL;
if (reader && reader.createObjectURL) {
url = reader.createObjectURL(file);
video.src = url;
reader.revokeObjectURL(url); //free up memory
return;
}
if (!window.FileReader) {
console.log('Sorry, not so much');
return;
}
reader = new window.FileReader();
reader.onload = function(evt) {
video.src = evt.target.result;
};
reader.readAsDataURL(file);
}, false);
Working example here: http://jsbin.com/isodes/1/edit
Mozilla has a more detailed article with instructions on how to upload once you've got your file.
IE10 supports both, but IE9 supports neither, so you'll have to fall back to a regular form upload without a preview.
Is there a good way to stream video through asp.net to a normal webpage and mobile? I've tried the following but it doesn't work in my Sony Ericsson K810i. When I try it in my browser, I can see the clip (don't know if it's streaming though).
html:
<object type="video/3gpp"
data="handlers/FileHandler.ashx"
id="player"
width="176"
height="148"
autoplay="true"></object>
FileHandler.ashx (Best way to stream files in ASP.NET):
public void ProcessRequest (HttpContext context) {
string path = "~/files/do.3gp";
string localPath = context.Server.MapPath(path);
if (!File.Exists(localPath))
{
return;
}
// get info about contenttype etc
FileInfo fileInfo = new FileInfo(localPath);
int len = (int)fileInfo.Length;
context.Response.AppendHeader("content-length", len.ToString());
context.Response.ContentType = FileHelper.GetMimeType(fileInfo.Name); // returns video/3gpp
// stream file
byte[] buffer = new byte[1 << 16]; // 64kb
int bytesRead = 0;
using(var file = File.Open(localPath, FileMode.Open))
{
while((bytesRead = file.Read(buffer, 0, buffer.Length)) != 0)
{
context.Response.OutputStream.Write(buffer, 0, bytesRead);
}
}
// finish
context.Response.Flush();
context.Response.Close();
context.Response.End();
}
What you've got isn't "technically" streaming. It's a file download. Your client (browser/phone) sent an HTTP request and your FileHandler.ashx opened the file and wrote the bytes into the response stream. This is exactly the same interaction for a web page request except the data is html text rather than binary data representing a video.
If the phone isn't supporting the video it might be incompatible encoding. If you're sure the video is playable by the phone, see if the phone wants progressive download support (like the iPhone / iPad / iPod Touch require for the media player to "stream" videos.) If this is so, you'll need to look at any of a number of solutions that are available for handling requests for byte-range data and responding to the request with the bytes from the file in the range specified.
I wrote a library for ASP.NET MVC to handle this and my work was mostly done based on this guidance and source code.
I'm trying to create an MJPEG stream, I have a series of jpegs that I want to put together into a stream so that a user can just hit a URL and get an mjpeg stream.
I've been trying for the last few days to get this to work, and it may just not be possible. I've brought up ethereal and listened to the packets coming from an axis camera on the net somewhere, and tried to mimmick it. I originally tried using WCF, and returning a "stream" but then later found out that I would need to set the content type on that stream, so I then tried the WCF REST api, but that suffers from the same problem. so I am now just using a bare bones HTTPListener, and handling the event. I would greatly prefer to use WCF, but I'm not sure that it will allow me to return a stream with the right content type.
so here's what I have for the httpListener .
in the handler of the listener call back I put the following.
HttpListenerResponse response = context.Response;
response.ProtocolVersion = new System.Version(1, 0);
response.StatusCode = 200;
response.StatusDescription = "OK";
response.ContentType = "multipart/x-mixed-replace;boundary=" + BOUNDARY + "\r\n";
System.IO.Stream output = response.OutputStream;
Render(output);
the Render method looks like this
var writer = new StreamWriter(st);
writer.Write("--" + BOUNDARY + "\r\n");
while (true)
{
for (int i = 0; i < imageset.Length; i++)
{
var resource = Properties.Resources.ResourceManager.GetObject(imageset[i]) as Bitmap;
var memStream = new MemoryStream();
resource.Save(memStream,ImageFormat.Jpeg);
byte[] imgBinaryData = memStream.ToArray();
string s = Convert.ToBase64String(imgBinaryData);
writer.Write("Content-type: image/jpeg\r\n");
foreach (var s1 in imgBinaryData)
{
writer.Write((char)s1);
}
writer.Write("\n--" + BOUNDARY + "\n");
writer.Flush();
Thread.Sleep(500);
}
}
At this point I've just added a few jpeg images as properties on the dll, and am iterating over them, eventually these will be dynamic images, but for now I just want to get the thing to work.
From what I understand about the MJPEG (spec) is that the content must be set to multipart/x-mixed-replace and a boundary set. and then you just deliminate the jpegs within the stream by the boundary.
This seems like it should be simpler then I'm making it, but I'm wondering where I'm going wrong. if I load this URL up in IE or Firefox, it just hangs. if I try to make a stub html page with an img tag, whose source is the URL then I get a broken image.
Any ideas, thanks
Josh
Well, as far as I can tell, here are your issues:
The StreamWriter is not a correct choice. Use a regular stream write function is fine. Meaning, you should write data in Byte array instead of string.
You convert the Binary data of the image to String64, the browser does not known that, still thinking it is 32bit data.
Your jpeg frame format is not correct. You should also add Content-Length to the frame header so that the application that receive the stream know when to stop reading rather than having to check for the next boundary string every read. This will result in about 4-5 times faster in reading data. And there are also inconsistency in your new line character, some are "\r\n" while some others are "\n".
While loop is a infinite loop.
So, here is the solution.
Note: There might be some syntax errors but you probably get the general idea.
private byte[] CreateHeader(int length)
{
string header =
"--" + BOUDARY + "\r\n" +
"Content-Type:image/jpeg\r\n" +
"Content-Length:" + length + "\r\n" +
+ "\r\n"; // there are always 2 new line character before the actual data
// using ascii encoder is fine since there is no international character used in this string.
return ASCIIEncoding.ASCII.GetBytes(header);
}
public byte[] CreateFooter()
{
return ASCIIEncoding.ASCII.GetBytes("\r\n");
}
private void WriteFrame(Stream st, Bitmap image)
{
// prepare image data
byte[] imageData = null;
// this is to make sure memory stream is disposed after using
using (MemoryStream ms = new MemoryStream())
{
image.Save(ms, ImageFormat.Jpeg);
imageData = ms.ToArray();
}
// prepare header
byte[] header = CreateHeader(imageData.Length);
// prepare footer
byte[] footer = CreateFooter();
// Start writing data
st.Write(header, 0, header.Length);
st.Write(imageData, 0, imageData.Length);
st.Write(footer, 0, footer.Length);
}
private void Render(Stream st)
{
for (int i = 0; i < imageset.Length; i++)
{
var resource = Properties.Resources.ResourceManager.GetObject(imageset[i]) as Bitmap;
WriteFrame(st, resource);
Thread.Sleep(500);
}
}
There is also an implementation # https://net7mma.codeplex.com/SourceControl/latest the library there can transcode the Http to Rtp compatible on the fly!