How to get FileUpload working in WASM for Uno Platform - uno-platform

I'm trying to get HTML file upload control working on WASM. So far I've tried to do the following:
[HtmlElement("input")]
public class FilePickerView : FrameworkElement
{
public FilePickerView()
{
// XAML behavior: a non-null background is required on an element to be "visible to pointers".
// Uno reproduces this behavior, so we must set it here even if we're not using the background.
// Not doing this will lead to a `pointer-events: none` CSS style on the control.
Background = new SolidColorBrush(Colors.Transparent);
this.SetHtmlAttribute("type", "file");
}
}
And then in the view:
<wasm:FilePickerView Height="35" Width="300" x:Name="filePicker" HorizontalAlignment="Left" />
I get the control displayed, I can click on it and it displays the name of the file I've selected.
I am pretty lost after this.
I'd like to be able to do two things:
Access file path in code behind.
Send file contents to code behind for processing.
Would appreciate any pointers on this.
I've been through the following pages in the documentation:
(Wasm) Handling custom HTML events - https://qa.website.platform.uno/docs/articles/wasm-custom-events.html
Embedding Existing JavaScript Components Into Uno-WASM - Part 1 - https://qa.website.platform.uno/docs/articles/interop/wasm-javascript-1.html
Embedding Existing JavaScript Components Into Uno-WASM - Part 2 - https://qa.website.platform.uno/docs/articles/interop/wasm-javascript-2.html
Embedding Existing JavaScript Components Into Uno-WASM - Part 3 - https://qa.website.platform.uno/docs/articles/interop/wasm-javascript-3.html

After connecting pieces from the internet, I came up with this method.
However, it only works with files max 500 kb big.
To enable large file upload I had to upgrade wasm target to .net 5 and use developer versions (2.0.0-dev.167) of Uno.Wasm.Bootstrap and Uno.WasmBootstrap.DevServer (how to upgrade target is described here).
In this code I enabled upload of only .wav audio files
private async void uploadBtn_Click(object sender, RoutedEventArgs e)
{
FileSelectedEvent -=OnFileUploadedEvent;
FileSelectedEvent += OnFileUploadedEvent;
WebAssemblyRuntime.InvokeJS(#"
var input = document.createElement('input');
input.type = 'file';
input.accept = '.wav';
input.onchange = e => {
var file = e.target.files[0];
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = readerEvent => {
//this is the binary uploaded content
var content = readerEvent.target.result;
//invoke C# method to get audio binary data
var selectFile = Module.mono_bind_static_method(" + "\"[MyApp.Wasm] MyApp.Shared.MyPage:SelectFile\""+#");
selectFile(content);
};
};
input.click(); "
);
}
public static void SelectFile(string fileAsDataUrl) => FileSelectedEvent?.Invoke(null, new FileSelectedEventHandlerArgs(fileAsDataUrl));
private void OnFileUploadedEvent(object sender, FileSelectedEventHandlerArgs e)
{
FileSelectedEvent -= OnFileUploadedEvent;
var base64Data = Regex.Match(e.FileAsDataUrl, #"data:audio/(?<type>.+?),(?<data>.+)").Groups["data"].Value;
var binData = Convert.FromBase64String(base64Data); //this is the data I want
Console.Out.WriteLine("I got binary data of uploaded file");
}
private static event FileSelectedEventHandler FileSelectedEvent;
private delegate void FileSelectedEventHandler(object sender, FileSelectedEventHandlerArgs args);
private class FileSelectedEventHandlerArgs
{
public string FileAsDataUrl { get; }
public FileSelectedEventHandlerArgs(string fileAsDataUrl) => FileAsDataUrl = fileAsDataUrl;
}
Also I was not able to run it with SQLite at the same time. Sadly, I still haven't figured out why.
Sources:
https://github.com/unoplatform/uno/issues/508
https://github.com/unoplatform/uno/issues/3525
https://platform.uno/blog/uno-platform-3-2-net-5-c-9-support-and-net-5-webassembly-aot-support/
EDIT: Appareantly SQLite for .NET 5/6 is still work in progress and there are some packages that need changes.

Related

Routing WebView2 REST calls to local .NET 5 Controllers

I'm currently designing an Angular SPA web client, backed with .NET5 REST. It's all in the same Visual Studio project, and it builds / runs fine.
I'm now investigating the possibility of distributing this as a windows desktop application. I was able to get Electron.NET to work, but it seems like a round-about solution (Node?!). I also didn't particularly like that the resources were visible/changeable in the distributed app.
This led me to investigate using WebView2 within WPF (Microsoft seems to be making a similar transition with MSTeams.) I've found some examples, but they only use:
solely remote content ("www.bing.com")
local content, but only img / html / etc
postmessage, etc to communicate using custom objects.
None of these is what I want. Well, that's not entirely true. I need #2 to load the Angular SPA, but when the WebView2-hosted Angular invokes HttpClient, I'd like to intercept that request in the host application and Route it to my REST Controllers. This would allow me to keep nearly all of my code intact, and presumably ship a smaller, more obfuscated exe.
Is this possible? obvious? Is my desire fundamentally flawed? (wouldn't be the first time)
Chromium.AspNetCore.Bridge offers a solution to the problem. It uses owin to host the server-side code in memory, and provides a RequestInterceptor to cleanly relay all requests to the "server" code.
The link above has working examples, but briefly:
App.xaml.cs:
private IWebHost _host;
private AppFunc _appFunc;
public AppFunc AppFunc
{
get { return _appFunc; }
}
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "Development");
_ = Task.Run(async () =>
{
var builder = new WebHostBuilder();
builder.ConfigureServices(services =>
{
var server = new OwinServer();
server.UseOwin(appFunc =>
{
_appFunc = appFunc;
});
services.AddSingleton<IServer>(server);
});
_host = builder
.UseStartup<Startup>()
.UseContentRoot(Directory.GetCurrentDirectory())
.Build();
await _host.RunAsync();
});
}
MainWindow.xaml.cs
private AppFunc _appFunc;
public MainWindow()
{
InitializeComponent();
Browser.CoreWebView2InitializationCompleted += Browser_CoreWebView2InitializationCompleted;
}
private void Browser_CoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e)
{
if (e.IsSuccess)
{
_appFunc = ((App)Application.Current).AppFunc;
Browser.CoreWebView2.WebResourceRequested += BrowserWebResourceRequestedAsync;
Browser.CoreWebView2.AddWebResourceRequestedFilter("*", CoreWebView2WebResourceContext.All);
}
}
private async void BrowserWebResourceRequestedAsync(object sender, CoreWebView2WebResourceRequestedEventArgs e)
{
var deferral = e.GetDeferral();
var request = new ResourceRequest(e.Request.Uri, e.Request.Method, e.Request.Headers, e.Request.Content);
var response = await RequestInterceptor.ProcessRequest(_appFunc, request);
var coreWebView2 = (CoreWebView2)sender;
e.Response = coreWebView2.Environment.CreateWebResourceResponse(response.Stream, response.StatusCode, response.ReasonPhrase, response.GetHeaderString());
deferral.Complete();
}

