0

I have webapp in Vaadin Framework 8. I have Windows GUI app in C#. The gui app is using WebBrowser component to display webapp. WebBrowser component is internally using IE11 core through ActiveX. I can successfully load and display the webapp in the gui app browser component.

I need to pass data from webapp to the gui app. The webapp has many rows loaded on server side, only few are displayed in grid. I want to pass all data from webapp to gui app in some format (csv or json).

I have tryed some approaches, but I wasn't successfull.

[Approach 1]

Webapp: attach downloadable resource (csv) to Link with predefined id using FileDownloader. Download by user mouse click works fine, file save dialog pops up and data are downloaded successfully.

Link link = new Link("Data");
link.setId("myId");
StreamResource resource = getMyResource(data);
FileDownloader downloader = new FileDownloader(resource);
downloader.extend(link);

Page.getCurrent().getJavaScript().addFunction("test", new JavaScriptFunction() {
     @Override
     public void call(JsonArray arguments) {
         Page.getCurrent().getJavaScript()
             .execute("document.getElementById('myId').click()");
    }
});

Gui app: raise onClick event on link and capture WebBrowser.FileDownload event, capture WebBrowser.Navigate event. I have failed to raise onClick event from C# using:

HtmlElement el = webBrowser.Document.GetElementById("myId");
el.RaiseEvent("onClick");
el.InvokeMember("click");
webBrowser.Document.InvokeScript("document.getElementById('myId').click();", null);
webBrowser.Document.InvokeScript("test", null);

Result: WebBrowser.FileDownload event doesn't work (is fired but can't capture url nor data), capture WebBrowser.Navigate event works partialy (can see resource url, but can't download data using byte[] b = new WebClient().DownloadData(e.Url);).

[Approach 2]

Similar to approach 1. I tryed to get resource url, put the direct url to Link and download the resource in c# using direct link. I can construct the same resource url as is used by browser to download data when user clicks the link.

Extended file downloader that keeps resource, key and connector:

public class ExtendedFileDownloader extends FileDownloader {
    private String myKey;
    private Resource myResource;
    private ClientConnector myConnector;

    public ExtendedFileDownloader(StreamResource resource, ClientConnector connector) {
        super(resource);
        myConnector = connector;
    }

    @Override
    protected void setResource(String key, Resource resource) {
        super.setResource(key, resource);
        myKey = key;
        myResource = resource;
    }

    public String getResourceUrl() {
        ResourceReference ref =
            ResourceReference.create(
                myResource,
                (myConnector != null) ? myConnector : this,
                myKey);
        String url = ref.getURL();
        return url;
    }
}

In view:

// fix app://path... urls to /<base-path>/path urls
private String fixResourceReferenceUrl(String resourceReferenceUrl) {
    String resourceReferencePath = resourceReferenceUrl.replace("app://", "");
    String uiBaseUrl = ui.getUiRootPath();
    String fixedUrl = uiBaseUrl + "/" + resourceReferencePath;
    return fixedUrl;
}

Link link2 = new Link("Data2");
link2.setId("myId2");
StreamResource resource = getMyResource(data);
ExtendedFileDownloader downloader = new ExtendedFileDownloader(resource, this);
String fixedResourceUrl = fixResourceReferenceUrl(downloader.getResourceUrl());
link2.setResource(new ExternalResource(fixedResourceUrl));    

Result: The data cannot be downloaded using this link, server error 410 or NotFound errors.

Any Ideas ? Any other approaches to try ?

blacksun
  • 63
  • 6
  • Would it be possible for your webapp and GUI app to use the same database or backend service for sharing the data? – Tatu Lund Sep 21 '18 at 16:48
  • Yes, common backend service can help, but I would like to find some direct method. Do you think it would be possible to download data using direct resource link (approach 2) when I use all cookies from gui app `Webbrowser` component in a `WebClient` component (or another downloader) ? – blacksun Sep 22 '18 at 17:37

1 Answers1

0

I have finally solved the problem. The solution is very close to approach 2. The resource url is passed in element with custom attribute. C# WebClient needs to set cookies from WebBrowser and Referer HTTP headers. The data can be successfully downloaded by C# app.

Element attribute in vaadin webapp can be set using Vaadin-addon Attributes.

Cookies in C# app can be retrieved using this solution.

// Fix resource urls begining with app://
public String fixResourceReferenceUrl(String resourceReferenceUrl) {
    try {
        String uiRootPath = UI.getCurrent().getUiRootPath();
        URI location = Page.getCurrent().getLocation();
        String appLocation = new URIBuilder()
                .setScheme(location.getScheme())
                .setHost(location.getHost())
                .setPort(location.getPort())
                .setPath(uiRootPath)
                .build()
                .toString();
        String resourceReferencePath = resourceReferenceUrl.replace("app://", "");
        String fixedUrl = appLocation + "/" + resourceReferencePath;
        return fixedUrl;
    }
    catch (Exception e) {
        return null;
    }
}

In view (using ExtendedFileDownloader from above):

Link link = new Link("Data");
link.setId("myId");
StreamResource resource = getMyResource(data);
ExtendedFileDownloader downloader = new ExtendedFileDownloader(resource);
downloader.extend(link);
Attribute attr = new Attribute("x-my-data", fixResourceReferenceUrl(downloader.getResourceUrl()));
attr.extend(link);
link.setVisible(true);

In C# app:

[DllImport("wininet.dll", SetLastError = true)]
public static extern bool InternetGetCookieEx(
    string url,
    string cookieName,
    StringBuilder cookieData,
    ref int size,
    Int32 dwFlags,
    IntPtr lpReserved);

private const Int32 InternetCookieHttponly = 0x2000;

public static String GetUriCookies(String uri)
{
    // Determine the size of the cookie
    int datasize = 8192 * 16;
    StringBuilder cookieData = new StringBuilder(datasize);
    if (!InternetGetCookieEx(uri, null, cookieData, ref datasize, InternetCookieHttponly, IntPtr.Zero))
    {
        if (datasize < 0)
            return null;
        // Allocate stringbuilder large enough to hold the cookie
        cookieData = new StringBuilder(datasize);
        if (!InternetGetCookieEx(
            uri,
            null, cookieData,
            ref datasize,
            InternetCookieHttponly,
            IntPtr.Zero))
            return null;
    }
    return cookieData.ToString();
}

private void button_Click(object sender, EventArgs e)
{
    HtmlElement el = webBrowser.Document.GetElementById("myId");
    String url = el.GetAttribute("x-my-data");
    String cookies = GetUriCookies(url);
    WebClient wc = new WebClient();
    wc.Headers.Add("Cookie", cookies);
    wc.Headers.Add("Referer", WEB_APP_URL); // url of webapp base path, http://myhost/MyUI
    byte[] data = wc.DownloadData(url);
}
blacksun
  • 63
  • 6