I'm creating an image upload API that takes files with POST requests. Here's the code:
def upload = Action(parse.temporaryFile) { request =>
val file = request.body.file
Ok(file.getName + " is uploaded!")
}
The file.getName returns something like: requestBody4386210151720036351asTemporaryFile
The question is how I could get the original filename instead of this temporary name? I checked the headers. There is nothing in it. I guess I could ask the client to pass the filename in the header. But should the original filename be included somewhere in the request?
All the parse.temporaryFile body parser does is store the raw bytes from the body as a local temporary file on the server. This has no semantics in terms of "file upload" as its normally understood. For that, you need to either ensure that all the other info is sent as query params, or (more typically) handle a multipart/form-data request, which is the standard way browsers send files (along with other form data).
For this, you can use the parse.multipartFormData body parser like so, assuming the form was submitted with a file field with name "image":
def upload = Action(parse.multipartFormData) { request =>
request.body.file("image").map { file =>
Ok(s"File uploaded: ${file.filename}")
}.getOrElse {
BadRequest("File is missing")
}
}
Relevant documentation.
It is not sent by default. You will need to send it specifically from the browser. For example, for an input tag, the files property will contain an array of the selected files, files[0].name containing the name of the first (or only) file. (I see there are possibly other properties besides name but they may differ per browser and I haven't played with them.) Use a change event to store the filename somewhere so that your controller can retrieve it. For example I have some jquery coffeescript like
$("#imageFile").change ->
fileName=$("#imageFile").val()
$("#imageName").val(fileName)
The value property also contains a version of the file name, but including the path (which is supposed to be something like "C:\fakepath" for security reasons, unless the site is a "trusted" site afaik.)
(More info and examples abound, W3 Schools, SO: Get Filename with JQuery, SO: Resolve path name and SO: Pass filename for example.)
As an example, this will print the original filename to the console and return it in the view.
def upload = Action(parse.multipartFormData(handleFilePartAsFile)) { implicit request =>
val fileOption = request.body.file("filename").map {
case FilePart(key, filename, contentType, file) =>
print(filename)
filename
}
Ok(s"filename = ${fileOption}")
}
/**
* Type of multipart file handler to be used by body parser
*/
type FilePartHandler[A] = FileInfo => Accumulator[ByteString, FilePart[A]]
/**
* A FilePartHandler which returns a File, rather than Play's TemporaryFile class.
*/
private def handleFilePartAsFile: FilePartHandler[File] = {
case FileInfo(partName, filename, contentType) =>
val attr = PosixFilePermissions.asFileAttribute(util.EnumSet.of(OWNER_READ, OWNER_WRITE))
val path: Path = Files.createTempFile("multipartBody", "tempFile", attr)
val file = path.toFile
val fileSink: Sink[ByteString, Future[IOResult]] = FileIO.toPath(file.toPath())
val accumulator: Accumulator[ByteString, IOResult] = Accumulator(fileSink)
accumulator.map {
case IOResult(count, status) =>
FilePart(partName, filename, contentType, file)
} (play.api.libs.concurrent.Execution.defaultContext)
}
Related
In Elm, how can I access a file on my local drive?
For example, I need to access the file:
c:\MyFolder\somefile.txt
(I'm assuming you're targeting the browser and not Node. If you want Node support, here is the documentation for it's fs module. The high-level usage will be similar to what I'm describing below for browsers.)
There is not (yet) an Elm-only API for this, so you'll have to use ports. This article is very helpful, I will adapt its example.
In short, you have to use File and FileReader API (caniuse.com), and on load of the file send the data to Elm through port. (In my example below, Elm will get a GetFile {name : String, content : String} message for every file submitted.) Here is a working example in Ellie.
Msg:
type Msg
= GetFile File
type alias File =
{ name : String
, content : String
}
Port:
port getFile : (File -> msg) -> Sub msg
(don't forget port module instead of module on top of the Elm source)
Subscription:
subscriptions : Model -> Sub Msg
subscriptions model =
getFile GetFile
HTML file input:
<input type="file" id="files" name="files[]" multiple />
JS (the main part!):
<script>
var app = Elm.Main.fullscreen();
function handleFileSelect(evt) {
var files = evt.target.files;
for (var i = 0, f; f = files[i]; i++) {
if (!f.type.match('image.*')) {
continue;
}
var reader = new FileReader();
reader.onload = (function(theFile) {
return function(e) {
app.ports.getFile.send({name: theFile.name, content: e.target.result});
};
})(f);
reader.readAsDataURL(f);
}
}
document.getElementById('files').addEventListener('change', handleFileSelect, false);
</script>
EDIT: this example only accepts images. If you don't want that, remove the
if (!f.type.match('image.*')) {
continue;
}
part and do something different in the viewFile function (ie. don't try to interpret the content data as an image src).
Elm is now able open files as of 0.19.
Steps are as follows:
Attach an event handler to a button that sends the appropriate message to the update function.
Update function receives message and runs the file-opening function, which tells Elm runtime to ask browsers to open a file selection dialogue.
Once user action has completed, Elm runtime returns a data of type File to the update function, and the update function can decide what to do.
To read file's content, a file-reading function has to be invoked. Again, the function tells the Elm runtime to read the content of the file. The runtime again invokes your update function, this time passing the content of the file.
Please refer to this thread on Elm discourse, which includes this example on Ellie app
I know that there are tons of examples of multi-part form data uploading in ASP.net. However, all of them just upload files to the server, and use System.IO to write it to server disk space. Also, the client side implementations seem to handle files only in uploading, so I can't really use existing upload plugins.
What if I have an existing record and I want to upload images and associate them with the record? Would I need to write database access code in the upload (Api) function, and if so, how do I pass that record's PK with the upload request? Do I instead upload the files in that one request, obtain the file names generated by the server, and then make separate API calls to associate the files with the record?
While at it, does anyone know how YouTube uploading works? From a user's perspective, it seems like we can upload a video, and while uploading, we can set title, description, tags, etc, and even save the record. Is a record for the video immediately created before the API request to upload, which is why we can save info even before upload completes?
Again, I'm not asking HOW to upload files. I'm asking how to associate uploaded files with an existing record and the API calls involved in it. Also, I am asking for what API calls to make WHEN in the user experience when they also input information about what they're uploading.
I'm assuming you're using an api call to get the initial data for displaying a list of files or an individual file. You would have to do this in order to pass the id back to the PUT method to update the file.
Here's a sample of the GET method:
[HttpGet]
public IEnumerable<FileMetaData> Get()
{
var allFiles = MyEntities.Files.Select(f => new FileMetaData()
{
Name = f.Name,
FileName = f.FileName,
Description = f.Description,
FileId = f.Id,
ContentType = f.ContentType,
Tags = f.Tags,
NumberOfKB = f.NumberOfKB
});
return allFiles;
}
Here's a sample of the POST method, which you can adapt to be a PUT (update) instead:
[HttpPost]
[ValidateMimeMultipartContentFilter]
public async Task<IHttpActionResult> PutFile()
{
try
{
var streamProvider =
await Request.Content.ReadAsMultipartAsync(new InMemoryMultipartFormDataStreamProvider());
//We only allow one file
var thisFile = files[0];
//For a PUT version, you would grab the file from the database based on the id included in the form data, instead of creating a new file
var file = new File()
{
FileName = thisFile.FileName,
ContentType = thisFile.ContentType,
NumberOfKB = thisFile.ContentLength
};
//This is the file metadata that your client would pass in as formData on the PUT / POST.
var formData = streamProvider.FormData;
if (formData != null && formData.Count > 0)
{
file.Id = formData["id"];
file.Description = formData["description"];
file.Name = formData["name"] ?? string.Empty;
file.Tags = formData["tags"];
}
file.Resource = thisFile.Data;
//For your PUT, change this to an update.
MyEntities.Entry(file).State = EntityState.Detached;
MyEntities.Files.Add(file);
await MyEntities.SaveChangesAsync();
//return the ID
return Ok(file.Id.ToString());
}
I got the InMemoryMultipartFormDataStreamProvider from this article:
https://conficient.wordpress.com/2013/07/22/async-file-uploads-with-mvc-webapi-and-bootstrap/
And adapted it to fit my needs for the form data I was returning.
I am attempting to generate a PDF file that contains object information and then attach it to another object that is stored in the database. The attachmentable plugin I am using is working now for user end attachments, but I need my system to be able to do it automatically.
I am using:
Grails 1.3.9
Attachmentable 0.3.0 http://grails.org/plugin/attachmentable
Rendering 0.4.3 http://grails.org/plugin/rendering
I have been able to generate and display the pdf, but do not know how to attach it using the attachmentable plugin. I need some way to take the generated pdf byte array and convert it to a MultipartFile for the attachmentable plugin function I call. The error I get shows that my argument types are invalid.
I save object1 and object2, then generate the pdf of object1 and try to attach it to object2.
Thanks in advance for you help!
Thing1 Controller Snippets:
ByteArrayOutputStream bytes = pdfRenderingService.render(template: "/thing1/pdf", model: [thing1: thing1])
attachmentableService.addAttachment("unknown", thing2.id, bytes)
AttachmentableService function I am attempting to call:
def addAttachment(def poster, def reference, CommonsMultipartFile file) {
addAttachment(CH.config, poster, reference, file)
}
def addAttachment(def config,
def poster,
def reference,
CommonsMultipartFile file) {
if (reference.ident() == null) {
throw new AttachmentableException(
"You must save the entity [${delegate}] before calling addAttachment.")
}
if (!file?.size) {
throw new EmptyFileException(file.name, file.originalFilename)
}
String delegateClassName = AttachmentableUtil.fixClassName(reference.class)
String posterClass = (poster instanceof String) ? poster : AttachmentableUtil.fixClassName(poster.class.name)
Long posterId = (poster instanceof String) ? 0L : poster.id
String filename = file.originalFilename
// link
def link = AttachmentLink.findByReferenceClassAndReferenceId(
delegateClassName, reference.ident())
if (!link) {
link = new AttachmentLink(
referenceClass: delegateClassName,
referenceId: reference.ident())
}
// attachment
Attachment attachment = new Attachment(
// file
name: FilenameUtils.getBaseName(filename),
ext: FilenameUtils.getExtension(filename),
length: 0L,
contentType: file.contentType,
// poster
posterClass: posterClass,
posterId: posterId,
// input
inputName: file.name)
link.addToAttachments attachment
if (!link.save(flush: true)) {
throw new AttachmentableException(
"Cannot create Attachment for arguments [$user, $file], they are invalid.")
}
// save file to disk
File diskFile = AttachmentableUtil.getFile(config, attachment, true)
file.transferTo(diskFile)
attachment.length = diskFile.length()
// interceptors
if(reference.respondsTo('onAddAttachment')) {
reference.onAddAttachment(attachment)
}
attachment.save(flush:true) // Force update so searchable can try to index it again.
return reference
}
Grails runtime error:
groovy.lang.MissingMethodException: No signature of method: com.macrobit.grails.plugins.attachmentable.services.AttachmentableService.addAttachment() is applicable for argument types: (java.lang.String, java.lang.Long, java.io.ByteArrayOutputStream) values: [unknown, 80536, %PDF-1.4 and a long string of unreadable data...]
Possible solutions: addAttachment(java.lang.Object, java.lang.Object, org.springframework.web.multipart.commons.CommonsMultipartFile), addAttachment(java.lang.Object, java.lang.Object, java.lang.Object, org.springframework.web.multipart.commons.CommonsMultipartFile)
Service Method I Added:
def customAddMethod(def poster, def reference, def pdfBytes) {
customAddMethod(CH.config, poster, reference, pdfBytes)
}
def customAddMethod(def config,
def poster,
def reference,
def pdfBytes) {
if (reference.ident() == null) {
throw new AttachmentableException(
"You must save the entity [${delegate}] before calling customAddMethod.")
}
String delegateClassName = AttachmentableUtil.fixClassName(reference.class)
String posterClass = (poster instanceof String) ? poster : AttachmentableUtil.fixClassName(poster.class.name)
Long posterId = (poster instanceof String) ? 0L : poster.id
String filename = "File Name"
// link
def link = AttachmentLink.findByReferenceClassAndReferenceId(
delegateClassName, reference.ident())
if (!link) {
link = new AttachmentLink(
referenceClass: delegateClassName,
referenceId: reference.ident())
}
// attachment
Attachment attachment = new Attachment(
// file
name: "File Name",
ext: "pdf",
length: 0L,
contentType: "application/pdf",
// poster
posterClass: posterClass,
posterId: posterId,
// input
inputName: "File Name")
link.addToAttachments attachment
if (!link.save(flush: true)) {
throw new AttachmentableException(
"Cannot create Attachment for arguments [$user, $file], they are invalid.")
}
// save file to disk
byte[] bytes = pdfBytes.toByteArray(); //convert ByteArrayOutputStream to ByteArray
File diskFile = AttachmentableUtil.getFile(config, attachment, true) //file path
FileOutputStream fos = new FileOutputStream(diskFile); //open file output stream to write to
fos.write(bytes); //write rendered pdf bytes to file
fos.flush();
fos.close();
attachment.length = diskFile.length()
// interceptors
if(reference.respondsTo('onAddAttachment')) {
reference.onAddAttachment(attachment)
}
attachment.save(flush:true) // Force update so searchable can try to index it again.
return reference
}
It looks like the AttachmentableService you referenced (from the Attachmentable plugin) assumes it's dealing with a file-upload scenario, such that you could easily grab the MultipartFile instance via request.getFile(). That's not the case for you - you're creating the file via the Rendering plugin, and you want that file attached to a domain object, right?
You could try constructing a CommonsMultipartFile instance manually by first writing the pdf bytes to disk, and then create a DiskFileItem via DiskFileItemFactory.
See this post for an example of what I'm thinking:
How to make CommonsMultipartFile from absolute file path?
Another, better, option might be to checkout that plugin's source and add a method that doesn't require you to go through those gyrations - perhaps a version of the addAttachment method that accepts a File or an OutputStream instead - and submit a PR to the plugin author.
(Looks like they're adding an 'addAttachment' method to qualifying domain objects, which also expects a CommonsMultipartFile).
Otherwise, you might just have to create your own service to basically provide the same end result, which apparently is to create an AttachmentLink and associated Attachment instance.
The plugin play-reactivemongo offers an easy way to upload a file:
def upload = Action(gridFSBodyParser(gridFS)) { request =>
val futureFile: Future[ReadFile[BSONValue]] = request.body.files.head.ref
futureFile.map { file =>
// do something
Ok
}.recover { case e: Throwable => InternalServerError(e.getMessage) }
}
Unfortunately this solution doesn't suit me because:
I would like only my DAO layer to depend on reactive-mongo.
I need to save the file only if a user is authenticated (with SecureSocial) and use some user's properties as checks and metadata.
If no user is authenticated the request body shouldn't be parsed at all (see also this question).
It would be something along the lines
def upload = SecuredAction { request =>
val user = request.user
val enumerator = an enumrator from the body parsing ???
myDAO.saveFile(user, enumerator)
object myDAO {
def saveFile(user:User, enumerator:Enumerator[Array[Byte]]) = {
...
val fileToSave = DefaultFileToSave(...)
gridfs.save(enumerator, fileToSave)
...
}
}
Unfortunately it seems there is no way to get an enumerator from the parsing of the request body. The only way seems to provide the Action with a parser and an Iteratee that will be fed with the the body being parsed.
I couldn't figure out how to achieve it in a reactive way (without using a temporary file or storing the body in memory). Is it at all possible?
Actually, you might consider not using girdFS built-in parser at all:
val gfs = new GridFS(db)
// the controller method, Authenticated here is custom object extending ActionBuilder
def upload = Authenticated.async(parse.multipartFormData) { request =>
...
request.body.file("photo") match {
// handle error cases
...
case Some(photo) =>
val fileToSave = DefaultFileToSave(photo.filename, photo.contentType)
// here some more operations, basically you don't need the and need only photo.ref.file
val enumerator = Enumerator(Image(photo.ref.file).fitToWidth(120).write)
gfs.save(enumerator, fileToSave) map {
//handle responses and stuff
...
}
}
}
}
How do I open an existing file on the server when a user clicks an actionlink? The following code works for downloading a file but I want to open a new browser window, or tab, and display the file contents.
public ActionResult Download()
{
return File(#"~\Files\output.txt", "application/text", "blahblahblah.txt");
}
You must add "inline" for a new tab.
byte[] fileBytes = System.IO.File.ReadAllBytes(contentDetailInfo.ContentFilePath);
Response.AppendHeader("Content-Disposition", "inline; filename=" + contentDetailInfo.ContentFileName);
return File(fileBytes, contentDetailInfo.ContentFileMimeType);
The way you're using the File() method is to specify a file name in the third argument, which results in a content-disposition header being sent to the client. This header is what tells a web browser that the response is a file to be saved (and suggests a name to save it). A browser can override this behavior, but that's not controllable from the server.
One thing you can try is to not specify a file name:
return File(#"~\Files\output.txt", "application/text");
The response is still a file, and ultimately it's still up to the browser what to do with it. (Again, not controllable from the server.) Technically there's no such thing as a "file" in HTTP, it's just headers and content in the response. By omitting a suggested file name, the framework in this case may omit the content-disposition header, which is your desired outcome. It's worth testing the result in your browser to see if the header is actually omitted.
Use a target of blank on your link to open it in a new window or tab:
Download File
However, forcing the browser to display the contents is out of your control, as it entirely depends on how the user has configured their browser to deal with files that are application/text.
If you are dealing with text, you can create a view and populate the text on that view, which is then returned to the user as a regular HTML page.
please try this and replace your controller name and action name in html action link
public ActionResult ShowFileInNewTab()
{
using (var client = new WebClient()) //this is to open new webclient with specifice file
{
var buffer = client.DownloadData("~\Files\output.txt");
return File(buffer, "application/text");
}
}
OR
public ActionResult ShowFileInNewTab()
{
var buffer = "~\Files\output.txt"; //bytes form this
return File(buffer, "application/text");
}
this is action link which show in new blank tab
<%=Html.ActionLink("Open File in New Tab", "ShowFileInNewTab","ControllerName", new { target = "_blank" })%>
I canĀ“t vote your answered as is useful, follow dow. Thanks very much !
public FileResult Downloads(string file)
{
string diretorio = Server.MapPath("~/Docs");
var ext = ".pdf";
file = file + extensao;
var arquivo = Path.Combine(diretorio, file);
var contentType = "application/pdf";
using (var client = new WebClient())
{
var buffer = client.DownloadData(arquivo);
return File(buffer, contentType);
}
}