Liferay.Upload Component Usage for Multi-file Upload - file-upload

Developing a custom portlet to upload multiple files in Liferay 6.2.
Found the Liferay.Upload component while going through Liferay Source Code in the Document Library Portlet at the following location:
https://github.com/liferay/liferay-portal/blob/master/portal-web/docroot/html/portlet/document_library/upload_multiple_file_entries.jsp
We want to reuse this component, but unable to find any documentation regarding the usage.
How do we use the Liferay.Upload component? What do each of the inputs mean & do?
Can we re-use this Liferay.Upload in our custom portlet?
Any concrete documentation on the usage of Liferay.Upload?
Any already implemented portlets available on the web with the Source Code available on the web?
Given below is the extract of the usage of the Liferay Upload component:
<aui:script use="liferay-upload">
new Liferay.Upload(
{
boundingBox: '#<portlet:namespace />fileUpload',
deleteFile: '<liferay-portlet:actionURL doAsUserId="<%= user.getUserId() %>"><portlet:param name="struts_action" value="/document_library/edit_file_entry" /><portlet:param name="<%= Constants.CMD %>" value="<%= Constants.DELETE_TEMP %>" /><portlet:param name="folderId" value="<%= String.valueOf(folderId) %>" /></liferay-portlet:actionURL>&ticketKey=<%= ticket.getKey() %><liferay-ui:input-permissions-params modelName="<%= DLFileEntryConstants.getClassName() %>" />',
fileDescription: '<%= StringUtil.merge(PrefsPropsUtil.getStringArray(PropsKeys.DL_FILE_EXTENSIONS, StringPool.COMMA)) %>',
maxFileSize: '<%= PrefsPropsUtil.getLong(PropsKeys.DL_FILE_MAX_SIZE) %> B',
metadataContainer: '#<portlet:namespace />commonFileMetadataContainer',
metadataExplanationContainer: '#<portlet:namespace />metadataExplanationContainer',
namespace: '<portlet:namespace />',
tempFileURL: {
method: Liferay.Service.bind('/dlapp/get-temp-file-entry-names'),
params: {
groupId: <%= scopeGroupId %>,
folderId: <%= folderId %>,
tempFolderName: 'com.liferay.portlet.documentlibrary.action.EditFileEntryAction'
}
},
tempRandomSuffix: '<%= EditFileEntryAction.TEMP_RANDOM_SUFFIX %>',
uploadFile: '<liferay-portlet:actionURL doAsUserId="<%= user.getUserId() %>"><portlet:param name="struts_action" value="/document_library/edit_file_entry" /><portlet:param name="<%= Constants.CMD %>" value="<%= Constants.ADD_TEMP %>" /><portlet:param name="folderId" value="<%= String.valueOf(folderId) %>" /></liferay-portlet:actionURL>&ticketKey=<%= ticket.getKey() %><liferay-ui:input-permissions-params modelName="<%= DLFileEntryConstants.getClassName() %>" />'
}
);
</aui:script>
Any pointers are highly appreciated!!