GWT File Upload form submit POST request not able to read file

I have tried FileUpload referring GWT source doc. Since I wanted to add it on different tab I have created GWT page for that and added FileUpload over there.
Not implmented entryPoint since its been implemented in their root page.
I am not using onModuleLoad method I am just creating method to display element and adding it to FormPanel.
I am able to submit POST request but not able to capture File on servlet. Am I doing something wrong at GWT side or servlet Side.
I have used similar kind of code at GWT side
public class FormPanelExample implements Composite {
public void FormPanelExample() {
// Create a FormPanel and point it at a service.
final FormPanel form = new FormPanel();
form.setAction("/myFormHandler");
// Because we're going to add a FileUpload widget, we'll need to set the
// form to use the POST method, and multipart MIME encoding.
form.setEncoding(FormPanel.ENCODING_MULTIPART);
form.setMethod(FormPanel.METHOD_POST);
// Create a panel to hold all of the form widgets.
VerticalPanel panel = new VerticalPanel();
form.setWidget(panel);
// Create a TextBox, giving it a name so that it will be submitted.
final TextBox tb = new TextBox();
tb.setName("textBoxFormElement");
panel.add(tb);
// Create a ListBox, giving it a name and some values to be associated with
// its options.
ListBox lb = new ListBox();
lb.setName("listBoxFormElement");
lb.addItem("foo", "fooValue");
lb.addItem("bar", "barValue");
lb.addItem("baz", "bazValue");
panel.add(lb);
// Create a FileUpload widget.
FileUpload upload = new FileUpload();
upload.setName("uploadFormElement");
panel.add(upload);
// Add a 'submit' button.
panel.add(new Button("Submit", new ClickHandler() {
public void onClick(ClickEvent event) {
form.submit();
}
}));
// Add an event handler to the form.
form.addSubmitHandler(new FormPanel.SubmitHandler() {
public void onSubmit(SubmitEvent event) {
// This event is fired just before the form is submitted. We can take
// this opportunity to perform validation.
if (tb.getText().length() == 0) {
Window.alert("The text box must not be empty");
event.cancel();
}
}
});
form.addSubmitCompleteHandler(new FormPanel.SubmitCompleteHandler() {
public void onSubmitComplete(SubmitCompleteEvent event) {
// When the form submission is successfully completed, this event is
// fired. Assuming the service returned a response of type text/html,
// we can get the result text here (see the FormPanel documentation for
// further explanation).
Window.alert(event.getResults());
}
});
RootPanel.get().add(form);
}
}
At Servlet side
if (!ServletFileUpload.isMultipartContent(request)) {
throw new FileUploadException("error multipart request not found");
}
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
List<FileItem> items = upload.parseRequest(request);
if (items == null) {
response.getWriter().write("File not correctly uploaded");
return;
}
Iterator<FileItem> iter = items.iterator();
When I am calling iter.next(), it gives error no such elementFound Exception
By exception it looks to be on submit file is not submitting to servlet request.
Try using postman to call the endpoint and upload a file directly to your running Servlet to ensure that is working correctly.
I checked my own implementation of this code and it almost matches yours exactly, except I'm not using anything but the FileUpload on the panel. Remove the TextBox and ListBox so we can check that just the file part is working on its own, then introduce each item and test it separately.
I found this to be more reliable on the server side
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload servletFileUpload = new ServletFileUpload(factory);
FileItemIterator iter = servletFileUpload.getItemIterator(request);

