diff --git a/Caching/FileDbCache/FileDbCache.cs b/Caching/FileDbCache/FileDbCache.cs index 7f0748f1..dfa699bd 100644 --- a/Caching/FileDbCache/FileDbCache.cs +++ b/Caching/FileDbCache/FileDbCache.cs @@ -12,7 +12,7 @@ namespace Caching /// /// ObjectCache implementation based on EzTools FileDb - http://www.eztools-software.com/tools/filedb/. /// - public class FileDbCache : ObjectCache, IDisposable + public class FileDbCache : ObjectCache { private const string keyField = "Key"; private const string valueField = "Value"; @@ -67,7 +67,7 @@ namespace Caching } this.name = name; - path = Path.Combine(directory, name); + path = Path.Combine(directory, name.Trim()); if (string.IsNullOrEmpty(Path.GetExtension(path))) { @@ -91,6 +91,11 @@ namespace Caching { CreateDatabase(); } + + if (fileDb.IsOpen) + { + AppDomain.CurrentDomain.ProcessExit += OnProcessExit; + } } public bool AutoFlush @@ -406,12 +411,45 @@ namespace Caching } } - public void Dispose() + public void Close() + { + if (fileDb.IsOpen) + { + fileDb.Close(); + AppDomain.CurrentDomain.ProcessExit -= OnProcessExit; + } + } + + private void OnProcessExit(object sender, EventArgs e) + { + Close(); + } + + private void CreateDatabase() { if (fileDb.IsOpen) { fileDb.Close(); } + + if (File.Exists(path)) + { + File.Delete(path); + } + else + { + Directory.CreateDirectory(Path.GetDirectoryName(path)); + } + + fileDb.Create(path, + new Field[] + { + new Field(keyField, DataTypeEnum.String) { IsPrimaryKey = true }, + new Field(valueField, DataTypeEnum.Byte) { IsArray = true }, + new Field(expiresField, DataTypeEnum.DateTime) + }); + + Trace.TraceInformation("FileDbCache: Created database {0}", path); } private bool RepairDatabase() @@ -420,47 +458,19 @@ namespace Caching { fileDb.Reindex(); } - catch (Exception ex) + catch (Exception ex1) { - Trace.TraceWarning("FileDbCache: FileDb.Reindex() failed: {0}", ex.Message); - return CreateDatabase(); - } + Trace.TraceWarning("FileDbCache: FileDb.Reindex() failed: {0}", ex1.Message); - return true; - } - - private bool CreateDatabase() - { - if (fileDb.IsOpen) - { - fileDb.Close(); - } - - try - { - if (File.Exists(path)) + try { - File.Delete(path); + CreateDatabase(); } - else + catch (Exception ex2) { - Directory.CreateDirectory(Path.GetDirectoryName(path)); + Trace.TraceWarning("FileDbCache: Creating database {0} failed: {1}", path, ex2.Message); + return false; } - - fileDb.Create(path, - new Field[] - { - new Field(keyField, DataTypeEnum.String) { IsPrimaryKey = true }, - new Field(valueField, DataTypeEnum.Byte) { IsArray = true }, - new Field(expiresField, DataTypeEnum.DateTime) - }); - - Trace.TraceInformation("FileDbCache: Created database {0}", path); - } - catch (Exception ex) - { - Trace.TraceWarning("FileDbCache: Creating database failed: {0}", ex.Message); - return false; } return true; diff --git a/Caching/ImageFileCache/ImageFileCache.cs b/Caching/ImageFileCache/ImageFileCache.cs new file mode 100644 index 00000000..a4a4a752 --- /dev/null +++ b/Caching/ImageFileCache/ImageFileCache.cs @@ -0,0 +1,293 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.Caching; + +namespace Caching +{ + /// + /// ObjectCache implementation based on local image files. + /// The only valid data type for cached values is a byte[], which contains + /// an 8-byte binary UTC time (as created by DateTime.ToBinary), followed + /// by a PNG, JPEG, BMP, GIF, TIFF or WMP image buffer. + /// + public class ImageFileCache : ObjectCache + { + private readonly string name; + private readonly string directory; + + public ImageFileCache(string name, NameValueCollection config) + : this(name, config["directory"]) + { + } + + public ImageFileCache(string name, string directory) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("The parameter name must not be null or empty or only white-space."); + } + + if (string.IsNullOrWhiteSpace(directory)) + { + throw new ArgumentException("The parameter directory must not be null or empty or only white-space."); + } + + this.name = name; + this.directory = Path.Combine(directory, name.Trim()); + Directory.CreateDirectory(this.directory); + + Trace.TraceInformation("Created ImageFileCache in {0}", this.directory); + } + + public override string Name + { + get { return name; } + } + + public override DefaultCacheCapabilities DefaultCacheCapabilities + { + get { return DefaultCacheCapabilities.InMemoryProvider; } + } + + public override object this[string key] + { + get { return Get(key); } + set { Set(key, value, null); } + } + + protected override IEnumerator> GetEnumerator() + { + throw new NotSupportedException("LocalFileCache does not support the ability to enumerate items."); + } + + public override CacheEntryChangeMonitor CreateCacheEntryChangeMonitor(IEnumerable keys, string regionName = null) + { + throw new NotSupportedException("LocalFileCache does not support the ability to create change monitors."); + } + + public override long GetCount(string regionName = null) + { + throw new NotSupportedException("LocalFileCache does not support the ability to count items."); + } + + public override bool Contains(string key, string regionName = null) + { + if (regionName != null) + { + throw new NotSupportedException("The parameter regionName must be null."); + } + + try + { + return MemoryCache.Default.Contains(key) || FindFile(GetPath(key)) != null; + } + catch + { + return false; + } + } + + public override object Get(string key, string regionName = null) + { + if (regionName != null) + { + throw new NotSupportedException("The parameter regionName must be null."); + } + + var value = MemoryCache.Default.Get(key); + + if (value == null) + { + try + { + var path = FindFile(GetPath(key)); + + if (path != null) + { + long creationTime = File.GetLastWriteTimeUtc(path).ToBinary(); + + using (FileStream fileStream = new FileStream(path, FileMode.Open)) + using (MemoryStream memoryStream = new MemoryStream((int)(fileStream.Length + 8))) + { + memoryStream.Write(BitConverter.GetBytes(creationTime), 0, 8); + fileStream.CopyTo(memoryStream); + value = memoryStream.GetBuffer(); + } + } + } + catch + { + } + } + + return value; + } + + public override CacheItem GetCacheItem(string key, string regionName = null) + { + var value = Get(key, regionName); + return value != null ? new CacheItem(key, value) : null; + } + + public override IDictionary GetValues(IEnumerable keys, string regionName = null) + { + if (regionName != null) + { + throw new NotSupportedException("The parameter regionName must be null."); + } + + var values = new Dictionary(); + + foreach (string key in keys) + { + values[key] = Get(key); + } + + return values; + } + + public override void Set(string key, object value, CacheItemPolicy policy, string regionName = null) + { + if (regionName != null) + { + throw new NotSupportedException("The parameter regionName must be null."); + } + + var buffer = value as byte[]; + + if (buffer == null) + { + throw new NotSupportedException("The parameter value must be a byte[]."); + } + + MemoryCache.Default.Set(key, buffer, policy); + + var extension = GetFileExtension(buffer); + + if (extension != null) + { + var path = GetPath(key) + extension; + Directory.CreateDirectory(Path.GetDirectoryName(path)); + + using (FileStream fileStream = new FileStream(path, FileMode.Create)) + { + fileStream.Write(buffer, 8, buffer.Length - 8); + } + } + } + + public override void Set(CacheItem item, CacheItemPolicy policy) + { + Set(item.Key, item.Value, policy, item.RegionName); + } + + public override void Set(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null) + { + Set(key, value, new CacheItemPolicy { AbsoluteExpiration = absoluteExpiration }, regionName); + } + + public override object AddOrGetExisting(string key, object value, CacheItemPolicy policy, string regionName = null) + { + var oldValue = Get(key, regionName); + Set(key, value, policy); + return oldValue; + } + + public override CacheItem AddOrGetExisting(CacheItem item, CacheItemPolicy policy) + { + var oldItem = GetCacheItem(item.Key, item.RegionName); + Set(item, policy); + return oldItem; + } + + public override object AddOrGetExisting(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null) + { + return AddOrGetExisting(key, value, new CacheItemPolicy { AbsoluteExpiration = absoluteExpiration }, regionName); + } + + public override object Remove(string key, string regionName = null) + { + var oldValue = Get(key, regionName); + MemoryCache.Default.Remove(key); + + try + { + var path = FindFile(GetPath(key)); + + if (path != null) + { + File.Delete(path); + } + } + catch + { + } + + return oldValue; + } + + private string GetPath(string key) + { + return Path.Combine(directory, key); + } + + private static string FindFile(string path) + { + if (!string.IsNullOrEmpty(Path.GetExtension(path))) + { + return path; + } + + string directoryName = Path.GetDirectoryName(path); + + if (Directory.Exists(directoryName)) + { + return Directory.EnumerateFiles(directoryName, Path.GetFileName(path) + ".*").FirstOrDefault(); + } + + return null; + } + + private static readonly Tuple[] fileTypes = new Tuple[] + { + new Tuple(".png", new byte[] { 0x89, 0x50, 0x4E, 0x47, 0xD, 0xA, 0x1A, 0xA }), + new Tuple(".jpg", new byte[] { 0xFF, 0xD8, 0xFF, 0xE0, 0, 0x10, 0x4A, 0x46, 0x49, 0x46, 0 }), + new Tuple(".bmp", new byte[] { 0x42, 0x4D }), + new Tuple(".gif", new byte[] { 0x47, 0x49, 0x46 }), + new Tuple(".tif", new byte[] { 0x49, 0x49, 42, 0 }), + new Tuple(".tif", new byte[] { 0x4D, 0x4D, 0, 42 }), + new Tuple(".wdp", new byte[] { 0x49, 0x49, 0xBC }), + }; + + private static string GetFileExtension(byte[] buffer) + { + string extension = null; + DateTime creationTime = DateTime.FromBinary(BitConverter.ToInt64(buffer, 0)); + + if (creationTime.Kind == DateTimeKind.Utc && creationTime <= DateTime.UtcNow) + { + Func, bool> match = + t => + { + int i = 0; + if (t.Item2.Length + 8 <= buffer.Length) + { + while (i < t.Item2.Length && t.Item2[i] == buffer[i + 8]) + { + i++; + } + } + return i == t.Item2.Length; + }; + + extension = fileTypes.Where(match).Select(t => t.Item1).FirstOrDefault(); + } + + return extension; + } + } +} diff --git a/Caching/ImageFileCache/ImageFileCache.csproj b/Caching/ImageFileCache/ImageFileCache.csproj new file mode 100644 index 00000000..c2717585 --- /dev/null +++ b/Caching/ImageFileCache/ImageFileCache.csproj @@ -0,0 +1,50 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {86470440-FEE2-4120-AF5A-3762FB9C536F} + Library + Properties + Caching + ImageFileCache + v4.0 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + none + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + \ No newline at end of file diff --git a/Caching/ImageFileCache/Properties/AssemblyInfo.cs b/Caching/ImageFileCache/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..ef77d2c5 --- /dev/null +++ b/Caching/ImageFileCache/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ImageFileCache")] +[assembly: AssemblyDescription("ObjectCache implementation based on local image files")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ImageFileCache")] +[assembly: AssemblyCopyright("Copyright © 2012 Clemens Fischer")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("0483659a-9743-41a2-9505-9c1a4d9ecc06")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/MapControl.sln b/MapControl.sln index e90ceadf..a612b95a 100644 --- a/MapControl.sln +++ b/MapControl.sln @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleApplication", "Sample EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SurfaceApplication", "SampleApps\SurfaceApplication\SurfaceApplication.csproj", "{6285FB9D-B7EA-469A-B464-224077967167}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageFileCache", "Caching\ImageFileCache\ImageFileCache.csproj", "{86470440-FEE2-4120-AF5A-3762FB9C536F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -59,6 +61,16 @@ Global {6285FB9D-B7EA-469A-B464-224077967167}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {6285FB9D-B7EA-469A-B464-224077967167}.Release|Mixed Platforms.Build.0 = Release|Any CPU {6285FB9D-B7EA-469A-B464-224077967167}.Release|x86.ActiveCfg = Release|Any CPU + {86470440-FEE2-4120-AF5A-3762FB9C536F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {86470440-FEE2-4120-AF5A-3762FB9C536F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {86470440-FEE2-4120-AF5A-3762FB9C536F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {86470440-FEE2-4120-AF5A-3762FB9C536F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {86470440-FEE2-4120-AF5A-3762FB9C536F}.Debug|x86.ActiveCfg = Debug|Any CPU + {86470440-FEE2-4120-AF5A-3762FB9C536F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {86470440-FEE2-4120-AF5A-3762FB9C536F}.Release|Any CPU.Build.0 = Release|Any CPU + {86470440-FEE2-4120-AF5A-3762FB9C536F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {86470440-FEE2-4120-AF5A-3762FB9C536F}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {86470440-FEE2-4120-AF5A-3762FB9C536F}.Release|x86.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/MapControl/TileImageLoader.cs b/MapControl/TileImageLoader.cs index 75a8a664..111a8cc6 100644 --- a/MapControl/TileImageLoader.cs +++ b/MapControl/TileImageLoader.cs @@ -5,14 +5,12 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Collections.Specialized; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Runtime.Caching; using System.Threading; -using System.Windows; using System.Windows.Media.Imaging; using System.Windows.Threading; @@ -26,6 +24,17 @@ namespace MapControl private readonly TileLayer tileLayer; private readonly ConcurrentQueue pendingTiles = new ConcurrentQueue(); + /// + /// Default Name of an ObjectCache instance that is assigned to the Cache property. + /// + public static readonly string DefaultCacheName = "TileCache"; + + /// + /// Default value for the directory where an ObjectCache instance may save cached data. + /// + public static readonly string DefaultCacheDirectory = + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "MapControl"); + /// /// The ObjectCache used to cache tile images. /// The default is System.Runtime.Caching.MemoryCache.Default. @@ -48,53 +57,11 @@ namespace MapControl /// public static TimeSpan CacheUpdateAge { get; set; } - /// - /// Creates an instance of the ObjectCache-derived type T and sets the static Cache - /// property to this instance. Class T must (like System.Runtime.Caching.MemoryCache) - /// provide a constructor with two parameters, first a string that gets the name of - /// the cache instance, second a NameValueCollection that gets the config parameter. - /// If config is null, a new NameValueCollection is created. If config does not already - /// contain an entry with key "directory", a new entry is added with this key and a - /// value that specifies the path to an application data directory where the cache - /// implementation may store persistent cache data files. - /// - public static void CreateCache(NameValueCollection config = null) where T : ObjectCache - { - if (config == null) - { - config = new NameValueCollection(1); - } - - if (config["directory"] == null) - { - config["directory"] = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "MapControl"); - } - - try - { - Cache = (ObjectCache)Activator.CreateInstance(typeof(T), "TileCache", config); - } - catch (Exception ex) - { - Trace.TraceWarning("Could not create instance of type {0} with String and NameValueCollection constructor parameters: {1}", typeof(T), ex.Message); - throw; - } - } - static TileImageLoader() { Cache = MemoryCache.Default; CacheExpiration = TimeSpan.FromDays(30d); CacheUpdateAge = TimeSpan.FromDays(1d); - - Application.Current.Exit += (o, e) => - { - IDisposable disposableCache = Cache as IDisposable; - if (disposableCache != null) - { - disposableCache.Dispose(); - } - }; } internal TileImageLoader(TileLayer tileLayer) @@ -151,7 +118,7 @@ namespace MapControl Cache.Remove(key); pendingTiles.Enqueue(tile); } - else if (GetCreationTime(imageBuffer) + CacheUpdateAge < DateTime.UtcNow) + else if (IsCacheOutdated(imageBuffer)) { // update cached image outdatedTiles.Add(tile); @@ -206,21 +173,15 @@ namespace MapControl using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) using (Stream responseStream = response.GetResponseStream()) { - if (response.ContentLength > 0) + long length = response.ContentLength; + long creationTime = DateTime.UtcNow.ToBinary(); + + using (MemoryStream memoryStream = length > 0 ? new MemoryStream((int)length + 8) : new MemoryStream()) { - using (MemoryStream memoryStream = new MemoryStream((int)response.ContentLength + 8)) - { - CopyWithCreationTime(responseStream, memoryStream); - buffer = memoryStream.GetBuffer(); - } - } - else - { - using (MemoryStream memoryStream = new MemoryStream()) - { - CopyWithCreationTime(responseStream, memoryStream); - buffer = memoryStream.ToArray(); - } + memoryStream.Write(BitConverter.GetBytes(creationTime), 0, 8); + responseStream.CopyTo(memoryStream); + + buffer = length > 0 ? memoryStream.GetBuffer() : memoryStream.ToArray(); } } @@ -245,13 +206,20 @@ namespace MapControl return buffer; } - private bool CreateTileImage(Tile tile, byte[] buffer) + private bool IsCacheOutdated(byte[] imageBuffer) + { + long creationTime = BitConverter.ToInt64(imageBuffer, 0); + + return DateTime.FromBinary(creationTime) + CacheUpdateAge < DateTime.UtcNow; + } + + private bool CreateTileImage(Tile tile, byte[] imageBuffer) { BitmapImage bitmap = new BitmapImage(); try { - using (Stream stream = new MemoryStream(buffer, 0, buffer.Length - 8, false)) + using (Stream stream = new MemoryStream(imageBuffer, 8, imageBuffer.Length - 8, false)) { bitmap.BeginInit(); bitmap.CacheOption = BitmapCacheOption.OnLoad; @@ -270,17 +238,6 @@ namespace MapControl return true; } - private static DateTime GetCreationTime(byte[] imageBuffer) - { - return new DateTime(BitConverter.ToInt64(imageBuffer, imageBuffer.Length - 8)); - } - - private static void CopyWithCreationTime(Stream source, Stream target) - { - source.CopyTo(target); - target.Write(BitConverter.GetBytes(DateTime.UtcNow.Ticks), 0, 8); - } - private static void TraceWarning(string format, params object[] args) { Trace.TraceWarning("[{0:00}] {1}", Thread.CurrentThread.ManagedThreadId, string.Format(format, args)); diff --git a/SampleApps/SampleApplication/App.config b/SampleApps/SampleApplication/App.config index bdcb4591..b49fc3d9 100644 --- a/SampleApps/SampleApplication/App.config +++ b/SampleApps/SampleApplication/App.config @@ -7,8 +7,8 @@ - - False + + diff --git a/SampleApps/SampleApplication/MainWindow.xaml b/SampleApps/SampleApplication/MainWindow.xaml index 1f10037a..73b2dafd 100644 --- a/SampleApps/SampleApplication/MainWindow.xaml +++ b/SampleApps/SampleApplication/MainWindow.xaml @@ -21,7 +21,7 @@ map content without using their APIs (i.e. Google Maps API or Bing Maps API). Hence the declarations below are for demonstration purpose only. --> - @@ -30,7 +30,7 @@ + TileSource="http://ecn.t{i}.tiles.virtualearth.net/tiles/h{q}.jpeg?g=0&stl=h" MaxZoomLevel="20" HasDarkBackground="True"/>--> diff --git a/SampleApps/SampleApplication/MainWindow.xaml.cs b/SampleApps/SampleApplication/MainWindow.xaml.cs index f4d7b241..c3086992 100644 --- a/SampleApps/SampleApplication/MainWindow.xaml.cs +++ b/SampleApps/SampleApplication/MainWindow.xaml.cs @@ -19,9 +19,16 @@ namespace SampleApplication public MainWindow() { - if (Properties.Settings.Default.UsePersistentCache) + switch (Properties.Settings.Default.TileCache) { - TileImageLoader.CreateCache(); + case "FileDbCache": + TileImageLoader.Cache = new FileDbCache(TileImageLoader.DefaultCacheName, TileImageLoader.DefaultCacheDirectory); + break; + case "ImageFileCache": + TileImageLoader.Cache = new ImageFileCache(TileImageLoader.DefaultCacheName, TileImageLoader.DefaultCacheDirectory); + break; + default: + break; } InitializeComponent(); diff --git a/SampleApps/SampleApplication/Properties/Settings.Designer.cs b/SampleApps/SampleApplication/Properties/Settings.Designer.cs index 4a720455..d130c145 100644 --- a/SampleApps/SampleApplication/Properties/Settings.Designer.cs +++ b/SampleApps/SampleApplication/Properties/Settings.Designer.cs @@ -25,10 +25,10 @@ namespace SampleApplication.Properties { [global::System.Configuration.ApplicationScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("False")] - public bool UsePersistentCache { + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string TileCache { get { - return ((bool)(this["UsePersistentCache"])); + return ((string)(this["TileCache"])); } } } diff --git a/SampleApps/SampleApplication/Properties/Settings.settings b/SampleApps/SampleApplication/Properties/Settings.settings index 889a6332..a4682593 100644 --- a/SampleApps/SampleApplication/Properties/Settings.settings +++ b/SampleApps/SampleApplication/Properties/Settings.settings @@ -2,8 +2,8 @@ - - False + + \ No newline at end of file diff --git a/SampleApps/SampleApplication/SampleApplication.csproj b/SampleApps/SampleApplication/SampleApplication.csproj index f8ae4383..3e0f21f0 100644 --- a/SampleApps/SampleApplication/SampleApplication.csproj +++ b/SampleApps/SampleApplication/SampleApplication.csproj @@ -84,6 +84,10 @@ {EF44F661-B98A-4676-927F-85D138F82300} FileDbCache + + {86470440-FEE2-4120-AF5A-3762FB9C536F} + ImageFileCache + {06481252-2310-414A-B9FC-D5739FDF6BD3} MapControl