I gave it a shot based on Pawel's links and succeeded in a sort-of POC.
view.jsp
<%#page import="com.liferay.portal.kernel.util.ParamUtil"%>
<%#page import="com.liferay.portal.kernel.portlet.LiferayWindowState"%>
<%#page import="com.liferay.portal.kernel.util.StringPool"%>
<%#page import="com.liferay.portal.kernel.util.PropsKeys"%>
<%#page import="com.liferay.portal.kernel.util.PrefsPropsUtil"%>
<%#page import="com.liferay.portal.kernel.util.StringUtil"%>
<%# taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %>
<%# taglib uri="http://liferay.com/tld/aui" prefix="aui"%>
<portlet:defineObjects />
This is the <b>Liferay File Upload</b> portlet in View mode.
<portlet:actionURL name="uploadFile" var="uploadFileURL" >
<portlet:param name="jspPage" value="/html/singefileuploadaction/view.jsp" />
</portlet:actionURL>
<portlet:resourceURL var="importPortletURL" id="uploadSubmit">
<portlet:param name="jspPage" value="/html/singefileuploadaction/view.jsp" />
</portlet:resourceURL>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<aui:form action="<%= importPortletURL %>" cssClass="lfr-export-dialog" method="post" name="fm1">
<div class="lfr-dynamic-uploader">
<div class="lfr-upload-container" id="<portlet:namespace />fileUpload">
</div>
</div>
<div id="<portlet:namespace/>fallback"></div>
<aui:button-row>
<aui:button cssClass='hide' name="continueButton" type="submit" value="Continue" />
</aui:button-row>
<aui:script use="liferay-upload,aui-base">
var liferayUpload = new Liferay.Upload({
allowedFileTypes: '<%= StringUtil.merge(PrefsPropsUtil.getStringArray(PropsKeys.DL_FILE_EXTENSIONS, StringPool.COMMA)) %>',
container: '#<portlet:namespace />fileUpload',
maxFileSize: <%=Long.parseLong(PrefsPropsUtil.getString(PropsKeys.DL_FILE_MAX_SIZE)) %> / 1024,
namespace:'<portlet:namespace />',
uploadFile: '<%=uploadFileURL.toString()%>',
tempFileRemoved: function(){console.log('Temp File Removed');},
'strings.dropFilesText': 'Drop Files Here to Upload.',
'strings.dropFileText': 'Drop File Here to Upload.',
'strings.selectFileText': 'Select File to Upload.',
'strings.selectFilesText': 'Select Files to Upload.',
'strings.fileCannotBeSavedText': 'File cannot be saved.',
'strings.pendingFileText': 'This file was previously uploaded but not actually saved',
'strings.uploadsCompleteText': 'Upload is complete. Please save.',
multipleFiles: false
});
<!-- ASHOK: !IMPORTANT-DO NOT REMOVE-This code is to re-position the Upload Component HTML code which is placed on top of the page by default: Might be a BUG?? -->
$( document ).ready(function() {
$('.component.liferayupload').appendTo("#<portlet:namespace />fileUpload");
});
var continueButton = A.one('#<portlet:namespace />continueButton');
function toggleContinueButton() {
var uploadedFiles = liferayUpload._fileListContent.all('.upload-file.upload-complete');
if (uploadedFiles.size() == 1) {
console.log('One file Upload');
console.log(uploadedFiles);
continueButton.show();
}
else {
console.log(uploadedFiles);
continueButton.hide();
}
}
<!-- Ashok: Upload Component Events BEGIN-->
<!-- Ashok: Fired when File Upload STARTS-->
liferayUpload._uploader.on(
'fileuploadstart',
function(event) {
console.log('File Upload Start');
}
);
<!-- Ashok: Fired when File Upload is COMPLETE-->
Liferay.on(
'uploadcomplete',
function(event) {
console.log('File Upload Complete');
}
);
<!-- Ashok: Fired when All Uploads are COMPLETE-->
liferayUpload._uploader.on(
'alluploadscomplete',
function(event) {
console.log('All Uploads Complete');
toggleContinueButton();
}
);
<!-- Ashok: Fired when Temp file is REMOVED-->
Liferay.on(
'tempFileRemoved',
function(event) {
console.log('Temp File Removed');
toggleContinueButton();
}
);
<!-- Ashok: Upload Component Events END-->
$('#<portlet:namespace />continueButton').on(
'click',
function(event) {
event.preventDefault();
$('#<portlet:namespace />fm1').ajaxSubmit(
{
success: function(responseData) {
<%-- $('#<portlet:namespace />exportImportOptions').html(responseData); --%>
}
}
);
}
);
</aui:script>
</aui:form>
SingeFileUploadAction.java
package com.ashok.liferay.upload;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.PortletException;
import javax.portlet.ResourceRequest;
import javax.portlet.ResourceResponse;
import com.liferay.portal.kernel.upload.UploadPortletRequest;
import com.liferay.portal.util.PortalUtil;
import com.liferay.util.bridges.mvc.MVCPortlet;
/**
* Portlet implementation class SingeFileUploadAction
*/
public class SingeFileUploadAction extends MVCPortlet {
public void uploadFile(ActionRequest actionRequest,
ActionResponse actionResponse) throws IOException, PortletException {
System.out.println("In SingeFileUploadAction");
UploadPortletRequest uploadRequest=PortalUtil.getUploadPortletRequest(actionRequest);
File file =uploadRequest.getFile("file");
String fileName = uploadRequest.getFileName("file");
System.out.println("FileName:"+fileName);
//Mike Test
Map<String, String[]> reqMap = actionRequest.getParameterMap();
System.out.println("Printing all actionRequest Params");
for (Map.Entry<String, String[]> entry : reqMap.entrySet())
{
System.out.println(entry.getKey() + "/" + Arrays.toString(entry.getValue()));
}
System.out.println("----------\nPrinting all uploadRequest Params");
Map<String, String[]> upReqMap =uploadRequest.getParameterMap();
for (Map.Entry<String, String[]> entry : upReqMap.entrySet())
{
System.out.println(entry.getKey() + "/" + Arrays.toString(entry.getValue()));
}
System.out.println(file.getName());
}
public void uploadSubmit(ResourceRequest resourceRequest,
ResourceResponse resourceResponse) {
System.out.println("In uploadSubmit");
UploadPortletRequest uploadRequest=PortalUtil.getUploadPortletRequest(resourceRequest);
// File file =uploadRequest.getFile("file");
// System.out.println(file.getName());
}
}

After trying to customize this component for my needs, I would recommend a different way to implement file uploading in Liferay.
The lack of documentation of the Liferay.Upload Component plus the complexity of the underlaying code of the Liferay File Upload portlet makes it a difficult path to customize the file uploading implemented in Liferay.
My recommendation is the following: create a new custom portlet and use the jQuery library: jQuery File UPload plugin. Visit its website here. It is a well written library with decent documentation, you can adapt it to your needs since it covers a lot of the settings you would expect in a file uploader.
A summary of how the jsp view in your portlet would be:
<%# taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %>
<%# taglib uri="http://liferay.com/tld/aui" prefix="aui"%>
<portlet:defineObjects />
File Bulk Uploader
<portlet:actionURL var="uploadFileURL" name="uploadFiles"/>
<script>
$(document).ready(function(){
$("#multipleupload").uploadFile({
url:"<%= uploadFileURL.toString() %>",
multiple:true,
dragDrop:true,
sequential:true,
sequentialCount:1
});
});
</script>
<div id="multipleupload">Upload</div>
As you can see, using this library makes a really simple implementation of the view, providing a file uploader. Implement afterwards your portlet class action like this:
public void uploadFiles(ActionRequest request, ActionResponse response) throws PortletException, IOException, SystemException, PortalException{
UploadPortletRequest uploadRequest = PortalUtil.getUploadPortletRequest(request);
Enumeration<?> paramEnum = uploadRequest.getParameterNames();
File tempFile;
Map<String, File> fileMap = new LinkedHashMap<String, File>();
while (paramEnum.hasMoreElements()){
String parameter = (String) paramEnum.nextElement();
if (parameter.startsWith("file")){
tempFile = uploadRequest.getFile(parameter);
//******************************************
//do what you need with the file here
//******************************************
}
}
}
When debugging paramEnum, it gets a "file" parameter each time a dragged and dropped file is processed for upload. This is the sequential mechanism and so, the uploadFiles action is triggered for each file:

