WebBrowser控件缓存问题

30

我正在使用Windows Form中的WebBrowser控件来显示PDF文档。

然而,每当PDF文档被重新生成时,WebBrowser控件仅显示其本地缓存版本,而不是来自服务器的更新版本。

我正在尝试使用下面展示的Refresh()方法来强制控件重新加载PDF文档,但它没有起作用:

_webBrowser.Navigate(pdfUrl);

_webBrowser.Refresh(WebBrowserRefreshOption.Completely)

我是否需要做其他任何事情来强制刷新从服务器重新加载PDF?


如果您在IE(应用程序之外)中加载相同的URL,它是否表现出相同的行为? - Jerry Bullard
11个回答

34

关于Serj Sagan所提到的问题,以下是简化后的代码,将奇怪的错误处理方式删除,移除了无限循环,并且支持32/64位系统。

/**
* Modified from code originally found here: http://support.microsoft.com/kb/326201
**/
public class WebBrowserHelper
{
    #region Definitions/DLL Imports
    /// <summary>
    /// For PInvoke: Contains information about an entry in the Internet cache
    /// </summary>
    [StructLayout(LayoutKind.Explicit)]
    public struct ExemptDeltaOrReserverd
    {
        [FieldOffset(0)]
        public UInt32 dwReserved;
        [FieldOffset(0)]
        public UInt32 dwExemptDelta;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct INTERNET_CACHE_ENTRY_INFOA
    {
        public UInt32 dwStructSize;
        public IntPtr lpszSourceUrlName;
        public IntPtr lpszLocalFileName;
        public UInt32 CacheEntryType;
        public UInt32 dwUseCount;
        public UInt32 dwHitRate;
        public UInt32 dwSizeLow;
        public UInt32 dwSizeHigh;
        public FILETIME LastModifiedTime;
        public FILETIME ExpireTime;
        public FILETIME LastAccessTime;
        public FILETIME LastSyncTime;
        public IntPtr lpHeaderInfo;
        public UInt32 dwHeaderInfoSize;
        public IntPtr lpszFileExtension;
        public ExemptDeltaOrReserverd dwExemptDeltaOrReserved;
    }

    // For PInvoke: Initiates the enumeration of the cache groups in the Internet cache
    [DllImport(@"wininet",
        SetLastError = true,
        CharSet = CharSet.Auto,
        EntryPoint = "FindFirstUrlCacheGroup",
        CallingConvention = CallingConvention.StdCall)]
    public static extern IntPtr FindFirstUrlCacheGroup(
        int dwFlags,
        int dwFilter,
        IntPtr lpSearchCondition,
    int dwSearchCondition,
    ref long lpGroupId,
    IntPtr lpReserved);

    // For PInvoke: Retrieves the next cache group in a cache group enumeration
    [DllImport(@"wininet",
    SetLastError = true,
        CharSet = CharSet.Auto,
    EntryPoint = "FindNextUrlCacheGroup",
        CallingConvention = CallingConvention.StdCall)]
    public static extern bool FindNextUrlCacheGroup(
        IntPtr hFind,
        ref long lpGroupId,
        IntPtr lpReserved);

    // For PInvoke: Releases the specified GROUPID and any associated state in the cache index file
    [DllImport(@"wininet",
        SetLastError = true,
        CharSet = CharSet.Auto,
        EntryPoint = "DeleteUrlCacheGroup",
        CallingConvention = CallingConvention.StdCall)]
    public static extern bool DeleteUrlCacheGroup(
        long GroupId,
        int dwFlags,
        IntPtr lpReserved);

    // For PInvoke: Begins the enumeration of the Internet cache
    [DllImport(@"wininet",
        SetLastError = true,
        CharSet = CharSet.Auto,
        EntryPoint = "FindFirstUrlCacheEntryA",
        CallingConvention = CallingConvention.StdCall)]
    public static extern IntPtr FindFirstUrlCacheEntry(
        [MarshalAs(UnmanagedType.LPTStr)] string lpszUrlSearchPattern,
        IntPtr lpFirstCacheEntryInfo,
        ref int lpdwFirstCacheEntryInfoBufferSize);

    // For PInvoke: Retrieves the next entry in the Internet cache
    [DllImport(@"wininet",
        SetLastError = true,
        CharSet = CharSet.Auto,
        EntryPoint = "FindNextUrlCacheEntryA",
        CallingConvention = CallingConvention.StdCall)]
    public static extern bool FindNextUrlCacheEntry(
        IntPtr hFind,
        IntPtr lpNextCacheEntryInfo,
        ref int lpdwNextCacheEntryInfoBufferSize);

    // For PInvoke: Removes the file that is associated with the source name from the cache, if the file exists
    [DllImport(@"wininet",
        SetLastError = true,
        CharSet = CharSet.Auto,
        EntryPoint = "DeleteUrlCacheEntryA",
        CallingConvention = CallingConvention.StdCall)]
    public static extern bool DeleteUrlCacheEntry(
        IntPtr lpszUrlName);
    #endregion

