I'm using VS 2013, VB.Net, and Telerik 2016 Q1 in an MVC application. I'm using the Telerik RadFlowDocument to create a report, and send it to the user as a pdf. I have successfully created the report and saved it locally. My issue is when I try to convert it to a memory stream and send it back to the user, the user is never given the option to download the report.
I have a button that starts the process via ajax:
function supervisorPDF() {
$.ajax({
url: '#Url.Action("GenerateSupervisorReportPDF", "InteriorReport")',
type: 'POST'
})
.success(function (result) {
})
.error(function (xhr, status) {
alert(status);
})
}
I have a sub routine that receives the ajax call and then generates the report:
Sub GenerateSupervisorReportPDF()
Dim generator As New PdfGenerator
Dim selectedSupervisorName = System.Web.HttpContext.Current.Session("selectedSupervisorName").ToString()
Dim selectedSupervisorIdNumber = System.Web.HttpContext.Current.Session("selectedSupervisorIdNumber").ToString()
Dim report As New RadFlowDocument
Dim viewModel As New SupervisorReportViewModel
Dim model = viewModel.getSupervisorReportInfo(selectedSupervisorName, selectedSupervisorIdNumber)
report = generator.generateSupervisorPdf(model)
Dim provider As New PdfFormatProvider()
Dim output As New MemoryStream
provider.Export(report, output)
Dim buffer As Byte()
Try
buffer = output.ToArray
Response.ClearContent()
Response.ClearHeaders()
Response.ContentType = "application/pdf"
Response.OutputStream.Write(buffer, 0, buffer.Length)
Response.AddHeader("Content-Disposition", "attachment;filename=SupervisorReport.pdf")
Response.End()
Catch ex As Exception
End Try
End Sub
In this subroutine, if I declare output as a local file path everything works as intended. However when using this implementation my Response.OutputStream always ends up null. No errors are thrown.
What do I need to change to send this report back to the user in such a way that they have the option of downloading the report?
You won't be able to do this via ajax.
In order for the browser to receive and handle
Content-Disposition:attachment
it must be browsed to directly.
The ajax call may download the binary to jquery, but jquery won't be able to save it locally.
Easiest option is to have the browser open a link to the action that generates the PDF directly, eg:
<a href='#Url.Action("GenerateSupervisorReportPDF", "InteriorReport")'>download report</a>
As an extra, you can reduce all of the Response. code to a simple return FileStreamResult(... and it should handle prompting the user to save etc - I'll leave this to you (as I don't have the exact syntax to hand)
Related
I am looking to create a file on the fly and offer a download link to the user in a GRAILS application.
I followed the approach from here. I have no errors however it doesn't seem to work. Here's my controller code.
`render (file: pptFile, fileName:'someppt.pptx', contentType: 'application/octet-stream')
Client side code makes an AJAX call to retrieve the file from server. It does not cause the server to force downloading of the file on the client (browser). Here's the client side code.
$.ajax({
type : 'POST',
url : '<<URL>>',
success: function(result) {
var uri = 'data:application/octet-stream;charset=UTF-8,' +
encodeURIComponent(result);
window.open(uri, 'somePPT.pptx');
},
failure: function(){
alert ('failure')
}
});
Perhaps something akin to this (paraphrased, but used for downloading a json file):
def someControllerMethod() {
def dlContent = someService.marshalJson()
def contentType = "application/octet-stream"
def filename = "someFilename.json"
response.setHeader("Content-Disposition", "attachment;filename=${filename}")
render(contentType: contentType, text: dlContent as JSON)
}
okay. So I finally got this to work. As proposed by #railsdog and many others (This problem has been discussed on other threads in stackoverflow but the specific case I had was slightly different from those) I ended up writing to response directly from server and took out the AJAX call. The only reason I was doing an AJAX call was because I did not want to submit the current page that had the "generate file" functionality (There are many data elements on the page and I did not want to re-do the entire page just for downloading the file). So I ended up using an anchor tag with target as "_blank". Here's the code snippet
<a href="myControllerMethodToGenerateFileAndWriteToHTTPResponseDirectlyAsSuggestedByOthersInThisPost"
target="_blank"/>
This actually opened a new page and did the submission to initiate the download. Problem solved. It's working fine in CHROME. :) Thanks guys!
I like the solution using the render method from #railsdog !
A slightly other approach which I used so far was:
def controllerMethod() {
...
File file = sepaXmlService.createTransfersFile(...)
response.setContentType("application/xml")
response.setHeader("Content-disposition", "attachment;filename=${file.getName()}")
OutputStream out = response.getOutputStream()
out.write(file.bytes)
out.close()
file.delete()
return
...
}
In the view I use the following statement in the form:
<g:actionSubmit action="controllerMethod" class="btn" value="Get XML!" /></td>
I think it should also be possible to use a
<g:link controller="foobar" action="controllerMethod" class="btn">GetXML</g:link>
We use ASP.Net Mvc 4.0.
My objecctive is to save a form with both normal input fields as well as file input fields.
I should be able to add extra data while posting.
I should be able to do perform few actions on 'Ajax Post's Success.
We used ajax post to post the form data as we could accomplish above 2, but failed in serializing and posting of files to server.
Whenever we post using ajax post, always Request.Files.Count == 0, when i check in my controller's Post Action.
ajax post i have used is:
function PostData(formId, eventSource, eventName, eventArgs, controlId) {
var $dialogForm = $("#" + formId + "Form");
fdata = $dialogForm.serialize();
fdata = fdata + '&eventSource=' + eventSource + "&eventName=" + eventName + '&eventArgs=' + eventArgs;
$.ajax({
url: $dialogForm.attr("action"),
type: $dialogForm.attr("method"),
cache: false,
data: fdata,
success: function (result) {
ProcessEvent(result);
}
});
}
Please provide me a solution for this!
well you cannot upload files when you go with the concept of ajax. But there are tweaks which are used to upload file and form data using ajax. Whenever a file type is encountered in a form the form data along with file can be copied to an iframe and the iframe can be submitted which give you a feel that file has been uploaded along with other form data using ajax.
There are various plugin available in jquery which ease this task for you.
One of my favourite is ajax form
http://malsup.com/jquery/form/#file-upload
You can use this one..
Following the tutorial found on ASP.NET, implemented a Web API controller method for doing asynchronous file uploads that looks like this:
public Task<HttpResponseMessage> PostFormData()
{
// Check if the request contains multipart/form-data.
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
// Read the form data and return an async task.
var task = Request.Content.ReadAsMultipartAsync(provider).
ContinueWith<HttpResponseMessage>(t =>
{
if (t.IsFaulted || t.IsCanceled)
{
Request.CreateErrorResponse(HttpStatusCode.InternalServerError, t.Exception);
}
return Request.CreateResponse(HttpStatusCode.OK);
});
return task;
}
Uploading a file via a standard multipart HTML form works perfectly. However, when another developer attempts to upload a file via multipart form constructed by Flex's FileReference class, an error is thrown:
Unexpected end of MIME multipart stream. MIME multipart message is not complete.
I have no idea if the problem lies in Web API or Flex. I've found some sort of related fixes that had no affect (Multipart form POST using ASP.Net Web API), and more recently this one ("MIME multipart stream. MIME multipart message is not complete" error on webapi upload). If the second link holds true, does anyone know if it's out in the current release of Web API available via Nuget? The discussion was in May, the most recent release from Nuget was August, so I assume this fix was deployed already, and is not the root cause of my issue.
I had the same problem with MVC4, but Will is correct, add a name to your input.....
<input type="file" id="fileInput" name="fileInput"/>
and all the magic is back up and working!
I had the same problem with flex. And below is the code that solved it. Basically I used a custom stream to append the newline that asp.net web api is expecting.
Stream reqStream = Request.Content.ReadAsStreamAsync().Result;
MemoryStream tempStream = new MemoryStream();
reqStream.CopyTo(tempStream);
tempStream.Seek(0, SeekOrigin.End);
StreamWriter writer = new StreamWriter(tempStream);
writer.WriteLine();
writer.Flush();
tempStream.Position = 0;
StreamContent streamContent = new StreamContent(tempStream);
foreach(var header in Request.Content.Headers)
{
streamContent.Headers.Add(header.Key, header.Value);
}
// Read the form data and return an async task.
await streamContent.ReadAsMultipartAsync(provider);
Hope this helps.
Reading through your existing research and following through to the codeplex issue reported it looks like someone else confirmed this issue to still exist in September.
They believe that MVC 4 fails to parse uploads without a terminating "\r\n".
The issue is really simple but extremely hard to fix. The problem is that Uploadify does > not add an "\r\n" at the end of the MultiPartForm message
http://aspnetwebstack.codeplex.com/discussions/354215
It may be worth checking that the Flex upload adds the "\r\n"
For those landing here googling:
Unexpected end of MIME multipart stream. MIME multipart message is not complete.
Reading the request stream more than once will also cause this exception. I struggled with it for hours until I found a source explaining that the request stream only could be read once.
In my case, I combined trying to read the request stream using a MultipartMemoryStreamProvider and at the same time letting ASP.NET do some magic for me by specifying parameters (coming from the request body) for my api method.
Make sure the virtual directory ("~/App_Data" directory as below example) where the image files are first uploaded are physically existance. When you publish the project, it may not be in the output files.
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
I just removed my headers I was setting on my post method which ended up solving this issue.
The problem is this line:
string root = HttpContext.Current.Server.MapPath("~/App_Data");
It will only work in localhost, you can use HostingEnvironment.MapPath instead in any context where System.Web objects like HttpContext.Current are not available (e.g also from a static method).
var mappedPath = System.Web.Hosting.HostingEnvironment.MapPath("~/SomePath");
See also What is the difference between Server.MapPath and HostingEnvironment.MapPath?
Reference to this answer How to do a Server Map Path.
I am developing a MVC3 (razorview) application that makes use of the Telerik Controls.
So what I am doing is the following:
I want to upload a file, do some validation. If the file is valid I want to give the user the option to upload the file permanently. If it is not valid, the errors are displayed and the user can't upload the file.
This is what I have so far:
Javascript code:
<script type="text/javascript">
function onSuccess(e) {
if (jQuery.isEmptyObject(e.response.errors) && jQuery.isEmptyObject(e.response.warnings)) {
$("#successmsg").show();
} else {
var errorMsg = "";
if (!jQuery.isEmptyObject(e.response.errors)) {
for (var i = 0; i < e.response.errors.length; i++) {
errorMsg = errorMsg + "<li>" + e.response.errors[i] + "</li>";
}
}
if (errorMsg != "") {
var strErrorContent = $("#errormsg_template").html();
$("#errormsg").html(strErrorContent.replace("{0}", errorMsg));
$("#errormsg").show();
}
}
}
Telerik component in view:
<p>
#Html.Telerik().Upload().Name("file_upload").Multiple(false).Async(settings =>
{
settings.AutoUpload(false);
settings.Save("Test", "Pricat");
}).ClientEvents(events =>
{
events.OnError("onError");
events.OnSuccess("onSuccess");
events.OnUpload("onUpload");
})
</p>
So this code is working for the validation part. The user selects the file, the file is uploaded and some internal processing is validating the message.
If the message is valid, a successmessage is shown. If it not valid, the errors are displayed.
How should I proceed to add a button that does the upload (which is basically just another method call), without browsing to the file again?
So the following
settings.Save("Test", "Pricat");
should become
settings.Save("Upload", "Pricat");
So in the controller I have these 2 methods:
public ActionResult Upload(HttpPostedFileBase file_upload) { }
public ActionResult Test(HttpPostedFileBase file_upload) { }
What I had in mind:
In javascript, if the e.reponse doesn't contain errors, display a button (ASP.NET button, another upload telerik button, I don't know..).
When that button is clicked, it should call the "Upload" method in my controller. I just don't know how to send the file to that method
Does anyone know how to do this?
Or any other suggestions are more than welcome!
I hope it is a bit clear.
Thanks in advance.
Best regards.
It would be a very bad practice to upload the file twice in the first place. Not only would that use unnecessary bandwidth, you wouldn't really be able to guarantee the contents of the file stayed the same between requests.
Your 'Test' method, if successful, should save the file on the server somewhere in a 'validated but not permanently saved' state, and return a reference to the saved file to the client.
You would then pass that reference back into the 'Upload' action should the user decide to permanently save the file. (thus making it not actually involve a file upload at all)
You'll probably want to purge validated files which were never permanently saved every so often, but that's a small price to pay to do the process correctly.
If you insist on your solution you can upload a file with ASP.MVC using submit button like mentioned here: File Upload ASP.NET MVC 3.0
If you want it to be an AJAX post, you can use jQuery code like this (on onclick button event):
$.ajax('<%= Url.Action("Upload", "MyFileController") %>', {
async: async,
data: $('#myFileForm').serialize(),
success: function() { //something on success },
error: function() { //something on error },
type: 'POST',
timeout: 10000
}
But similary as #mootinator stated. I would better save the file on server (let's say in some temporary table or folder - or just flag it temporary in db with boolean value), validate it, ask your user if he want to save it permanently -> move the file to permanent folder or flag it permanent ... and in cases that it is not valid file or user refuses to save it permanently I would simply delete that file.
I have a controller that needs to redirect after receiving a file. I have saved the file successfully on the server side. Now, the only things that is bogging me down is how do I redirect to another site while sending the uploaded file that was saved on the server? Any tips? I am desparate.
OK so here it is, first I save the file on serverB:
file.SaveAs(Server.MapPath("~/ImageCache/") + file.FileName);
WebClient client = new WebClient();
Then I do the post:
byte[] data;
client.Headers.Set(HttpRequestHeader.ContentType, "image/jpeg");
data = client.UploadFile("http://hostA.com/Search/", "POST", Server.MapPath("~/ImageCache/") + file.FileName);
return Redirect( WHAT DO I WRITE HERE??);
Need to get to the place where I find the other service showing me the page when it has received the file.
How are you uploading the file? If this is the usual case of an <input type="file" />, you can just return Redirect("new url"); within your action.
Edit:
If you want to relay this to another web service, you don't need to redirect. There should be some sort of upload method defined in the webservice (including what type of webservice would help). You should be able to call that like you would any other webservice method, probably specifying the FileContents byte[] as a parameter.