Trying to synchronize states between JavaFX WebView DOM and controlling class

In my controller class, I have a WebView object set up
#FXML
private WebView newsletterPreview;
// some stuff...
urlString = url.toExternalForm();
WebEngine engine = newsletterPreview.getEngine();
bridge = new JSBridge(engine, this);
JSBridge code snippet ...
private String headlineCandidate;
public String getHeadlineCandidate() {
return headlineCandidate;
}
//mo stuff...
public JSBridge(WebEngine engine, ENewsLetterDialogController controller) {
engine.getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> {
if (newState == State.SUCCEEDED) {
window = (JSObject) engine.executeScript("window");
window.setMember("bridge", this);
engine.executeScript(loadDomListenerScript());
}
});
}
private String loadDomListenerScript() {
return WOWResourceUtils.resourceToString(LISTENER_SCRIPT);
}
public void captureHeadline(String element) {
this.headlineCandidate = element;
System.out.println(headlineCandidate);
}
Listener script snippet..
//listener script
"use strict";
document.addEventListener('click', function(e) {
e = e || window.event;
var target = e.target || e.srcElement, text = target.textContent ||
text.innerText;
window.bridge.captureHeadline(target.parentElement.outerHTML);
});
Where the LISTENER_SCRIPT is embedded Javascript as a resource, and headlineCandidate is a public string property
I have the following working:
I navigate load the web page, and I can get the JSBridge class to echo back the expected parent html of whatever element I click (obviously this is for testing). However I cannot find a good way to get that information back to Application thread so I can (for example) enable a button if the user clicks on the correct HTML element. Yes, this design is based on certain assumptions about the page. That's part of the use case.
THe root problem seems to be that the WebView is on a different thread than the Application, but I couldn't find a good way to synchronize the two
Should I try polling from Application Thread?
Sorry if it's been asked before, been searching here for about 3 days
Use Platform.runLater to communicate data from a non-JavaFX thread to the JavaFX thread:
// Java method invoked as a result of a callback from a
// JavaScript event handler in a webpage.
public void callbackJava(String data) {
// We are currently not on the JavaFX application thread and
// should not directly update the scene graph.
// Instead we call Platform.runLater to ship processing of data
// to the JavaFX application thread.
Platform.runLater(() -> handle(data));
}
public void handle(String data) {
// now we are on the JavaFX application thread and can
// update the scene graph.
label.setText(data);
}

How to reload apache commons configurations2 properties