    /// <summary>
    /// Clears the cache of the web browser
    /// </summary>
    public static void ClearCache()
    {
        // Indicates that all of the cache groups in the user's system should be enumerated
        const int CACHEGROUP_SEARCH_ALL = 0x0;
        // Indicates that all the cache entries that are associated with the cache group
        // should be deleted, unless the entry belongs to another cache group.
        const int CACHEGROUP_FLAG_FLUSHURL_ONDELETE = 0x2;
        const int ERROR_INSUFFICIENT_BUFFER = 0x7A;

        // Delete the groups first.
        // Groups may not always exist on the system.
        // For more information, visit the following Microsoft Web site:
        // http://msdn.microsoft.com/library/?url=/workshop/networking/wininet/overview/cache.asp            
        // By default, a URL does not belong to any group. Therefore, that cache may become
        // empty even when the CacheGroup APIs are not used because the existing URL does not belong to any group.            
        long groupId = 0;
        IntPtr enumHandle = FindFirstUrlCacheGroup(0, CACHEGROUP_SEARCH_ALL, IntPtr.Zero, 0, ref groupId, IntPtr.Zero);
        if (enumHandle != IntPtr.Zero) {
            bool more;
            do {
                // Delete a particular Cache Group.
                DeleteUrlCacheGroup(groupId, CACHEGROUP_FLAG_FLUSHURL_ONDELETE, IntPtr.Zero);
                more = FindNextUrlCacheGroup(enumHandle, ref groupId, IntPtr.Zero);
            } while (more);
        }

        // Start to delete URLs that do not belong to any group.
        int cacheEntryInfoBufferSizeInitial = 0;
        FindFirstUrlCacheEntry(null, IntPtr.Zero, ref cacheEntryInfoBufferSizeInitial);  // should always fail because buffer is too small
        if (Marshal.GetLastWin32Error() == ERROR_INSUFFICIENT_BUFFER) {
            int cacheEntryInfoBufferSize = cacheEntryInfoBufferSizeInitial;
            IntPtr cacheEntryInfoBuffer = Marshal.AllocHGlobal(cacheEntryInfoBufferSize);
            enumHandle = FindFirstUrlCacheEntry(null, cacheEntryInfoBuffer, ref cacheEntryInfoBufferSizeInitial);
            if (enumHandle != IntPtr.Zero) {
                bool more;
                do {
                    INTERNET_CACHE_ENTRY_INFOA internetCacheEntry = (INTERNET_CACHE_ENTRY_INFOA)Marshal.PtrToStructure(cacheEntryInfoBuffer, typeof(INTERNET_CACHE_ENTRY_INFOA));
                    cacheEntryInfoBufferSizeInitial = cacheEntryInfoBufferSize;
                    DeleteUrlCacheEntry(internetCacheEntry.lpszSourceUrlName);
                    more = FindNextUrlCacheEntry(enumHandle, cacheEntryInfoBuffer, ref cacheEntryInfoBufferSizeInitial);
                    if (!more && Marshal.GetLastWin32Error() == ERROR_INSUFFICIENT_BUFFER) {
                        cacheEntryInfoBufferSize = cacheEntryInfoBufferSizeInitial;
                        cacheEntryInfoBuffer = Marshal.ReAllocHGlobal(cacheEntryInfoBuffer, (IntPtr)cacheEntryInfoBufferSize);
                        more = FindNextUrlCacheEntry(enumHandle, cacheEntryInfoBuffer, ref cacheEntryInfoBufferSizeInitial);
                    }
                } while (more);
            }
            Marshal.FreeHGlobal(cacheEntryInfoBuffer);
        }
    }
}

非常感谢你的版本!你能简短地解释一下问题是什么,为什么它不能在64位架构上运行,以及是什么导致了无限循环吗? - メロディア
@melodia,请参考Sandra Walters上面的无限循环解释。我认为64位问题与显式FieldOffset属性有关。它们并不是必需的,并且会破坏IntPtr字段,在64位情况下需要8个字节。 - Oliver Bock
1
我遇到了同样的问题,我尝试了你的答案,只有在重新启动应用程序时才能正常工作。我正在使用 Office365 登录。我需要做的是停止在我的应用程序中缓存 office365。你有什么想法或建议来解决这个问题吗? - user2749605
@OliverBock 由于某种原因,它仅删除了少量条目,并将大部分内容留在“临时互联网文件\低\内容”子文件夹中。 - Miha Markic
@MihaMarkic,我能提供的建议就是你要跟踪它并尝试找出问题所在。 - Oliver Bock
我知道克里斯·克拉克的版本在这里被标记为答案,但是这个版本可能应该成为答案,因为它在64位Windows上崩溃率降低了100%。 - karfus

28

Sheng Jiang是正确的 - 你需要以编程方式清除IE的缓存。下面是一个示例代码,展示如何在C#中实现这个功能:

http://www.gutgames.com/post/Clearing-the-Cache-of-a-WebBrowser-Control.aspx

如果该页面无法访问,可以参考以下内容:

/**
* Modified from code originally found here: 
http://support.microsoft.com/kb/326201
**/

#region Usings
using System;
using System.Runtime.InteropServices;

#endregion

namespace Utilities.Web.WebBrowserHelper
{
    /// <summary>
    /// Class for clearing the cache
    /// </summary>
    public static class WebBrowserHelper
    {
        #region Definitions/DLL Imports
        /// <summary>
        /// For PInvoke: Contains information about an entry in the Internet cache
        /// </summary>
        [StructLayout(LayoutKind.Explicit, Size = 80)]
        public struct INTERNET_CACHE_ENTRY_INFOA
        {
            [FieldOffset(0)]
            public uint dwStructSize;
            [FieldOffset(4)]
            public IntPtr lpszSourceUrlName;
            [FieldOffset(8)]
            public IntPtr lpszLocalFileName;
            [FieldOffset(12)]
            public uint CacheEntryType;
            [FieldOffset(16)]
            public uint dwUseCount;
            [FieldOffset(20)]
            public uint dwHitRate;
            [FieldOffset(24)]
            public uint dwSizeLow;
            [FieldOffset(28)]
            public uint dwSizeHigh;
            [FieldOffset(32)]
            public System.Runtime.InteropServices.ComTypes.FILETIME LastModifiedTime;
            [FieldOffset(40)]
            public System.Runtime.InteropServices.ComTypes.FILETIME ExpireTime;
            [FieldOffset(48)]
            public System.Runtime.InteropServices.ComTypes.FILETIME LastAccessTime;
            [FieldOffset(56)]
            public System.Runtime.InteropServices.ComTypes.FILETIME LastSyncTime;
            [FieldOffset(64)]
            public IntPtr lpHeaderInfo;
            [FieldOffset(68)]
            public uint dwHeaderInfoSize;
            [FieldOffset(72)]
            public IntPtr lpszFileExtension;
            [FieldOffset(76)]
            public uint dwReserved;
            [FieldOffset(76)]
            public uint dwExemptDelta;
        }

