Open MultipartFile(Blob storage) in a _blank tab - file

I have a SpringBoot 2.1.3 + Thymeleaf 3 webapp. I have a big form with some information and also file upload. The uploading file works well, but when I want to reload into the same form (for detail or modify purpose) the information stored into DB everything works well less the part related to the files.
The code, for the uploading file part, is the follow:
<div class="form-group row">
<label for="allegato_durc" class="col-sm-5 col-form-label form-label">Allegato DURC</label>
<div class="col-sm-7">
<input type="file" th:field="*{documentiFornitoreDto.allegato_DURC}" class="form-control-file form-control-sm datif_input" id="allegato_durc">
</div>
<label for="allegato_CCIAA" class="col-sm-5 col-form-label form-label">Allegato CCIAA</label>
<div class="col-sm-7">
<input type="file" th:field="*{documentiFornitoreDto.allegato_CCIAA}" class="form-control-file form-control-sm datif_input" id="allegato_CCIAA">
</div>
</div>
Even if the file is present, I see the input field empty as below:
I'm storing the MultipartFile as MediumBlob into DB and, when I reload the info from DB, I rebuild the MultipartFile as follows:
public class ByteToMultipartFile implements MultipartFile {
private byte[] fileContent;
private String fileName;
public ByteToMultipartFile(String fileName, byte[] fileContent) {
this.fileContent = fileContent;
this.fileName = fileName;
}
#Override
public String getName() {
return fileName;
}
#Override
public String getOriginalFilename() {
return fileName;
}
#Override
public String getContentType() {
// TODO Auto-generated method stub
return null;
}
#Override
public boolean isEmpty() {
if (fileContent.length > 0) return false;
else return true;
}
#Override
public long getSize() {
return fileContent.length;
}
#Override
public byte[] getBytes() throws IOException {
return fileContent;
}
#Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(fileContent);
}
#Override
public void transferTo(File dest) throws IOException, IllegalStateException {
// TODO Auto-generated method stub
}
}
Maybe there's something wrong with the class above??
Anyway I would like to perform 2 things:
1) Show the filename near Choose button (Scegli file in the image) when present
2) Show a button that permit the user to OPEN the file in a properly Windows app (if it is a .pdf open it with acrobat reader and so on)
It is possible to do some??
I have read right here, into a old post, that a file could be open in a new _blank tab (or page makes no difference) this way:
<h4>#document.Name</h4>
that is roughly what I want. Now the author writes that this attr:
#document.ContentBlobURL
represents the blob storage address of the DB. Is there someone who knows what it is?? How can I retrieve that value?
I googling a lot but I couldn't find anything interesting.
I would like to point out that, as you know, in a SpringBoot application (for example) with this structure:
if I save the file on disk, inside static folder for example, I can open it by:
http://localhost:8080/costruzione_stampi.pdf
I would like the same thing but whitout saving files on the disk..
Hope someone will answer..

I found a solution, I wanna post it because I hope it helps somebody else.
Googling around I find out that I can't set value of
<input type="file" ...
in a form with data (I have tried with Multipart, File, Blob, byte[] ecc...) loaded from DB for security reasons.
With this I mean that I can't set the input file value with a procedure like below:
#Controller
public class AppController {
#GetMapping('/loadDataInForm')
public String showData(Model model) {
model.addAttribute('file', repository.getByPk(1)); // suppose that this repository retrive a Blob or MultipartFile or someone else
return "form.html"
}
}
form.html
.....
<input type="file" th:field="*{file}" id="file_data"> // always contains nothing
I found some workaround (one of this is here) but is really not a best practice.
Anyway, if you have a different needs, for example show a preview of the file chosen from user (but at uploading time!!) you can use this trick:
<input type="file" th:field="*{someDto.file}" id="allegato_durc" onchange="show();">
.....
<script type="text/javascript">
function show() {
const fileElem = document.getElementById('allegato_durc').files[0];
var binaryData = [];
binaryData.push(fileElem);
var blob = new Blob(binaryData, {type: "image/jpg"});
const objectURL = window.URL.createObjectURL(blob);
window.open(objectURL, '_blank');
}
</script>
Hope helps..

Related

Ho do i pass a model with data from the DB to an ABP.IO Layout Hook?