can anyone guide me on how to perform a reload of an apache commons configuration2 properties. I'm unable to find any implementation of this anywhere. The apache docs are a bit too abstract. This is what I have so far but it's not working.
CombinedConfiguration cc = new CombinedConfiguration();
Parameters params = new Parameters();
File configFile = new File("config.properties");
File emsFile = new File("anotherconfig.properties");
ReloadingFileBasedConfigurationBuilder<FileBasedConfiguration> configBuilder =
new ReloadingFileBasedConfigurationBuilder<FileBasedConfiguration>(PropertiesConfiguration.class)
.configure(params.fileBased()
.setFile(configFile));
PeriodicReloadingTrigger reloadTrg = new PeriodicReloadingTrigger(configBuilder.getReloadingController(), null, 5, TimeUnit.SECONDS);
reloadTrg.start();
cc.addConfiguration(configBuilder.getConfiguration());
FileBasedConfigurationBuilder<FileBasedConfiguration> emsBuilder =
new FileBasedConfigurationBuilder<FileBasedConfiguration>(PropertiesConfiguration.class)
.configure(params.properties()
.setFile(emsFile));
cc.addConfiguration(emsBuilder.getConfiguration());
DataSource ds = EmsDataSource.getInstance().getDatasource(this);
BasicConfigurationBuilder<DatabaseConfiguration> dbBuilder =
new BasicConfigurationBuilder<DatabaseConfiguration>(DatabaseConfiguration.class);
dbBuilder.configure(
params.database()
.setDataSource(ds)
.setTable("EMS_CONFIG")
.setKeyColumn("KEY")
.setValueColumn("VALUE")
);
cc.addConfiguration(dbBuilder.getConfiguration());
The configuration obtained from a builder is not updated automatically. You need to get the configuration from the builder every time you read it.
From Automatic Reloading of Configuration Sources:
One important point to keep in mind when using this approach to reloading is that reloads are only functional if the builder is used as central component for accessing configuration data. The configuration instance obtained from the builder will not change automagically! So if an application fetches a configuration object from the builder at startup and then uses it throughout its life time, changes on the external configuration file become never visible. The correct approach is to keep a reference to the builder centrally and obtain the configuration from there every time configuration data is needed.
use following code:
#Component
public class ApplicationProperties {
private PropertiesConfiguration configuration;
#PostConstruct
private void init() {
try {
String filePath = PropertiesConstants.PROPERTIES_FILE_PATH;
System.out.println("Loading the properties file: " + filePath);
configuration = new PropertiesConfiguration(filePath);
//Create new FileChangedReloadingStrategy to reload the properties file based on the given time interval
FileChangedReloadingStrategy fileChangedReloadingStrategy = new FileChangedReloadingStrategy();
fileChangedReloadingStrategy.setRefreshDelay(PropertiesConstants.REFRESH_DELAY);
configuration.setReloadingStrategy(fileChangedReloadingStrategy);
} catch (ConfigurationException e) {
e.printStackTrace();
}
}
public String getProperty(String key) {
return (String) configuration.getProperty(key);
}
public void setProperty(String key, Object value) {
configuration.setProperty(key, value);
}
public void save() {
try {
configuration.save();
} catch (ConfigurationException e) {
e.printStackTrace();
}
}
}

How to specify a button to open an URL?

I want to write a web application that triggers the default email client of the user to send an email.
Thus, I created a Link, that leads to an URL conforming to the mailto URI scheme (http://en.wikipedia.org/wiki/Mailto):
Link emailLink = new Link("Send Email",
new ExternalResource("mailto:someone#example.com"));
However, instead of using a Link, I want to provide a Button that allows to trigger the respective functionality. But, for buttons I cannot set an ExternalResource to be opened.
Does anybody know to solve this problem for Buttons, or how to create a Link that looks and behaves exactly like a button? I also tried some CCS modification but did not manage the task by myself. I also found some solutions for former Vaadin versions (https://vaadin.com/forum/#!/thread/69989), but, unfortunately they do not work for Vaadin 7.
I remember solving a similar problem using a ResourceReference.
Button emailButton = new Button("Email");
content.addComponent(emailButton);
Resource res = new ExternalResource("mailto:someone#example.com");
final ResourceReference rr = ResourceReference.create(res, content, "email");
emailButton.addClickListener(new Button.ClickListener() {
#Override
public void buttonClick(ClickEvent event) {
Page.getCurrent().open(rr.getURL(), null);
}
});
For solving similar issue, I applied previously:
String email="info#ORGNAME.org";
Link l=new Link();
l.setResource(new ExternalResource("mailto:" + email));
l.setCaption("Send email to " + email);
addComponent(l);
After some further tries a managed to adapt the proposed LinkButton solution from https://vaadin.com/forum/#!/thread/69989 for Vaadin 7:
public class LinkButton extends Button {
public LinkButton(final String url, String caption) {
super(caption);
setImmediate(true);
addClickListener(new Button.ClickListener() {
private static final long serialVersionUID = -2607584137357484607L;
#Override
public void buttonClick(ClickEvent event) {
LinkButton.this.getUI().getPage().open(url, "_blank");
}
});
}
}
However, this solution is still not perfect as it causes the opening of a popup window being blocked by some web browsers.

Resources