        // For PInvoke: Initiates the enumeration of the cache groups in the Internet cache
        [DllImport(@"wininet",
        SetLastError = true,
        CharSet = CharSet.Auto,
        EntryPoint = "FindFirstUrlCacheGroup",
        CallingConvention = CallingConvention.StdCall)]
        public static extern IntPtr FindFirstUrlCacheGroup(
        int dwFlags,
        int dwFilter,
        IntPtr lpSearchCondition,
        int dwSearchCondition,
        ref long lpGroupId,
        IntPtr lpReserved);

        // For PInvoke: Retrieves the next cache group in a cache group enumeration
        [DllImport(@"wininet",
        SetLastError = true,
        CharSet = CharSet.Auto,
        EntryPoint = "FindNextUrlCacheGroup",
        CallingConvention = CallingConvention.StdCall)]
        public static extern bool FindNextUrlCacheGroup(
        IntPtr hFind,
        ref long lpGroupId,
        IntPtr lpReserved);

        // For PInvoke: Releases the specified GROUPID and any associated state in the cache index file
        [DllImport(@"wininet",
        SetLastError = true,
        CharSet = CharSet.Auto,
        EntryPoint = "DeleteUrlCacheGroup",
        CallingConvention = CallingConvention.StdCall)]
        public static extern bool DeleteUrlCacheGroup(
        long GroupId,
        int dwFlags,
        IntPtr lpReserved);

        // For PInvoke: Begins the enumeration of the Internet cache
        [DllImport(@"wininet",
        SetLastError = true,
        CharSet = CharSet.Auto,
        EntryPoint = "FindFirstUrlCacheEntryA",
        CallingConvention = CallingConvention.StdCall)]
        public static extern IntPtr FindFirstUrlCacheEntry(
        [MarshalAs(UnmanagedType.LPTStr)] string lpszUrlSearchPattern,
        IntPtr lpFirstCacheEntryInfo,
        ref int lpdwFirstCacheEntryInfoBufferSize);

        // For PInvoke: Retrieves the next entry in the Internet cache
        [DllImport(@"wininet",
        SetLastError = true,
        CharSet = CharSet.Auto,
        EntryPoint = "FindNextUrlCacheEntryA",
        CallingConvention = CallingConvention.StdCall)]
        public static extern bool FindNextUrlCacheEntry(
        IntPtr hFind,
        IntPtr lpNextCacheEntryInfo,
        ref int lpdwNextCacheEntryInfoBufferSize);

        // For PInvoke: Removes the file that is associated with the source name from the cache, if the file exists
        [DllImport(@"wininet",
        SetLastError = true,
        CharSet = CharSet.Auto,
        EntryPoint = "DeleteUrlCacheEntryA",
        CallingConvention = CallingConvention.StdCall)]
        public static extern bool DeleteUrlCacheEntry(
        IntPtr lpszUrlName);
        #endregion

        #region Public Static Functions

