Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Windows Forms Webbrowswer control with IDownloadManager

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.

like image 830
jimbojones Avatar asked Nov 13 '12 14:11

jimbojones


1 Answers

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.

like image 148
Sam Shiles Avatar answered Oct 16 '22 14:10

Sam Shiles