1 // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. 2 3 using System.Collections.Concurrent; 4 using System.Collections.Generic; 5 using System.Diagnostics; 6 using System.Threading; 7 using System.Web.Hosting; 8 9 namespace System.Web.WebPages 10 { 11 /// <summary> 12 /// This class caches the result of VirtualPathProvider.FileExists for a short 13 /// period of time, and recomputes it if necessary. 14 /// 15 /// The default VPP MapPathBasedVirtualPathProvider caches the result of 16 /// the FileExists call with the appropriate dependencies, so it is less 17 /// expensive on subsequent calls, but it still needs to do MapPath which can 18 /// take quite some time. 19 /// </summary> 20 internal class FileExistenceCache 21 { 22 private const int TickPerMiliseconds = 10000; 23 private readonly VirtualPathProvider _virtualPathProvider; 24 private ConcurrentDictionary<string, bool> _cache; 25 private long _creationTick; 26 private int _ticksBeforeReset; 27 FileExistenceCache(VirtualPathProvider virtualPathProvider, int milliSecondsBeforeReset = 1000)28 public FileExistenceCache(VirtualPathProvider virtualPathProvider, int milliSecondsBeforeReset = 1000) 29 { 30 _virtualPathProvider = virtualPathProvider; 31 _ticksBeforeReset = milliSecondsBeforeReset * TickPerMiliseconds; 32 Reset(); 33 } 34 35 // Use the VPP returned by the HostingEnvironment unless a custom vpp is passed in (mainly for testing purposes) 36 public VirtualPathProvider VirtualPathProvider 37 { 38 get { return _virtualPathProvider; } 39 } 40 41 public int MilliSecondsBeforeReset 42 { 43 get { return _ticksBeforeReset / TickPerMiliseconds; } 44 internal set { _ticksBeforeReset = value * TickPerMiliseconds; } 45 } 46 47 internal IDictionary<string, bool> CacheInternal 48 { 49 get { return _cache; } 50 } 51 52 public bool TimeExceeded 53 { 54 get { return (DateTime.UtcNow.Ticks - Interlocked.Read(ref _creationTick)) > _ticksBeforeReset; } 55 } 56 Reset()57 public void Reset() 58 { 59 _cache = new ConcurrentDictionary<string, bool>(StringComparer.OrdinalIgnoreCase); 60 61 DateTime now = DateTime.UtcNow; 62 long tick = now.Ticks; 63 64 Interlocked.Exchange(ref _creationTick, tick); 65 } 66 FileExists(string virtualPath)67 public bool FileExists(string virtualPath) 68 { 69 if (TimeExceeded) 70 { 71 Reset(); 72 } 73 // The right way to do this is to verify in the constructor that the VirtualPathProvider argument is not null. 74 // However when unit testing this, we often new up instances when not running under Asp.Net when HostingEnvironment.VirtualPathProvider is null. 75 Debug.Assert(_virtualPathProvider != null); 76 return _cache.GetOrAdd(virtualPath, _virtualPathProvider.FileExists); 77 } 78 } 79 } 80