        /// <summary>
        /// Clears the cache of the web browser
        /// </summary>
        public static void ClearCache()
        {
            // Indicates that all of the cache groups in the user's system should be enumerated
            const int CACHEGROUP_SEARCH_ALL = 0x0;
            // Indicates that all the cache entries that are associated with the cache group
            // should be deleted, unless the entry belongs to another cache group.
            const int CACHEGROUP_FLAG_FLUSHURL_ONDELETE = 0x2;
            // File not found.
            const int ERROR_FILE_NOT_FOUND = 0x2;
            // No more items have been found.
            const int ERROR_NO_MORE_ITEMS = 259;
            // Pointer to a GROUPID variable
            long groupId = 0;

            // Local variables
            int cacheEntryInfoBufferSizeInitial = 0;
            int cacheEntryInfoBufferSize = 0;
            IntPtr cacheEntryInfoBuffer = IntPtr.Zero;
            INTERNET_CACHE_ENTRY_INFOA internetCacheEntry;
            IntPtr enumHandle = IntPtr.Zero;
            bool returnValue = false;

            // Delete the groups first.
            // Groups may not always exist on the system.
            // For more information, visit the following Microsoft Web site:
            // http://msdn.microsoft.com/library/?url=/workshop/networking/wininet/overview/cache.asp            
            // By default, a URL does not belong to any group. Therefore, that cache may become
            // empty even when the CacheGroup APIs are not used because the existing URL does not belong to any group.            
            enumHandle = FindFirstUrlCacheGroup(0, CACHEGROUP_SEARCH_ALL, IntPtr.Zero, 0, ref groupId, IntPtr.Zero);
            // If there are no items in the Cache, you are finished.
            if (enumHandle != IntPtr.Zero && ERROR_NO_MORE_ITEMS == Marshal.GetLastWin32Error())
                return;

            // Loop through Cache Group, and then delete entries.
            while (true)
            {
                if (ERROR_NO_MORE_ITEMS == Marshal.GetLastWin32Error() || ERROR_FILE_NOT_FOUND == Marshal.GetLastWin32Error()) { break; }
                // Delete a particular Cache Group.
                returnValue = DeleteUrlCacheGroup(groupId, CACHEGROUP_FLAG_FLUSHURL_ONDELETE, IntPtr.Zero);
                if (!returnValue && ERROR_FILE_NOT_FOUND == Marshal.GetLastWin32Error())
                {
                    returnValue = FindNextUrlCacheGroup(enumHandle, ref groupId, IntPtr.Zero);
                }

                if (!returnValue && (ERROR_NO_MORE_ITEMS == Marshal.GetLastWin32Error() || ERROR_FILE_NOT_FOUND == Marshal.GetLastWin32Error()))
                    break;
            }

            // Start to delete URLs that do not belong to any group.
            enumHandle = FindFirstUrlCacheEntry(null, IntPtr.Zero, ref cacheEntryInfoBufferSizeInitial);
            if (enumHandle != IntPtr.Zero && ERROR_NO_MORE_ITEMS == Marshal.GetLastWin32Error())
                return;

            cacheEntryInfoBufferSize = cacheEntryInfoBufferSizeInitial;
            cacheEntryInfoBuffer = Marshal.AllocHGlobal(cacheEntryInfoBufferSize);
            enumHandle = FindFirstUrlCacheEntry(null, cacheEntryInfoBuffer, ref cacheEntryInfoBufferSizeInitial);

            while (true)
            {
                internetCacheEntry = (INTERNET_CACHE_ENTRY_INFOA)Marshal.PtrToStructure(cacheEntryInfoBuffer, typeof(INTERNET_CACHE_ENTRY_INFOA));
                if (ERROR_NO_MORE_ITEMS == Marshal.GetLastWin32Error()) { break; }

                cacheEntryInfoBufferSizeInitial = cacheEntryInfoBufferSize;
                returnValue = DeleteUrlCacheEntry(internetCacheEntry.lpszSourceUrlName);
                if (!returnValue)
                {
                    returnValue = FindNextUrlCacheEntry(enumHandle, cacheEntryInfoBuffer, ref cacheEntryInfoBufferSizeInitial);
                }
                if (!returnValue && ERROR_NO_MORE_ITEMS == Marshal.GetLastWin32Error())
                {
                    break;
                }
                if (!returnValue && cacheEntryInfoBufferSizeInitial > cacheEntryInfoBufferSize)
            {
                    cacheEntryInfoBufferSize = cacheEntryInfoBufferSizeInitial;
                    cacheEntryInfoBuffer = Marshal.ReAllocHGlobal(cacheEntryInfoBuffer, (IntPtr)cacheEntryInfoBufferSize);
                    returnValue = FindNextUrlCacheEntry(enumHandle, cacheEntryInfoBuffer, ref cacheEntryInfoBufferSizeInitial);
                }
            }
            Marshal.FreeHGlobal(cacheEntryInfoBuffer);
        }
        #endregion
    }
}

这是基于微软KB文章的大量工作: http://support.microsoft.com/kb/326201

提前回答问题 - 是的,这很麻烦,没有其他解决方法。祝好运!


12
这是对Chris Clark答案的扩展...这段代码非常重要,我认为将其存放在Stack Overflow更加安全,同时我也去掉了行号。以下是链接到原始文章:http://www.gutgames.com/post/Clearing-the-Cache-of-a-WebBrowser-Control.aspx

代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;

namespace Utilities.Web.WebBrowserHelper
{
    /**
    * Modified from code originally found here: http://support.microsoft.com/kb/326201
    **/
    public class WebBrowserHelper
    {
        #region Definitions/DLL Imports
        /// <summary>
        /// For PInvoke: Contains information about an entry in the Internet cache
        /// </summary>
        [StructLayout(LayoutKind.Explicit, Size = 80)]
        public struct INTERNET_CACHE_ENTRY_INFOA
        {
            [FieldOffset(0)]
            public uint dwStructSize;
            [FieldOffset(4)]
            public IntPtr lpszSourceUrlName;
            [FieldOffset(8)]
            public IntPtr lpszLocalFileName;
            [FieldOffset(12)]
            public uint CacheEntryType;
            [FieldOffset(16)]
            public uint dwUseCount;
            [FieldOffset(20)]
            public uint dwHitRate;
            [FieldOffset(24)]
            public uint dwSizeLow;
            [FieldOffset(28)]
            public uint dwSizeHigh;
            [FieldOffset(32)]
            public System.Runtime.InteropServices.ComTypes.FILETIME LastModifiedTime;
            [FieldOffset(40)]
            public System.Runtime.InteropServices.ComTypes.FILETIME ExpireTime;
            [FieldOffset(48)]
            public System.Runtime.InteropServices.ComTypes.FILETIME LastAccessTime;
            [FieldOffset(56)]
            public System.Runtime.InteropServices.ComTypes.FILETIME LastSyncTime;
            [FieldOffset(64)]
            public IntPtr lpHeaderInfo;
            [FieldOffset(68)]
            public uint dwHeaderInfoSize;
            [FieldOffset(72)]
            public IntPtr lpszFileExtension;
            [FieldOffset(76)]
            public uint dwReserved;
            [FieldOffset(76)]
            public uint dwExemptDelta;
        }