trying to setup a multi-tenant site using ABP.io framework 3.1.
I am trying to set the <meta keywords (amongst other tags) in the page html head. I am attempting to get the values from a database field for the current tenant so the meta keywords will be specific for the tenant.
I tried to follow the sample that is available here: https://docs.abp.io/en/abp/latest/UI/AspNetCore/Customization-User-Interface#layout-hooks where they inject a google analytics script code into the head tag.
this is fine, as it is static text, but when i try to load the partial page with a model it throws an error of expecting a different model to that which is passed in.
So far i have the Notification View Componet
Public class MetaKeywordViewComponent : AbpViewComponent
{
public async Task<IViewComponentResult> InvokeAsync() {
return View("/Pages/Shared/Components/Head/MetaKeyword.cshtml"); //, meta);
}
}
and the cshtml page
#using MyCompany.MyProduct.Web.Pages.Shared.Components.Head
#model MetaKeywordModel
#if (Model.SiteData.Keywords.Length > 0)
{
<meta content="#Model.SiteData.Keywords" name="keywords" />
}
and the cshtml.cs file as
public class MetaKeywordModel : MyProductPageModel
{
private readonly ITenantSiteDataAppService _tenantSiteDataAppService;
public TenantSiteDataDto SiteData { get; private set; }
public MetaKeywordModel(ITenantSiteDataAppService tenantSiteDataAppService)
{
_tenantSiteDataAppService = tenantSiteDataAppService;
}
public virtual async Task<ActionResult> OnGetAsync()
{
if (CurrentTenant != null)
{
SiteData = await _tenantSiteDataAppService.GetSiteDataAsync();
}
return Page();
}
}
but when i run the program i get the following error.
An unhandled exception has occurred while executing the request.
System.InvalidOperationException: The model item passed into the ViewDataDictionary is of type 'Volo.Abp.AspNetCore.Mvc.UI.Components.LayoutHook.LayoutHookViewModel', but this ViewDataDictionary instance requires a model item of type 'MyCompany.MyProduct.TenantData.Dtos.TenantSiteDataDto'.
How do i pass the data from my database into the page to be rendered if i can't use my model?
Any help tips or tricks would be greatly appreciated.
Regards
Matty
ViewComponent is different from the razor page.
See https://learn.microsoft.com/en-us/aspnet/core/mvc/views/view-components?view=aspnetcore-3.1#view-components
You should inject the service in view component class directly. like:
public class MetaKeywordViewComponent : AbpViewComponent
{
private readonly ITenantSiteDataAppService _tenantSiteDataAppService;
public MetaKeywordViewComponent(ITenantSiteDataAppService tenantSiteDataAppService)
{
_tenantSiteDataAppService = tenantSiteDataAppService;
}
public async Task<IViewComponentResult> InvokeAsync()
{
return View("/Pages/Shared/Components/Head/MetaKeyword.cshtml",
await _tenantSiteDataAppService.GetSiteDataAsync());
}
}
In addition, you can refer https://github.com/abpframework/abp/blob/42f37c5ff01ad853a5425d15539d4222cd0dab69/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/PageAlerts/PageAlertsViewComponent.cs

ArrayIndexOutOfBoundsException grails

I have this simple controller which uploads a file into the database. I have a working view which displays the form for uploading, but when I click on the upload button, I run into this error at the documentInstance.save() line: ArrayIndexOutOfBoundsException occurred when processing request:[POST]/myApp/documentFile/upload
class DocumentController {
def upload() {
def file = request.getFile('file')
if(file.empty) {
flash.message = "File cannot be empty"
} else {
def documentInstance = new Document()
documentInstance.filename = file.originalFilename
documentInstance.filedata = file.getBytes()
documentInstance.save()
}
redirect (action:'list')
}
}
Can anyone help me understand where the problem lies? Is the information I have given sufficient for answering this? If not please let me know.
UPDATE:
form element part of the gsp is as below.
<g:uploadForm action="upload">
<fieldset class="form">
<input type="file" name="file" />
</fieldset>
<fieldset class="buttons">
<g:submitButton name="upload" class="save" value="Upload" />
</fieldset>
</g:uploadForm>
Here is the Document domain class
class Document{
String filename
byte[] fileData
static constraints = {
filename(blank:false,nullable:false)
filedata(blank: true, nullable:true, maxSize:1073741824)
}
}
Try setting 'size' or 'maxSize' constraints on your domain objects 'filedata' field according to the size of the files you are uploading. Your database might be creating small columns that cannot hold the file size you are uploading. According to http://grails.org/doc/latest/guide/theWebLayer.html#uploadingFiles