It looks like this component is reused all over in Liferay. I think you could reuse it as well. I've found following documentation of initial parameters. I hope it'll help you progress. Good luck!

This is my implementation for multiple upload files using liferay style(drag and drop or select file quad). This code will upload the files in a temporary liferay folder (DLFolderConstants.DEFAULT_PARENT_FOLDER_ID), then inside the method METHODJAVANAMEManageForm in ClientPortlet.java you can get them and using as you want. This is the same behaviour of liferay multiple upload files.
view.jsp
<portlet:actionURL windowState="maximized" var="NAMEUrl" name="METHODJAVANAMEManageForm">
</portlet:actionURL>
<form action="<c:out value="${NAMEUrl}"/>" method="POST" enctype="multipart/form-data">
<liferay-util:include page="/jsp/multipleAttach.jsp" servletContext="<%=this.getServletContext()%>"/>
<!--other <input <div <span.....-->
</form>
multipleAttach.jsp:
<%#page import="com.liferay.portal.kernel.util.ParamUtil"%>
<%#page import="com.liferay.portal.kernel.portlet.LiferayWindowState"%>
<%#page import="com.liferay.portal.kernel.util.StringPool"%>
<%#page import="com.liferay.portal.kernel.util.PropsKeys"%>
<%#page import="com.liferay.portal.kernel.util.PrefsPropsUtil"%>
<%#page import="com.liferay.portal.kernel.util.StringUtil"%>
<%#page import="com.liferay.portal.service.TicketLocalServiceUtil"%>
<%# page import="com.liferay.portlet.documentlibrary.model.DLFileEntryConstants" %>
<%# page import="com.liferay.portal.kernel.util.Constants" %>
<%#page import="com.liferay.portal.service.ServiceContext"%>
<%#page import="com.liferay.portal.model.TicketConstants"%>
<%#page import="com.liferay.portal.model.User"%>
<%#page import="com.liferay.portal.model.Ticket"%>
<%#page import="com.liferay.portal.kernel.repository.model.Folder"%>
<%#page import="com.sun.mail.imap.DefaultFolder"%>
<%#page import="com.liferay.util.portlet.PortletProps"%>
<%#page import="com.liferay.portlet.documentlibrary.model.DLFolderConstants"%>
<%# taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %>
<%# taglib uri="http://liferay.com/tld/aui" prefix="aui"%>
<%# taglib uri="http://liferay.com/tld/util" prefix="liferay-util"%>
<%# taglib uri="http://liferay.com/tld/portlet" prefix="liferay-portlet"%>
<%# taglib uri="http://liferay.com/tld/theme" prefix="liferay-theme"%>
<%# taglib uri="http://liferay.com/tld/ui" prefix="liferay-ui"%>
<portlet:defineObjects />
<liferay-theme:defineObjects/>
<%
Ticket ticket = TicketLocalServiceUtil.addTicket(user.getCompanyId(), User.class.getName(), user.getUserId(), TicketConstants.TYPE_IMPERSONATE, null, null, new ServiceContext());
%>
<!--REEDME instead of<form> of view.jsp you can also use <aui:form action="<c:out value="${NAMEUrl}"/>" cssClass="lfr-export-dialog" method="post" name="fm1">-->
<div class="lfr-dynamic-uploader">
<div class="lfr-upload-container" id="<portlet:namespace />fileUpload">
</div>
</div>
<div id="<portlet:namespace/>fallback"></div>
<aui:script use="liferay-upload,aui-base">
var liferayUpload = new Liferay.Upload(
{
boundingBox: '#<portlet:namespace />fileUpload',
deleteFile: '<liferay-portlet:actionURL name="METHODJAVANAMEFordeleteFile" doAsUserId="<%= user.getUserId() %>"><portlet:param name="struts_action" value="/document_library/edit_file_entry" /><portlet:param name="<%= Constants.CMD %>" value="<%= Constants.DELETE_TEMP %>" /><portlet:param name="folderId" value="<%= String.valueOf(DLFolderConstants.DEFAULT_PARENT_FOLDER_ID) %>" /></liferay-portlet:actionURL>&ticketKey=<%= ticket.getKey() %><liferay-ui:input-permissions-params modelName="<%= DLFileEntryConstants.getClassName() %>" />',
fileDescription: '<%= StringUtil.merge(PrefsPropsUtil.getStringArray(PropsKeys.DL_FILE_EXTENSIONS, StringPool.COMMA)) %>',
maxFileSize: '<%= PrefsPropsUtil.getLong(PropsKeys.DL_FILE_MAX_SIZE) %>',
metadataContainer: '#<portlet:namespace />commonFileMetadataContainer',
metadataExplanationContainer: '#<portlet:namespace />metadataExplanationContainer',
namespace: '<portlet:namespace />',
tempFileURL: {
method: Liferay.Service.bind('/dlapp/get-temp-file-entry-names'),
params: {
groupId: <%= scopeGroupId %>,
folderId: <%= DLFolderConstants.DEFAULT_PARENT_FOLDER_ID %>,
tempFolderName: 'com.example.portlet.ClientPortlet' <!-- this is equals of _TEMP_FOLDER_NAME_ATTACHMENT of ClientPortlet.java -->
}
},
uploadFile: '<liferay-portlet:actionURL name="METHODJAVANAMEForuploadFile" doAsUserId="<%= user.getUserId() %>"><portlet:param name="struts_action" value="/document_library/edit_file_entry" /><portlet:param name="<%= Constants.CMD %>" value="<%= Constants.ADD_TEMP %>" /><portlet:param name="folderId" value="<%= String.valueOf(DLFolderConstants.DEFAULT_PARENT_FOLDER_ID) %>" /></liferay-portlet:actionURL>&ticketKey=<%= ticket.getKey() %><liferay-ui:input-permissions-params modelName="<%= DLFileEntryConstants.getClassName() %>" />'
}
);
</aui:script>
ClientPortlet.java
private static final String _TEMP_FOLDER_NAME_ATTACHMENT = ClientPortlet.class.getName();
public void METHODJAVANAMEManageForm(ActionRequest actionRequest,ActionResponse actionResponse) throws IOException, PortletException
{
List<FileEntry> tempFileEntrys = new ArrayList<FileEntry>();
try
{
ThemeDisplay themeDisplay =(ThemeDisplay)actionRequest.getAttribute(WebKeys.THEME_DISPLAY);
UploadPortletRequest uploadRequest = PortalUtil.getUploadPortletRequest(actionRequest);
//get selected checkbox of file to upload
String [] selectedFile = uploadRequest.getParameterValues("selectUploadedFileCheckbox");
if(selectedFile != null)
{
for(int i = 0; i < selectedFile.length; ++i)
{
FileEntry tmpfile = TempFileUtil.getTempFile(themeDisplay.getScopeGroupId(), themeDisplay.getUserId(), selectedFile[i], _TEMP_FOLDER_NAME_ATTACHMENT);
if(tmpfile!=null && tmpfile.getTitle()!=null && (!tmpfile.getTitle().equals("")))
{
tempFileEntrys.add(tmpfile);
System.out.println("file: " + tmpfile.getTitle());
}
}
}
/////upload tempFileEntrys where you needed
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
//delete all tmp files uploaded in liferay tmp folder
for (FileEntry tmp : tempFileEntrys)
{
try
{
TempFileUtil.deleteTempFile(tmp.getFileEntryId());
} catch (Exception e) {
e.printStackTrace();
}
}
}
actionResponse.setRenderParameter("mvcPath","/jsp/viewList.jsp");
}
public void METHODJAVANAMEForuploadFile(ActionRequest actionRequest,ActionResponse actionResponse) throws Exception
{
//upload file in liferay tmp folder
UploadPortletRequest uploadPortletRequest = PortalUtil.getUploadPortletRequest(actionRequest);
ThemeDisplay themeDisplay = (ThemeDisplay)actionRequest.getAttribute(WebKeys.THEME_DISPLAY);
long folderId = ParamUtil.getLong(uploadPortletRequest, "folderId");
String sourceFileName = uploadPortletRequest.getFileName("file");
InputStream inputStream = null;
try
{
inputStream = uploadPortletRequest.getFileAsStream("file");
String contentType = uploadPortletRequest.getContentType("file");
DLAppServiceUtil.addTempFileEntry(themeDisplay.getScopeGroupId(), folderId, sourceFileName,_TEMP_FOLDER_NAME_ATTACHMENT, inputStream, contentType);
JSONObject jsonObject = JSONFactoryUtil.createJSONObject();
jsonObject.put("name", sourceFileName);
jsonObject.put("title", sourceFileName);
writeJSON(actionRequest, actionResponse, jsonObject);
}
catch (Exception e)
{
UploadException uploadException =(UploadException)actionRequest.getAttribute(WebKeys.UPLOAD_EXCEPTION);
if ((uploadException != null) &&uploadException.isExceededSizeLimit())
{
throw new FileSizeException(uploadException.getCause());
}
else
{
throw e;
}
}
finally
{
StreamUtil.cleanUp(inputStream);
}
}
public void METHODJAVANAMEFordeleteFile(ActionRequest actionRequest,ActionResponse actionResponse) throws IOException, PortletException
{
//delete file from liferay tmp folder, before uploaded
ThemeDisplay themeDisplay = (ThemeDisplay)actionRequest.getAttribute(WebKeys.THEME_DISPLAY);
long folderId = ParamUtil.getLong(actionRequest, "folderId");
String fileName = ParamUtil.getString(actionRequest, "fileName");
JSONObject jsonObject = JSONFactoryUtil.createJSONObject();
try
{
DLAppServiceUtil.deleteTempFileEntry(themeDisplay.getScopeGroupId(), folderId, fileName,_TEMP_FOLDER_NAME_ATTACHMENT);
jsonObject.put("deleted", Boolean.TRUE);
}
catch (Exception e)
{
String errorMessage = themeDisplay.translate("an-unexpected-error-occurred-while-deleting-the-file");
jsonObject.put("deleted", Boolean.FALSE);
jsonObject.put("errorMessage", errorMessage);
}
writeJSON(actionRequest, actionResponse, jsonObject);
}
Click for Result view