        // For PInvoke: Initiates the enumeration of the cache groups in the Internet cache
        [DllImport(@"wininet",
            SetLastError = true,
            CharSet = CharSet.Auto,
            EntryPoint = "FindFirstUrlCacheGroup",
            CallingConvention = CallingConvention.StdCall)]
        public static extern IntPtr FindFirstUrlCacheGroup(
            int dwFlags,
            int dwFilter,
            IntPtr lpSearchCondition,
        int dwSearchCondition,
        ref long lpGroupId,
        IntPtr lpReserved);

        // For PInvoke: Retrieves the next cache group in a cache group enumeration
        [DllImport(@"wininet",
        SetLastError = true,
            CharSet = CharSet.Auto,
        EntryPoint = "FindNextUrlCacheGroup",
            CallingConvention = CallingConvention.StdCall)]
        public static extern bool FindNextUrlCacheGroup(
            IntPtr hFind,
            ref long lpGroupId,
            IntPtr lpReserved);

        // For PInvoke: Releases the specified GROUPID and any associated state in the cache index file
        [DllImport(@"wininet",
            SetLastError = true,
            CharSet = CharSet.Auto,
            EntryPoint = "DeleteUrlCacheGroup",
            CallingConvention = CallingConvention.StdCall)]
        public static extern bool DeleteUrlCacheGroup(
            long GroupId,
            int dwFlags,
            IntPtr lpReserved);

        // For PInvoke: Begins the enumeration of the Internet cache
        [DllImport(@"wininet",
            SetLastError = true,
            CharSet = CharSet.Auto,
            EntryPoint = "FindFirstUrlCacheEntryA",
            CallingConvention = CallingConvention.StdCall)]
        public static extern IntPtr FindFirstUrlCacheEntry(
            [MarshalAs(UnmanagedType.LPTStr)] string lpszUrlSearchPattern,
            IntPtr lpFirstCacheEntryInfo,
            ref int lpdwFirstCacheEntryInfoBufferSize);

        // For PInvoke: Retrieves the next entry in the Internet cache
        [DllImport(@"wininet",
            SetLastError = true,
            CharSet = CharSet.Auto,
            EntryPoint = "FindNextUrlCacheEntryA",
            CallingConvention = CallingConvention.StdCall)]
        public static extern bool FindNextUrlCacheEntry(
            IntPtr hFind,
            IntPtr lpNextCacheEntryInfo,
            ref int lpdwNextCacheEntryInfoBufferSize);

        // For PInvoke: Removes the file that is associated with the source name from the cache, if the file exists
        [DllImport(@"wininet",
            SetLastError = true,
            CharSet = CharSet.Auto,
            EntryPoint = "DeleteUrlCacheEntryA",
            CallingConvention = CallingConvention.StdCall)]
        public static extern bool DeleteUrlCacheEntry(
            IntPtr lpszUrlName);
        #endregion

        #region Public Static Functions

