Windows Forms Webbrowswer control with IDownloadManager
Asked Answered
H

1

11

I'm using the Systems.Windows.Forms.Webbrowser control and need to override the download manager. I've followed the instructions here to subclass the form and override CreateWebBrowserSiteBase()

/// <summary>
/// Browser with download manager
/// </summary>
public class MyBrowser : WebBrowser  
{
    /// <summary>
    /// Returns a reference to the unmanaged WebBrowser ActiveX control site,
    /// which you can extend to customize the managed <see ref="T:System.Windows.Forms.WebBrowser"/> control.
    /// </summary>
    /// <returns>
    /// A <see cref="T:System.Windows.Forms.WebBrowser.WebBrowserSite"/> that represents the WebBrowser ActiveX control site.
    /// </returns>
    protected override WebBrowserSiteBase CreateWebBrowserSiteBase()
    {
        var manager = new DownloadWebBrowserSite(this);
        manager.FileDownloading += (sender, args) =>
            {
                if (FileDownloading != null)
                {
                    FileDownloading(this, args);
                }
            };
        return manager;
    }
}

In the DownloadWebBrowserSite, I implement IServiceProvider to provide an IDownloadManager when requested.

        /// <summary>
        /// Queries for a service
        /// </summary>
        /// <param name="guidService">the service GUID</param>
        /// <param name="riid"></param>
        /// <param name="ppvObject"></param>
        /// <returns></returns>
        public int QueryService(ref Guid guidService, ref Guid riid, out IntPtr ppvObject)
        {
            if ( (guidService == Constants.IID_IDownloadManager && riid == Constants.IID_IDownloadManager ))
            {
                ppvObject = Marshal.GetComInterfaceForObject(_manager, typeof(IDownloadManager));
                return Constants.S_OK;
            }
            ppvObject = IntPtr.Zero;
            return Constants.E_NOINTERFACE;
        }

The DownloadManager is taken from the example above.

/// <summary>
/// Intercepts downloads of files, to add as PDFs or suppliments
/// </summary>
[ComVisible(true)]
[Guid("bdb9c34c-d0ca-448e-b497-8de62e709744")]
[CLSCompliant(false)]
public class DownloadManager : IDownloadManager
{
    /// <summary>
    /// event called when the browser is about to download a file
    /// </summary>
    public event EventHandler<FileDownloadEventArgs> FileDownloading;

    /// <summary>
    /// Return S_OK (0) so that IE will stop to download the file itself. 
    /// Else the default download user interface is used.
    /// </summary>
    public int Download(IMoniker pmk, IBindCtx pbc, uint dwBindVerb, int grfBINDF, IntPtr pBindInfo,
                        string pszHeaders, string pszRedir, uint uiCP)
    {
        // Get the display name of the pointer to an IMoniker interface that specifies the object to be downloaded.
        string name;
        pmk.GetDisplayName(pbc, null, out name);
        if (!string.IsNullOrEmpty(name))
        {
            Uri url;
            if (Uri.TryCreate(name, UriKind.Absolute, out url))
            {
                if ( FileDownloading != null )
                {
                     FileDownloading(this, new FileDownloadEventArgs(url));
                }
                return Constants.S_OK;
            }
        }
        return 1;
    }
}

The problem is that the pmk.GetDisplayName returns the initial URL, not the URL of the item to be downloaded. If the URI points to a dynamic page, such as http://www.example.com/download.php, I'm not getting the actual file to be downloaded. I need to get the URL from the header so that I get the actual file I'm supposed to be downloading.

Google indicates that I have to create a IBindStatusCallback implementation that also implements IHttpNegotiate and IServiceProvider to respond to IID_IHttpNegotiate, so that I can see the IHttpNegotiate.OnResponse. I've managed to implement that, however, QueryService only seems to ever ask for IID_IInternetProtocol and never IID_IHttpNegotiate.

Any advice would be great.