Nancy testing GetModel<T> throws KeyNotFoundException

I'm trying to test that the model returned from my Nancy application is as expected. I have followed the docs here but whenever I call the GetModel<T> extension method it throws a KeyNotFoundException.
System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.
I know what the error means but I'm failing to see why it's being thrown.
Here's my module
public class SanityModule : NancyModule
{
public SanityModule()
{
Get["sanity-check"] = _ => Negotiate.WithModel(new SanityViewModel { Id = 1 })
.WithStatusCode(HttpStatusCode.OK);
}
}
my view model
public class SanityViewModel
{
public int Id { get; set; }
}
and here's my test
[TestFixture]
public class SanityModuleTests
{
[Test]
public void Sanity_Check()
{
// Arrange
var browser = new Browser(with =>
{
with.Module<SanityModule>();
with.ViewFactory<TestingViewFactory>();
});
// Act
var result = browser.Get("/sanity-check", with =>
{
with.HttpRequest();
with.Header("accept", "application/json");
});
var model = result.GetModel<SanityViewModel>();
// Asset
model.Id.ShouldBeEquivalentTo(1);
}
}
Debugging this test shows that the module is hit and completes just fine. Running the application shows that the response is as expected.
Can anyone shed some light on this?
Thanks to the lovely guys, albertjan and the.fringe.ninja, in the Nancy Jabbr room we've got an explanation as to what's going on here.
TL;DR It makes sense for this to not work but the error message should be more descriptive. There is a workaround below.
The issue here is that I am requesting the response as application/json whilst using TestingViewFactory.
Let's take a look at the implementation of GetModel<T>();
public static TType GetModel<TType>(this BrowserResponse response)
{
return (TType)response.Context.Items[TestingViewContextKeys.VIEWMODEL];
}
This is simply grabbing the view model from the NancyContext and casting it to your type. This is where the error is thrown, as there is no view model in NancyContext. This is because the view model is added to NancyContext in the RenderView method of TestingViewFactory.
public Response RenderView(string viewName, dynamic model, ViewLocationContext viewLocationContext)
{
// Intercept and store interesting stuff
viewLocationContext.Context.Items[TestingViewContextKeys.VIEWMODEL] = model;
viewLocationContext.Context.Items[TestingViewContextKeys.VIEWNAME] = viewName;
viewLocationContext.Context.Items[TestingViewContextKeys.MODULENAME] = viewLocationContext.ModuleName;
viewLocationContext.Context.Items[TestingViewContextKeys.MODULEPATH] = viewLocationContext.ModulePath;
return this.decoratedViewFactory.RenderView(viewName, model, viewLocationContext);
}
My test is requesting json so RenderView will not be called. This means you can only use GetModel<T> if you use a html request.
Workaround
My application is an api so I do not have any views so changing the line
with.Header("accept", "application/json");
to
with.Header("accept", "text/html");
will throw a ViewNotFoundException. To avoid this I need to implement my own IViewFactory. (this comes from the.fringe.ninja)
public class TestViewFactory : IViewFactory
{
#region IViewFactory Members
public Nancy.Response RenderView(string viewName, dynamic model, ViewLocationContext viewLocationContext)
{
viewLocationContext.Context.Items[Fixtures.SystemUnderTest.ViewModelKey] = model;
return new HtmlResponse();
}
#endregion
}
Then it is simply a case of updating
with.ViewFactory<TestingViewFactory>();
to
with.ViewFactory<TestViewFactory>();
Now GetModel<T> should work without needing a view.

Getting p:graphicImage from byte column DB