        /// <summary>
        /// Clears the cache of the web browser
        /// </summary>
        public static void ClearCache()
        {
            // Indicates that all of the cache groups in the user's system should be enumerated
            const int CACHEGROUP_SEARCH_ALL = 0x0;
            // Indicates that all the cache entries that are associated with the cache group
            // should be deleted, unless the entry belongs to another cache group.
            const int CACHEGROUP_FLAG_FLUSHURL_ONDELETE = 0x2;
            // File not found.
            const int ERROR_FILE_NOT_FOUND = 0x2;
            // No more items have been found.
            const int ERROR_NO_MORE_ITEMS = 259;
            // Pointer to a GROUPID variable
            long groupId = 0;

            // Local variables
            int cacheEntryInfoBufferSizeInitial = 0;
            int cacheEntryInfoBufferSize = 0;
            IntPtr cacheEntryInfoBuffer = IntPtr.Zero;
            INTERNET_CACHE_ENTRY_INFOA internetCacheEntry;
            IntPtr enumHandle = IntPtr.Zero;
            bool returnValue = false;

            // Delete the groups first.
            // Groups may not always exist on the system.
            // For more information, visit the following Microsoft Web site:
            // http://msdn.microsoft.com/library/?url=/workshop/networking/wininet/overview/cache.asp            
            // By default, a URL does not belong to any group. Therefore, that cache may become
            // empty even when the CacheGroup APIs are not used because the existing URL does not belong to any group.            
            enumHandle = FindFirstUrlCacheGroup(0, CACHEGROUP_SEARCH_ALL, IntPtr.Zero, 0, ref groupId, IntPtr.Zero);
            // If there are no items in the Cache, you are finished.
            if (enumHandle != IntPtr.Zero && ERROR_NO_MORE_ITEMS == Marshal.GetLastWin32Error())
                return;

            // Loop through Cache Group, and then delete entries.
            while (true)
            {
                if (ERROR_NO_MORE_ITEMS == Marshal.GetLastWin32Error() || ERROR_FILE_NOT_FOUND == Marshal.GetLastWin32Error()) { break; }
                // Delete a particular Cache Group.
                returnValue = DeleteUrlCacheGroup(groupId, CACHEGROUP_FLAG_FLUSHURL_ONDELETE, IntPtr.Zero);
                if (!returnValue && ERROR_FILE_NOT_FOUND == Marshal.GetLastWin32Error())
                {
                    returnValue = FindNextUrlCacheGroup(enumHandle, ref groupId, IntPtr.Zero);
                }

                if (!returnValue && (ERROR_NO_MORE_ITEMS == Marshal.GetLastWin32Error() || ERROR_FILE_NOT_FOUND == Marshal.GetLastWin32Error()))
                    break;
            }

            // Start to delete URLs that do not belong to any group.
            enumHandle = FindFirstUrlCacheEntry(null, IntPtr.Zero, ref cacheEntryInfoBufferSizeInitial);
            if (enumHandle != IntPtr.Zero && ERROR_NO_MORE_ITEMS == Marshal.GetLastWin32Error())
                return;

            cacheEntryInfoBufferSize = cacheEntryInfoBufferSizeInitial;
            cacheEntryInfoBuffer = Marshal.AllocHGlobal(cacheEntryInfoBufferSize);
            enumHandle = FindFirstUrlCacheEntry(null, cacheEntryInfoBuffer, ref cacheEntryInfoBufferSizeInitial);

            while (true)
            {
                internetCacheEntry = (INTERNET_CACHE_ENTRY_INFOA)Marshal.PtrToStructure(cacheEntryInfoBuffer, typeof(INTERNET_CACHE_ENTRY_INFOA));
                if (ERROR_NO_MORE_ITEMS == Marshal.GetLastWin32Error()) { break; }

                cacheEntryInfoBufferSizeInitial = cacheEntryInfoBufferSize;
                returnValue = DeleteUrlCacheEntry(internetCacheEntry.lpszSourceUrlName);
                if (!returnValue)
                {
                    returnValue = FindNextUrlCacheEntry(enumHandle, cacheEntryInfoBuffer, ref cacheEntryInfoBufferSizeInitial);
                }
                if (!returnValue && ERROR_NO_MORE_ITEMS == Marshal.GetLastWin32Error())
                {
                    break;
                }
                if (!returnValue && cacheEntryInfoBufferSizeInitial > cacheEntryInfoBufferSize)
                {
                    cacheEntryInfoBufferSize = cacheEntryInfoBufferSizeInitial;
                    cacheEntryInfoBuffer = Marshal.ReAllocHGlobal(cacheEntryInfoBuffer, (IntPtr)cacheEntryInfoBufferSize);
                    returnValue = FindNextUrlCacheEntry(enumHandle, cacheEntryInfoBuffer, ref cacheEntryInfoBufferSizeInitial);
                }
            }
            Marshal.FreeHGlobal(cacheEntryInfoBuffer);
        }
        #endregion
    }
}

5
在ClearCache()函数中,第一个'while(true)'循环似乎会陷入无限循环,如果没有错误条件的话; returnValue为true,GetLastWin32Error返回0,因此下一个URL缓存组永远不会被检索。我理解DeleteUrlCacheGroup()的行为过去可能与现在不同。 - SWalters
1
也不支持64位。请查看下面的答案以获取已更正的版本。 - Oliver Bock
不,真正的循环并不是无限的...只有在出现错误时才会循环,这一点在break之前的if语句条件中清楚地看到:ERROR_NO_MORE_ITEMS == Marshal.GetLastWin32Error() - Serj Sagan
@SerjSagan 这是一个无限循环。Marshal.GetLastWin32Error() 总是返回0,从而导致循环永远不会结束。 - Miha Markic
@MihaMarkic 这绝对不是真的。Marshal.GetLastWin32Error()并不总是返回0。现在,我不会争论是否存在这种情况,即Marshal.GetLastWin32Error()永远不等于ERROR_NO_MORE_ITEMS,但这是一个与你所断言的不同的错误。 - Serj Sagan

4
自从WebBrowser(实际上是IE的Trident引擎)使用WinInet进行网络连接后,您可以使用WinInet的缓存管理API在导航之前删除缓存文件。请参考缓存管理API的文档

2
我们发现完全清除缓存需要太长时间。一个看起来很好的替代方案(并且解决了刷新不起作用的原始问题)是在文档加载完成后调用刷新。
webBrowser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(OnWebBrowserDocumentCompleted);

// ...

private void OnWebBrowserDocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
    if (cacheIsStale)
    {
        webBrowser.Refresh(WebBrowserRefreshOption.Completely);
    }
}