Related

Salesforce Marketing Cloud code resource can't process an XHR request from an amp-form component

I am using an amp-form inside a dynamic mail and want it to send some data to a code resource sitting on a Salesforce Marketing Cloud org. The request seems to be fine, but it never reaches the code resource. There is always the same error saying that the "Access-Control-Allow-Origin" header is missing. The content of the code resource is taken straight from the AMP documentation on CORS.
Code Resource
<script runat="server" executioncontexttype="post" executioncontextname=corsinampforemail>
Platform.Load("core", "1");
if (Platform.Request.GetRequestHeader("AMP-Email-Sender")) {
var senderEmail = Platform.Request.GetRequestHeader("AMP-Email-Sender")
if (isValidSender(senderEmail)) {
HTTPHeader.SetValue("AMP-Email-Allow-Sender", senderEmail)
} else {
Platform.Function.RaiseError("Sender Not Allowed",true,"statusCode","3");
}
} else if (Platform.Request.GetRequestHeader("Origin")) {
var requestOrigin = Platform.Request.GetRequestHeader("Origin")
if (Platform.Request.GetQueryStringParameter("__amp_source_origin")) {
var senderEmail = Platform.Request.GetQueryStringParameter("__amp_source_origin");
if (isValidSender(senderEmail)) {
HTTPHeader.SetValue("Access-Control-Allow-Origin", requestOrigin);
HTTPHeader.SetValue("Access-Control-Expose-Headers", "AMP-Access-Control-Allow-Source-Origin");
HTTPHeader.SetValue("AMP-Access-Control-Allow-Source-Origin", senderEmail);
} else {
Platform.Function.RaiseError("Invalid Source Origin",true,"statusCode","3");
}
} else {
Platform.Function.RaiseError("Source Origin Not Present",true,"statusCode","3");
}
} else {
Platform.Function.RaiseError("Origin and Sender Not Present",true,"statusCode","3");
}
</script>
amp-form
<form id="test-form" method="post"
action-xhr="https://.../..."
enctype="multipart/form-data">
<input type="submit" value="Submit">
<div submit-success>
<span>Submit successful</span>
</div>
<div submitting>
<span>Submitting...</span>
</div>
<div submit-error>
<span>Error</span>
</div>
</form>
Does anyone know what the problem is here?