I need to show a graphic image from a byte column in database. Below I have the code for a graphic imagem from a physical path.. how can I do it from a byte column?
<h:panelGrid columns="1" style="width:100%" cellpadding="5">
<p:graphicImage value="/images/cars/test.jpg"/>
</h:panelGrid>
The <p:graphicImage> supports streaming content. All you need is the following application scoped bean which returns the desired image bytes from the DB based on an unique image identifier as request parameter:
#Named
#ApplicationScoped
public class ImageStreamer {
#EJB
private ImageService service;
public StreamedContent getImage() throws IOException {
FacesContext context = FacesContext.getCurrentInstance();
if (context.getCurrentPhaseId() == PhaseId.RENDER_RESPONSE) {
// So, we're rendering the HTML. Return a stub StreamedContent so that it will generate right URL.
return new DefaultStreamedContent();
} else {
// So, browser is requesting the image. Return a real StreamedContent with the image bytes.
String imageId = context.getExternalContext().getRequestParameterMap().get("imageId");
Image image = imageService.find(Long.valueOf(imageId));
return new DefaultStreamedContent(new ByteArrayInputStream(image.getBytes()));
}
}
}
Here's how you can use it:
<p:graphicImage value="#{imageStreamer.image}">
<f:param name="id" value="#{someBean.imageId}" />
</p:graphicImage>
See also:
Display dynamic image from database with p:graphicImage and StreamedContent

SessionScoped Bean loses data on post-back on Google Appengine

I use Eclipse 3.7 GAE pluggin for development. My application uses JSF and datastore, and was set up as per https://sites.google.com/a/wildstartech.com/adventures-in-java/Java-Platform-Enterprise-Edition/JavaServer-Faces/javaserver-faces-20/configuring-javaserver-faces-20-to-run-on-the-google-appengine. In my development system, it works well. But when deployed to GAE, the SessionScoped Bean loses data on post-back:
// Input facelet
<h:outputLabel for="popupCal">Date </h:outputLabel>
<p:calendar value="#{editEntry.current.date1}" id="popupCal" />
<h:outputLabel for="code">Code </h:outputLabel>
<h:inputText id="code" value="#{editEntry.current.accountCode}"/>
<h:outputLabel for="amt">Amount </h:outputLabel>
<h:inputText id="amt" value="#{editEntry.current.amountInDollars}"/>
<h:commandButton action="#{editEntry.createCashExpenditure}" value="Create Entry"/>
#ManagedBean(name="editEntry")
#SessionScoped
public class EditEntry extends AbstractEntryBean implements Serializable {
#ManagedProperty(value="#{sessionBean}")
protected SessionBean sessionBean;
#ManagedProperty(value="#{dao}")
protected Dao dao;
#PostConstruct
public void init() {
Logger.getLogger(getClass().getName()).log(Level.WARNING, "dao is null? {0}", dao==null);
setTran_id(0L);
entries.clear();
setCurrent(new Entry());
getCurrent().clear();
...
this.refreshEntries();
}
public void refreshEntries() {
entries = dao.getEntries(current.getFinyr(), getTran_id());
Logger.getLogger(getClass().getName()).log(Level.INFO, "entries has {0} items", entries.size());
}
public String createCashExpenditure() {
if (dao == null) {
Logger.getLogger(getClass().getName()).log(Level.WARNING, "dao is null");
return null;
}
entries.clear();
Entry e = new Entry();
e.clear();
e.setAccountCode(current.getAccountCode());
e.setAccountName(dao.lookupAccoutName(e.getAccountCode()));
e.setAmount(current.getAmount());
e.setDate1(current.getDate1());
e.setTran_id(getTran_id());
Key key = dao.saveEntry(e, sessionBean.getFinyr());
e.setId(key.getId());
entries.add(e);
current = e;
this.setTran_id(e.getTran_id());
Logger.getLogger(getClass().getName()).log(Level.INFO, "current account is: {0}", current.getAccountCode());
return "newEntry?faces-redirect=true";
}
...
}
newEntry.xhtml
<p:dataTable id="items" value="#{editEntry.entries}" var="item">
// editEntry.entries is EMPTY!
When EditEntry.createCashExpenditure() is invoked, the log shows EditEntry.current is correctly populated, and saved to the datastore. Datastore viewer also displays the data. But on post-back, in newEntry.xhtml facelet, editEntry.entries becomes empty, EditEntry.current loses all data.
I have put in place ForceSessionSerializationPhaseListener as mentioned in http://java.zacheusz.eu/google-app-engine-http-session-vs-jsf-en/394/ The log shows this listener is invoked.
In web.xml, javax.faces.PROJECT_STAGE is Production,
I face the same issues, after redirect, previous session is gone. It only happen when deploy online.
I think that is due to session variable set to 'client' for javax.faces.STATE_SAVING_METHOD (in web.xml)
so before redirect, I need explicit set the session as below:
getSessionScope().put(sessionname,sessionObj);
public Map getSessionScope() {
return getFacesContext().getExternalContext().getSessionMap();
}

Resources