确定cacheIsStale的值将取决于您的应用程序。

2

为URL添加一个随机ID,以使每次URL都是唯一的


当涉及到强制资源(如外部样式表CSS)重新加载时,这对我不起作用。 - Uwe Keim

1
为了防止CSS缓存,您可以在包含CSS文件时使用一个巧妙的技巧:
<link type="text/css" href="css/outlook.css?<?php echo date('l jS \of F Y h:i:s A'); ?>" rel="stylesheet" />
这会下载CSS的新副本,因为?date_stuff每次都会改变。

我觉得你误解了问题? - Tim Post
一旦你用“pdf”替换“css”,这实际上是一个非常干净的答案。只需每次生成一个新的文件名即可。 - Researcher

0

对于那些仍需要编写VB.NET的人,我将Serj Sagan和Oliver Bock上面发布的代码移植到了VB中,并发现它可以很好地工作。

谢天谢地有这个帖子存在,我为这个问题担心了很长时间。

        '
    ' Modified from code originally found here: http://support.microsoft.com/kb/326201
    '
    Public Class WebBrowserHelper

区域 "定义/DLL 导入"

        ''' <summary>
        ''' For PInvoke: Contains information about an entry In the Internet cache
        ''' </summary>
        <StructLayout(LayoutKind.Explicit)>
        Public Structure ExemptDeltaOrReserverd
            <FieldOffset(0)>
            Public dwReserved As UInt32
            <FieldOffset(0)>
            Public dwExemptDelta As UInt32
        End Structure

        <StructLayout(LayoutKind.Sequential)>
        Public Structure INTERNET_CACHE_ENTRY_INFOA
            Public dwStructSize As UInt32
            Public lpszSourceUrlName As IntPtr
            Public lpszLocalFileName As IntPtr
            Public CacheEntryType As UInt32
            Public dwUseCount As UInt32
            Public dwHitRate As UInt32
            Public dwSizeLow As UInt32
            Public dwSizeHigh As UInt32
            Public LastModifiedTime As FILETIME
            Public ExpireTime As FILETIME
            Public LastAccessTime As FILETIME
            Public LastSyncTime As FILETIME
            Public lpHeaderInfo As IntPtr
            Public dwHeaderInfoSize As UInt32
            Public lpszFileExtension As IntPtr
            Public dwExemptDeltaOrReserved As ExemptDeltaOrReserverd
        End Structure

        ' For PInvoke: Initiates the enumeration Of the cache groups In the Internet cache
        <DllImport("wininet", SetLastError:=True, CharSet:=CharSet.Auto, EntryPoint:="FindFirstUrlCacheGroup", CallingConvention:=CallingConvention.StdCall)>
        Public Shared Function FindFirstUrlCacheGroup(dwFlags As Integer, dwFilter As Integer, lpSearchCondition As IntPtr, dwSearchCondition As Integer, ByRef lpGroupId As Long,
                            lpReserved As IntPtr) As IntPtr
        End Function

        ' For PInvoke: Retrieves the Next cache group In a cache group enumeration
        <DllImport("wininet", SetLastError:=True, CharSet:=CharSet.Auto, EntryPoint:="FindNextUrlCacheGroup", CallingConvention:=CallingConvention.StdCall)>
        Public Shared Function FindNextUrlCacheGroup(hFind As IntPtr, ByRef lpGroupId As Long, lpReserved As IntPtr) As Boolean
        End Function

        ' For PInvoke: Releases the specified GROUPID And any associated state In the cache index file
        <DllImport("wininet", SetLastError:=True, CharSet:=CharSet.Auto, EntryPoint:="DeleteUrlCacheGroup", CallingConvention:=CallingConvention.StdCall)>
        Public Shared Function DeleteUrlCacheGroup(GroupId As Long, dwFlags As Integer, lpReserved As IntPtr) As Boolean
        End Function

        ' For PInvoke: Begins the enumeration Of the Internet cache
        <DllImport("wininet", SetLastError:=True, CharSet:=CharSet.Auto, EntryPoint:="FindFirstUrlCacheEntryA", CallingConvention:=CallingConvention.StdCall)>
        Public Shared Function FindFirstUrlCacheEntry(<MarshalAs(UnmanagedType.LPTStr)> lpszUrlSearchPattern As String, lpFirstCacheEntryInfo As IntPtr,
                                                      ByRef lpdwFirstCacheEntryInfoBufferSize As Integer) As IntPtr
        End Function

        ' For PInvoke: Retrieves the Next entry In the Internet cache
        <DllImport("wininet", SetLastError:=True, CharSet:=CharSet.Auto, EntryPoint:="FindNextUrlCacheEntryA", CallingConvention:=CallingConvention.StdCall)>
        Public Shared Function FindNextUrlCacheEntry(hFind As IntPtr, lpNextCacheEntryInfo As IntPtr, ByRef lpdwNextCacheEntryInfoBufferSize As Integer) As Boolean
        End Function

        ' For PInvoke: Removes the file that Is associated With the source name from the cache, If the file exists
        <DllImport("wininet", SetLastError:=True, CharSet:=CharSet.Auto, EntryPoint:="DeleteUrlCacheEntryA", CallingConvention:=CallingConvention.StdCall)>
        Public Shared Function DeleteUrlCacheEntry(lpszUrlName As IntPtr) As Boolean
        End Function

结束区域

        ''' <summary>
        ''' Clears the cache of the web browser
        ''' </summary>
        Public Shared Sub ClearCache()
            ' Indicates that all of the cache groups in the user's system should be enumerated
            Const CACHEGROUP_SEARCH_ALL As Integer = &H0
            ' Indicates that all the cache entries that are associated with the cache group
            ' should be deleted, unless the entry belongs to another cache group.
            Const CACHEGROUP_FLAG_FLUSHURL_ONDELETE As Integer = &H2
            Const ERROR_INSUFFICIENT_BUFFER As Integer = &H7A

            ' Delete the groups first.
            ' Groups may Not always exist on the system.
            ' For more information, visit the following Microsoft Web site:
            ' http//msdn.microsoft.com/library/?url=/workshop/networking/wininet/overview/cache.asp            
            ' By default, a URL does Not belong to any group. Therefore, that cache may become
            ' empty even when the CacheGroup APIs are Not used because the existing URL does Not belong to any group.            
            Dim groupId As Long = 0
            Dim enumHandle As IntPtr = FindFirstUrlCacheGroup(0, CACHEGROUP_SEARCH_ALL, IntPtr.Zero, 0, groupId, IntPtr.Zero)

            If (enumHandle <> IntPtr.Zero) Then
                Dim more As Boolean

                Do
                    ' Delete a particular Cache Group.
                    DeleteUrlCacheGroup(groupId, CACHEGROUP_FLAG_FLUSHURL_ONDELETE, IntPtr.Zero)
                    more = FindNextUrlCacheGroup(enumHandle, groupId, IntPtr.Zero)
                Loop While (more)
            End If

            ' Start to delete URLs that do Not belong to any group.
            Dim cacheEntryInfoBufferSizeInitial As Integer = 0

            FindFirstUrlCacheEntry(Nothing, IntPtr.Zero, cacheEntryInfoBufferSizeInitial)  ' should always fail because buffer Is too small

            If Marshal.GetLastWin32Error() = ERROR_INSUFFICIENT_BUFFER Then
                Dim cacheEntryInfoBufferSize As Integer = cacheEntryInfoBufferSizeInitial
                Dim cacheEntryInfoBuffer As IntPtr = Marshal.AllocHGlobal(cacheEntryInfoBufferSize)

                enumHandle = FindFirstUrlCacheEntry(Nothing, cacheEntryInfoBuffer, cacheEntryInfoBufferSizeInitial)

                If (enumHandle <> IntPtr.Zero) Then
                    Dim more As Boolean
                    Do
                        Dim internetCacheEntry As INTERNET_CACHE_ENTRY_INFOA = CType(Marshal.PtrToStructure(cacheEntryInfoBuffer, GetType(INTERNET_CACHE_ENTRY_INFOA)), INTERNET_CACHE_ENTRY_INFOA)

                        cacheEntryInfoBufferSizeInitial = cacheEntryInfoBufferSize
                        DeleteUrlCacheEntry(internetCacheEntry.lpszSourceUrlName)
                        more = FindNextUrlCacheEntry(enumHandle, cacheEntryInfoBuffer, cacheEntryInfoBufferSizeInitial)

                        If Not more AndAlso Marshal.GetLastWin32Error() = ERROR_INSUFFICIENT_BUFFER Then
                            cacheEntryInfoBufferSize = cacheEntryInfoBufferSizeInitial
                            cacheEntryInfoBuffer = Marshal.ReAllocHGlobal(cacheEntryInfoBuffer, CType(cacheEntryInfoBufferSize, IntPtr))
                            more = FindNextUrlCacheEntry(enumHandle, cacheEntryInfoBuffer, cacheEntryInfoBufferSizeInitial)
                        End If
                    Loop While (more)
                End If

                Marshal.FreeHGlobal(cacheEntryInfoBuffer)
            End If
        End Sub
    End Class

0

PDF是否被嵌入了对象标签或其他内容?如果是这样的话,要求浏览器对象刷新将没有任何效果-您必须编写脚本来刷新PDF查看器(例如,通过从WebBrowser控件获取PDF查看器对象ID),因为它是下载的。


0

Vescan Petru 正在正确的方向上。清除IE浏览器缓存是相当粗暴且不友好的做法。将Chris Clark提供的建议的微软代码适应到只清除特定文件的缓存看起来是一个有趣的代码片段,但最简单的解决方案是获取一个新的临时文件名,然后用该名称复制目标PDF文件。然后显示临时文件 - 在退出时删除它。

这是我遇到同样问题时实施的工作解决方案。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接