Angular 8 to upload an image file (jpg, etc,) - a second approach

I want to use Angular 8 with an Asp.Net Web Api to upload an image file and store it into a SQL server database table where the image column is defined as varbinary(max).
Note: I can do this using an ASP.Net web form just fine - it adds the image and I can display it accordingly. (I included the adding of the image code below). So I am trying to mimic that web form upload but using Angular as the front-end.
For uploading a file in Angular 8, I used this advice: https://www.academind.com/learn/angular/snippets/angular-image-upload-made-easy/
I send the selected image file to the Web Api as binary.
After clicking the button, I get an error on the server-side.
Http failure response for http://localhost:50454/Api/Image/AddImage/: 500 Internal Server Error. Body was: [object Object]
I am able to make the selected image file appear on the page.
Here is all the code. Note: not all the code is included for simplicity.
My SQL server table definition - where by the image is defined as varbinary:
CREATE TABLE [dbo].[tblImages]
(
[ImageId] [int] IDENTITY(1,1) NOT NULL,
[ImageData] [varbinary](max) NOT NULL
CONSTRAINT [PK_Image] PRIMARY KEY CLUSTERED
(
[ImageId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
My SQL server insert stored procedure:
CREATE procedure [dbo].[InsertImages]
#ImageData varbinary(max)
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO dbo.tblImages (
ImageData )
VALUES (
#ImageData )
RETURN 0
END
The Angular html:
<input type="file" (change)="onFileChanged($event)">
<img [src]="imgURL" height="200" *ngIf="imgURL">
The Angular component method code:
onFileChanged(event) {
// Get the file selected.
const selectedFile = event.target.files[0];
// To display the selected image before deciding to upload it.
let reader = new FileReader();
// Gets a 'base64' representation of an image.
reader.readAsDataURL(event.target.files[0]);
reader.onload = (event2) => {
// Sets the html <img> tag to the image.
this.imgURL = reader.result;
};
// Call the service to use the web api to add the image to the database.
this.imageService.addImage(selectedFile).subscribe(
event => {
console.log(event);
});
}
My Angular Image service method:
private data: any;
// The Asp.Net Web Api endpoint.
url = "http://localhost:50454/Api/Image";
addImage(image: Image): Observable<Image> {
const httpOptions = {
headers: new HttpHeaders({ "Content-Type": "application/json" })
};
// Call the ASP.NET 2.1 MVC Web API.
return this.http
.post<Image>(this.url + "/AddImage/", image, httpOptions)
.pipe(
tap(data => data),
catchError(this.handleError)
);
}
My Angular Model class:
export class Image {
ImageData: Binary[]; <--- Is this correct?
}
My Asp.Net Web Api data model:
namespace PrototypeWebApi2.Models
{
public class Image
{
public byte[] ImageData { get; set; } <--- Is this correct?
}
}
My Asp.Net Web Api method:
[HttpPost]
[Route("AddImage")]
public IHttpActionResult PostImage(Image data)
{
try
{
return Ok(dataaccesslayer.AddImage(data));
}
catch (Exception)
{
throw;
}
}
My Asp.Net Web Api data access layer method:
public int AddImage(Image image)
{
try
{
using (SqlConnection con = new SqlConnection(connectionString))
{
SqlCommand cmd = new SqlCommand("InsertImages", con);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Clear();
cmd.Parameters.AddWithValue("#ImageData", image.ImageData);
con.Open();
cmd.ExecuteNonQuery();
con.Close();
}
return 1;
}
catch
{
throw;
}
}
My Asp.Net Web form code that uploads an image file and works fine:
HTML:
<%# Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="AdminPage.aspx.cs" Inherits="Media.Admin.AdminPage" %>
<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
<h1>Administration</h1>
<hr />
<h3>Add Image:</h3>
<table>
<tr>
<td><asp:Label ID="LabelAddImageFile" runat="server">Image File:</asp:Label></td>
<td>
<asp:FileUpload ID="MediaUploadFile" runat="server" />
<asp:RequiredFieldValidator ID="RequiredFieldValidator2" runat="server" Text="* Image file path required." ControlToValidate="MediaUploadFile" SetFocusOnError="true" Display="Dynamic"></asp:RequiredFieldValidator>
</td>
</tr>
</table>
<p></p>
<asp:Button ID="AddMediaButton" runat="server" Text="Add Media" OnClick="AddMediaButton_Click" CausesValidation="true" CssClass="btn btn-primary"/>
<asp:Label ID="LabelAddStatus" runat="server" Text=""></asp:Label>
<p></p>
</asp:Content>
Code behind:
protected void AddMediaButton_Click(object sender, EventArgs e)
{
string strErrorMessage = "";
string fileExtension = "";
int fileSize = 0;
Boolean fileOK = false;
// Get the image file that was selected. References the ASP.Net control.
HttpPostedFile postedFile = MediaUploadFile.PostedFile;
fileExtension = Path.GetExtension(fileName).ToLower();
....
if (fileOK)
{
Stream stream = postedFile.InputStream;
BinaryReader binaryReader = new BinaryReader(stream);
// Read the file into a array of bytes.
Byte[] bytes = binaryReader.ReadBytes((int)stream.Length);
try
{
....
cmd.Parameters.Add("#ImageData", SqlDbType.VarBinary).Value = bytes;
cmd.ExecuteNonQuery();
.....
}
catch (Exception ex)
{
}
finally
{
dbFunc.CloseDB();
}
}
else
{
....
}
}
The object type that you pull from event.target.files[0] is File but you pass it into the addImage function which expects an Image type. I'm guessing its assumed to be any so Typescript doesn't catch the mismatch.
Next, when posting the file to the server I've always had better success using a FormData object.
uploadFiles(files: File[]) {
const formData = new FormData();
files.forEach(file => {
formData.append(file.name, file, file.name);
});
return this.http.post(`/api/files`, formData);
}
Angular's HttpClient service automatically set Content-Type: multipart/form-data header for that.
Then on the WebApi side I use System.Web.HttpContext.Current.Request.Files to access the actual file binaries. From there you should be able to use some form of IO.Stream to save the file.
Hope this helps!
This is confused and has errors on passing data
1) you have missed "this" key word for selectedFile.
2)Form submitting must be done inside onSubmit() method but no in onChanged() method.
3) Parameters have used to catch a formData in service method is wrong.
Change the variable type as followa
selectedFile: any;
Change the codes as follows
onSubmit(){
let form = new FormData;
form.append("ImageData", this.selectedFile, this.selectedFile.name);
this.imageService.addImage(form).subscribe(
event => {
console.log(event);
});
}
Service method as,
addImage(image: FormData): Observable<Image> { //changed
const httpOptions = {
headers: new HttpHeaders({ "Content-Type": "application/json" })
};
// Call the ASP.NET 2.1 MVC Web API.
return this.http
.post<Image>(this.url + "/AddImage/", image, httpOptions)
.pipe(
tap(data => data),
catchError(this.handleError)
);
}
Export class
export class Image {
selectedFile: string; <--- Is this correct?
}
in ASP.net,
namespace PrototypeWebApi2.Models
{
public class Image
{
public byte[] selectedFile { get; set; } <--- name changed
}
}
And finally change the queries according to this changed property name.

AutoComplete Textbox with database

I wanna do autocomplete when i enter a letter.
I have a database "USERS" and it has name .When i try texted for example e
it must show "edgar,edwin,emir" but ,t shows nothing.
ClientController here:
public class ClientController : Controller
{
public JsonResult AutocompleteSuggestions(string searchstring)
{
ModelContext db = new ModelContext();
var suggestions = from E in db.USERS
select E.Name;
var namelist = suggestions.Where(n => n.ToLower().Contains(searchstring.ToLower()));
return Json(namelist, JsonRequestBehavior.AllowGet);
}
}
index.cshtml here:in here there is a textbox and i send client controller autocopleteSuggeston method but it doesnt go or it doesnt work.I add jquery script file on cshtml but it still not working.
#using (Html.BeginForm())
{
<p>
Name: #Html.TextBox("SearchString")
<input type="submit" value="Search" />
</p>
}
<script type="text/javascript">
window.jQuery(function () {
window.jQuery("#SearchString").autocomplete({
source: "/Client/AutocompleteSuggestions",
minLength: 1,
select: function (event, ui) {
if (ui.item) {
window.jQuery("#SearchString").val(ui.item.value);
window.jQuery("form").submit();
}
}
});
});
</script>
i add jquery
Where is the mistake?
you need to add [HttpPost] before JsonResult method like this:
[HttpPost]
public JsonResult AutocompleteSuggestions(string searchstring)
{
ModelContext db = new ModelContext();
var suggestions = from E in db.USERS
select E.Name;
var namelist = suggestions.Where(n => n.ToLower().Contains(searchstring.ToLower()));
return Json(namelist, JsonRequestBehavior.AllowGet);
}
as the form here is submitted using window.jQuery("form").submit(),it invokes a Post Action, so you need to add [HttpPost] for capturing the form submissions or any kind of Post Action!
Change your View Code to
#using( Html.BeginForm(null, null, FormMethod.Post, new{#id ="SearchForm"} ))
{
<p>
Name: #Html.TextBox("SearchString")
<input type="submit" value="Search" />
</p>
}
$(function() {
$("#SearchString").autocomplete({
source: "/Client/AutocompleteSuggestions",
select: function(event, ui) {
$("#SearchString").val(ui.item.value);
$("#SearchForm").submit();
}
});
});

MVC 4 theme switching with Ajax.ActionLinks

The full text of this question is available with a screenshot here
Thanks for any help - original post follows:
So I downloaded the MvcMusicStore and fired up the completed project. I read all the articles talking about extending the view engine and using jquery plugins but I wanted to believe it could be simpler than that to just change the CSS file path when a link gets clicked. Mainly because I didn't want to copy code verbatim that I didn't fully understand. I'm very new to MVC.
So this is what I did:
To HomeController.cs I added:
public ActionResult Theme(string themeName)
{
ViewBag.Theme = ThemeModel.GetSetThemeCookie(themeName);
return View();
}
to Models I added this class:
public class ThemeModel
{
public static string GetSetThemeCookie(string theme)
{
HttpCookie cookie = HttpContext.Current.Request.Cookies.Get("userTheme");
string rv = "Blue";
if (theme != null)
rv = theme;
else
{
if (cookie != null)
rv = cookie["themeName"];
else
rv = "Blue";
}
cookie = new HttpCookie("userTheme");
HttpContext.Current.Response.Cookies.Remove("userTheme");
cookie.Expires = DateTime.Now.AddYears(100);
cookie["themeName"] = rv;
HttpContext.Current.Response.SetCookie(cookie);
return rv;
}
}
I then created 2 copies of Site.css, changing only the background color and font-family and a view to generate my link tag.
<link href="#Url.Content(string.Format("~/Content/{0}.css", ViewBag.Theme))" rel="stylesheet" type="text/css" />
Finally, I made these changes to my _Layout.cshtml.
<!DOCTYPE html>
<html>
<head>
<title>#ViewBag.Title</title>
#if (ViewBag.Theme == null) {Html.RenderAction("Theme", "Home");}
<script src="#Url.Content("~/Scripts/jquery-1.4.4.min.js")"
type="text/javascript"></script>
</head>
<body>
<div id="header">
<h1>ASP.NET MVC MUSIC STORE</h1>
<ul id="navlist">
<li class="first">Home</li>
<li>Store</li>
<li>#{Html.RenderAction("CartSummary", "ShoppingCart");}</li>
<li>Admin</li>
</ul>
</div>
#{Html.RenderAction("GenreMenu", "Store");}
<div id="main">
#RenderBody()
</div>
<div id="footer">
Themes: #Ajax.ActionLink("Coral", "Theme", "Home", new { themeName = "Coral" }, null, new { #style = "color : coral"} )
#Ajax.ActionLink("Blue", "Theme", "Home", new { themeName = "Blue" }, null, new { #style = "color : blue;"})
</div>
</body>
</html>
When I run the app I get the general layout rendered twice. Once with only the genre menu rendered on the left and nothing in the body. And then again with the top 5 albums. I can't post the image as I don't have enough rep.
When I click my Coral and Blue links, my theme changes and I get just the one set without the top 5 albums.
So after some more reading on here I tried this:
_Layout.cshtml:
#{Html.RenderAction("Theme", "Home");}
HomeController.cs
public ActionResult Theme(string themeName)
{
ViewBag.Theme = ThemeModel.GetSetThemeCookie(themeName);
return PartialView();
}
But even though this stops the duplicate rendering, when I click the theme link, the colour changes but I get absolutely nothing else on the page.
Well and truly flummoxed now and could really use some help.
Cheers,
.pd.
Okay - here's how I did it in the end.
Create a javascript file. Mine's called master.js:
function ajaxSuccSetTheme(theme) {
$('#linkTheme').attr('href', '/Content/' + theme + '.css');
}
Modify the _Layout.cshtml:
#{
if (ViewBag.Theme == null) {
ViewBag.Theme = MvcMusicStore.Models.ThemeModel.GetSetThemeCookie();
}
}
<link id="linkTheme" href="#Url.Content(string.Format("~/Content/{0}.css", ViewBag.Theme))" rel="stylesheet" type="text/css" />
<script src="#Url.Content("~/Scripts/jquery-2.0.3.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/master.js")" type="text/javascript"></script>
Notes on this:
The first time the page loads Theme will not have been written to the ViewBag
Give the <link> tag the same ID as the jQuery selector in your js file above
Update unobtrusive ajax jQuery file to the same version as your jQuery lib. Your Ajax.ActionLink won't work without it.
Then my theme switching links in _Layout.cshtml look like this:
<div id="footer">
Themes :
#Ajax.ActionLink("Coral", "Theme", "Home", new { themeName = "Coral" },
new AjaxOptions { HttpMethod = "POST", OnSuccess = string.Format("ajaxSuccSetTheme('{0}');", "Coral")},
new { #style = "color : coral;" }) |
#Ajax.ActionLink("Blue", "Theme", "Home", new { themeName = "Blue" },
new AjaxOptions { HttpMethod = "POST", OnSuccess = string.Format("ajaxSuccSetTheme('{0}');", "Blue")},
new { #style = "color : blue;" })
</div>
Notes on that:
themeName = "whatever" is the argument to your Theme Controller method. this gets passed to the cookie method in the ThemeModel
method = POST so IE doesn't cache it and I've read a couple other questions that got solved by not doing a GET
you have to kludge your own args to the OnSuccess js callback
Next the HomeController.cs change:
public ActionResult Theme(string themeName)
{
ViewBag.Theme = ThemeModel.GetSetThemeCookie(themeName);
if (Request.IsAjaxRequest())
{
return PartialView();
}
else
{
return null;
}
}
Honestly, it doesn't matter if you just return null without checking for IsAjaxRequest() cuz all we need from this is to set the cookie so it remembers when you next login.
Which just leaves the cookie setting method in the ThemeModel:
public class ThemeModel
{
public static string GetSetThemeCookie(string theme = null)
{
HttpCookie cookie = HttpContext.Current.Request.Cookies.Get("userTheme");
string rv = "Blue";
if (theme != null)
rv = theme;
else
{
if (cookie != null)
rv = cookie["themeName"];
else
{
cookie = new HttpCookie("userTheme");
rv = "Blue";
}
}
cookie.Expires = DateTime.Now.AddYears(100);
cookie["themeName"] = rv;
HttpContext.Current.Response.SetCookie(cookie);
return rv;
}
}
Hope I helped somebody. If you'd rather do it all in jQuery here's Tim Vanfosson's Theme Manager jQuery Plugin
Cheers,
.pd.

How to get uploaded image in serverside using Ember.js

I'm new to Ember.js and I'm stuck with a problem I need to save the uploaded image in db but I dont know how to do that I wrote code for upload the image but i'm stuck with passing it to the server my current code is given below
App.js
App = Ember.Application.create();
App.PreviewImageView = Ember.View.extend({
attributeBindings: ['name', 'width', 'height', 'src'],
tagName: 'img',
viewName: 'previewImageView',
printme: function () {
console.log('in previewImageView');
}
});
App.FileField= Ember.TextField.extend({
type: 'file',
attributeBindings: ['name'],
change: function (evt) {
var input = evt.target;
if (input.files && input.files[0]) {
var reader = new FileReader();
reader.onload = function (e) {
this.$().parent(':eq(0)').children('img:eq(0)').attr('src', e.target.result);
var view = that.getPath('parentView.previewImageView');
view.set('src', e.target.result);
}
reader.readAsDataURL(input.files[0]);
}
}
});
html
<script type="text/x-handlebars">
{{view App.FileField name="logo_image" contentBinding="content"}}
{{view App.PreviewImageView width="200" height="100" }}
</script>
I think you can mix some traditional MVC methods to solve your problem. from your current code I can assume that showing a preview of the image is completed so to get that file in server side just use the following code in your html
#using (Html.BeginForm("FileUpload", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<input type="file" name="file" {{view Wizard.FileField contentBinding="content"}} />
<input type="submit" id="btnUpload" value="Upload" />
}
and In you controller method you can access the file like this
public ActionResult FileUpload(HttpPostedFileBase file)
{
// Do what you want
}
To save the image in db you have to convert it into bytes (sql server 2008 support image now but db like postgresql still need image as bytes) to do that use the following method
MemoryStream target = new MemoryStream();
file.InputStream.CopyTo(target);
byte[] bytes= target.ToArray();
return View();
Assuming you are using ember-data, you can create a model to represent the image and then create/save from the reader's onload callback. For example:
App.LogoImage = DS.Model.extend({
id: DS.attr('number'),
attachment: DS.attr('string')
});
//in App.FileField...
reader.onload = function (e) {
this.$().parent(':eq(0)').children('img:eq(0)').attr('src', e.target.result);
var view = that.getPath('parentView.previewImageView');
view.set('src', e.target.result);
var file = e.srcElement.result;
var logo = App.LogoImage.createRecord({ attachment: file });
logo.save();
}