Hurff answered 13/11, 2012 at 14:53 Comment(11)
Hi, you mention implementing IBindingStatusCallback - as far as I am aware the interface is called IBindStatusCallback - is that a typo? You haven't shown the implementation of IBindStatusCallback. MSDN mentions that Urlmon.dll will use IBindStatusCallback's QueryInterface implementation to get a pointer to your IHttpNegotiate interface. Have a look at msdn.microsoft.com/en-us/library/ms775054(v=vs.85).aspxPrecambrian
Would it be possible to put a compilable sample somewhere so we can test it?Duron
@SimonMourier I will look into creating the sample in the next day or two.Hurff
@Precambrian Yes, it should be IBindStatusCallback. I've edited the question. I have implemented what msdn.microsoft.com/en-us/library/ms775054(v=vs.85).aspx suggests. The issue is that the IBindStatusCallback's QueryService is never called with IID_IHttpNegotiate (so that I can return the IHttpNegotiate implementation). It's always called with IID_IInternetProtocol. I don't know what to return when QueryService asks for IID_IInternetProtocol.Hurff
@SimonMourier I've added a dropbox link for a demo sample application with a README.txt in the solution.Hurff
What do you need the URL for? (ie. What is your end goal?) sciencedirect.com/science is the correct URL where files on that page are downloaded from. The server reacts to values you POST to that page. Do you need to download the file?Sidedress
@Josh Yes. I am trying to intercept the download (i.e prevent the save this file dialog). If you navigate to that URL in Internet explorer and press the "Export" button, you will get a popup "Do you want to open or save 'science8a2fc2aa' from 'www.sciencedirect.com'?" (The exact name of the file will vary). I want to get that file and intercept it. I was under the impression that IHttpNegotiate would allow me to do that, but if you have another way to intercept the download, that would be much appreciated.Hurff
When Download is called, you should download the file (for example using WebClient.DownloadFile for GET operations) and return OK to the method so IE will not download it itself. What do you need more?Duron
@SimonMourier The problem is that the URL supplied to the DownloadManager.Download in the example is "sciencedirect.com/science", not the URL of the actual file. When I call WebClient.DownloadFile, I get the HTML source of that page, not the actual file I want to download.Hurff
@Josh Yes, I want to download the file in my application without the user placing it on the filesystem. For example, if the file is a .ris citation file (like in the sample code), I want to parse the file and use the information in my app, without the user having to tell me where they downloaded the file to.Hurff
That's not a problem then, just do something like WebClient.DownloadString("sciencedirect.com/science") or maybe a similar thing with a POST HttpWebRequest instead of GET. This is exactly what IE does, you can check this using a tool such as Fiddler.Duron
P
5

Your missing a call to: CreateBindCtx.

Add the following to your DownloadManager:

   [DllImport("ole32.dll")]
        static extern int CreateBindCtx(uint reserved, out IBindCtx ppbc);

Then make sure you call this before registering your callback. I have implemented this in your Download() method as follows:

public int Download(IMoniker pmk, IBindCtx pbc, uint dwBindVerb, int grfBINDF, IntPtr pBindInfo,
                            string pszHeaders, string pszRedir, uint uiCP)
        {
            // Get the display name of the pointer to an IMoniker interface that specifies the object to be downloaded.
            string name;
            pmk.GetDisplayName(pbc, null, out name);
            if (!string.IsNullOrEmpty(name))
            {
                Uri url;
                if (Uri.TryCreate(name, UriKind.Absolute, out url))
                {
                    Debug.WriteLine("DownloadManager: initial URL is: " + url);
                    CreateBindCtx(0, out pbc);
                    RegisterCallback(pbc, url);
                    BindMonikerToStream(pmk, pbc);

                    return MyBrowser.Constants.S_OK;
                }
            }
            return 1;
        }

With this in place, your IHttpNegotiate implementation will be called and you'll have access to the response headers.

Participate answered 22/11, 2012 at 18:17 Comment(5)
Glad to be of help! Does it do everything you were hoping it would? And does it satisfy all the conditions for the bounty?Participate
Yes, it does. I thought the bounty was automatically awarded. Sorry bout that. I now realise that I also have to get the HTTP response content in a similar way, but I think that is a separate question :)Hurff
I downloaded a document and it is not working for me. The events aren't hitting. I ran the example from dl.dropbox.com/u/58016866/Working_WebBrowserIBindDemo.zip. I have IE 10 on my box and my box is 64 bit.Gilles
Hi Sam, the download link is broken, I'll remove it to prevent future NAA's, maybe you want to add a new one.Committeewoman
Oh, to dream that this example is still out there in working order.Jazmin

© 2022 - 2024 — McMap. All rights reserved.