├── .gitignore ├── LICENSE ├── Plugin.EmbeddedResource ├── Plugin.EmbeddedResource.csproj ├── Plugin.EmbeddedResource.nuspec ├── Properties │ └── AssemblyInfo.cs ├── ResourceLoader.cs ├── ResourceWriter.cs └── packages.config └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | [Dd]ebug/ 11 | [Dd]ebugPublic/ 12 | [Rr]elease/ 13 | [Rr]eleases/ 14 | x64/ 15 | x86/ 16 | build/ 17 | bld/ 18 | [Bb]in/ 19 | [Oo]bj/ 20 | 21 | # Roslyn cache directories 22 | *.ide/ 23 | 24 | # MSTest test Results 25 | [Tt]est[Rr]esult*/ 26 | [Bb]uild[Ll]og.* 27 | 28 | #NUNIT 29 | *.VisualState.xml 30 | TestResult.xml 31 | 32 | # Build Results of an ATL Project 33 | [Dd]ebugPS/ 34 | [Rr]eleasePS/ 35 | dlldata.c 36 | 37 | *_i.c 38 | *_p.c 39 | *_i.h 40 | *.ilk 41 | *.meta 42 | *.obj 43 | *.pch 44 | *.pdb 45 | *.pgc 46 | *.pgd 47 | *.rsp 48 | *.sbr 49 | *.tlb 50 | *.tli 51 | *.tlh 52 | *.tmp 53 | *.tmp_proj 54 | *.log 55 | *.vspscc 56 | *.vssscc 57 | .builds 58 | *.pidb 59 | *.svclog 60 | *.scc 61 | 62 | # Chutzpah Test files 63 | _Chutzpah* 64 | 65 | # Visual C++ cache files 66 | ipch/ 67 | *.aps 68 | *.ncb 69 | *.opensdf 70 | *.sdf 71 | *.cachefile 72 | 73 | # Visual Studio profiler 74 | *.psess 75 | *.vsp 76 | *.vspx 77 | 78 | # TFS 2012 Local Workspace 79 | $tf/ 80 | 81 | # Guidance Automation Toolkit 82 | *.gpState 83 | 84 | # ReSharper is a .NET coding add-in 85 | _ReSharper*/ 86 | *.[Rr]e[Ss]harper 87 | *.DotSettings.user 88 | 89 | # JustCode is a .NET coding addin-in 90 | .JustCode 91 | 92 | # TeamCity is a build add-in 93 | _TeamCity* 94 | 95 | # DotCover is a Code Coverage Tool 96 | *.dotCover 97 | 98 | # NCrunch 99 | _NCrunch_* 100 | .*crunch*.local.xml 101 | 102 | # MightyMoose 103 | *.mm.* 104 | AutoTest.Net/ 105 | 106 | # Web workbench (sass) 107 | .sass-cache/ 108 | 109 | # Installshield output folder 110 | [Ee]xpress/ 111 | 112 | # DocProject is a documentation generator add-in 113 | DocProject/buildhelp/ 114 | DocProject/Help/*.HxT 115 | DocProject/Help/*.HxC 116 | DocProject/Help/*.hhc 117 | DocProject/Help/*.hhk 118 | DocProject/Help/*.hhp 119 | DocProject/Help/Html2 120 | DocProject/Help/html 121 | 122 | # Click-Once directory 123 | publish/ 124 | 125 | # Publish Web Output 126 | *.[Pp]ublish.xml 127 | *.azurePubxml 128 | # TODO: Comment the next line if you want to checkin your web deploy settings 129 | # but database connection strings (with potential passwords) will be unencrypted 130 | *.pubxml 131 | *.publishproj 132 | 133 | # NuGet Packages 134 | *.nupkg 135 | # The packages folder can be ignored because of Package Restore 136 | **/packages/* 137 | # except build/, which is used as an MSBuild target. 138 | !**/packages/build/ 139 | # If using the old MSBuild-Integrated Package Restore, uncomment this: 140 | #!**/packages/repositories.config 141 | 142 | # Windows Azure Build Output 143 | csx/ 144 | *.build.csdef 145 | 146 | # Windows Store app package directory 147 | AppPackages/ 148 | 149 | # Others 150 | sql/ 151 | *.Cache 152 | ClientBin/ 153 | [Ss]tyle[Cc]op.* 154 | ~$* 155 | *~ 156 | *.dbmdl 157 | *.dbproj.schemaview 158 | *.pfx 159 | *.publishsettings 160 | node_modules/ 161 | 162 | # RIA/Silverlight projects 163 | Generated_Code/ 164 | 165 | # Backup & report files from converting an old project file 166 | # to a newer Visual Studio version. Backup files are not needed, 167 | # because we have git ;-) 168 | _UpgradeReport_Files/ 169 | Backup*/ 170 | UpgradeLog*.XML 171 | UpgradeLog*.htm 172 | 173 | # SQL Server files 174 | *.mdf 175 | *.ldf 176 | 177 | # Business Intelligence projects 178 | *.rdl.data 179 | *.bim.layout 180 | *.bim_*.settings 181 | 182 | # Microsoft Fakes 183 | FakesAssemblies/ 184 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Joseph Hill 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Plugin.EmbeddedResource/Plugin.EmbeddedResource.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 7 | 8.0.30703 8 | 2.0 9 | {AC1ABE2A-1B90-49C1-A723-742AE6BE4D4E} 10 | Library 11 | Plugin.EmbeddedResource 12 | Plugin.EmbeddedResource 13 | Profile78 14 | v4.5 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug 21 | DEBUG; 22 | prompt 23 | 4 24 | false 25 | 26 | 27 | full 28 | true 29 | bin\Release 30 | prompt 31 | 4 32 | false 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | ..\..\PortableRazorStarterKit\PortableCongress\packages\PCLStorage.1.0.0\lib\portable-net45+wp8+wpa81+win8+monoandroid+monotouch+Xamarin.iOS+Xamarin.Mac\PCLStorage.dll 43 | 44 | 45 | ..\..\PortableRazorStarterKit\PortableCongress\packages\PCLStorage.1.0.0\lib\portable-net45+wp8+wpa81+win8+monoandroid+monotouch+Xamarin.iOS+Xamarin.Mac\PCLStorage.Abstractions.dll 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /Plugin.EmbeddedResource/Plugin.EmbeddedResource.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Xam.Plugin.EmbeddedResource 5 | 1.0.1.0 6 | EmbeddedResource Plugin for Xamarin and Windows 7 | Joseph Hill 8 | Joseph Hill 9 | https://github.com/JosephHill/EmbeddedResourcePlugin/blob/master/LICENSE 10 | https://github.com/JosephHill/EmbeddedResourcePlugin 11 | https://files.xamarin.com/~joseph/EmbeddedResource_128.png 12 | false 13 | 14 | A cross-platform convenience library for unpacking embedded resources. 15 | 16 | 17 | Mobile applications often need to bundle files with the app, such as a SQLite database or static HTML or images, that will later be accessed from the file system. 18 | 19 | The EmbeddedResource Plugin for Xamarin and Windows is a Portable Class Library (PCL) that provides a cross-platform API to read files embedded in a .NET assembly and write these files to disk via [PCL Storage](https://pclstorage.codeplex.com/). 20 | 21 | Fix path handling on Windows Phone 22 | Copyright 2015, Xamarin Inc. 23 | xamarin, pcl, xam.pcl, windows phone, winphone, wp8, winrt, android, xamarin.forms, ios 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Plugin.EmbeddedResource/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | 4 | // Information about this assembly is defined by the following attributes. 5 | // Change them to the values specific to your project. 6 | 7 | [assembly: AssemblyTitle ("Plugin.EmbeddedResource")] 8 | [assembly: AssemblyDescription ("")] 9 | [assembly: AssemblyConfiguration ("")] 10 | [assembly: AssemblyCompany ("")] 11 | [assembly: AssemblyProduct ("")] 12 | [assembly: AssemblyCopyright ("joseph")] 13 | [assembly: AssemblyTrademark ("")] 14 | [assembly: AssemblyCulture ("")] 15 | 16 | // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". 17 | // The form "{Major}.{Minor}.*" will automatically update the build and revision, 18 | // and "{Major}.{Minor}.{Build}.*" will update just the revision. 19 | 20 | [assembly: AssemblyVersion ("1.0.*")] 21 | 22 | // The following attributes are used to specify the signing key for the assembly, 23 | // if desired. See the Mono documentation for more information about signing. 24 | 25 | //[assembly: AssemblyDelaySign(false)] 26 | //[assembly: AssemblyKeyFile("")] 27 | 28 | -------------------------------------------------------------------------------- /Plugin.EmbeddedResource/ResourceLoader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Reflection; 5 | 6 | namespace Plugin.EmbeddedResource 7 | { 8 | 9 | /// 10 | /// Utility class that can be used to find and load embedded resources into memory. 11 | /// 12 | public static class ResourceLoader 13 | { 14 | /// 15 | /// Attempts to find and return the given resource from within the specified assembly. 16 | /// 17 | /// The embedded resource stream. 18 | /// containing embedded resources. 19 | /// Full name of embedded resource in . 20 | public static Stream GetEmbeddedResourceStream(Assembly assembly, string resource) 21 | { 22 | var resourceNames = assembly.GetManifestResourceNames(); 23 | 24 | var resourcePaths = resourceNames 25 | .Where(x => x.EndsWith(resource, StringComparison.CurrentCultureIgnoreCase)) 26 | .ToArray(); 27 | 28 | if (!resourcePaths.Any()) 29 | { 30 | throw new Exception(string.Format("Resource ending with {0} not found.", resource)); 31 | } 32 | 33 | if (resourcePaths.Count() > 1) 34 | { 35 | throw new Exception(string.Format("Multiple resources ending with {0} found: {1}{2}", resource, Environment.NewLine, string.Join(Environment.NewLine, resourcePaths))); 36 | } 37 | 38 | return assembly.GetManifestResourceStream(resourcePaths.Single()); 39 | } 40 | 41 | /// 42 | /// Attempts to find and return the given resource from within the specified assembly. 43 | /// 44 | /// The embedded resource as a byte array. 45 | /// containing embedded resources. 46 | /// Full name of embedded resource in . 47 | public static byte[] GetEmbeddedResourceBytes(Assembly assembly, string resource) 48 | { 49 | var stream = GetEmbeddedResourceStream(assembly, resource); 50 | 51 | using (var memoryStream = new MemoryStream()) 52 | { 53 | stream.CopyTo(memoryStream); 54 | return memoryStream.ToArray(); 55 | } 56 | } 57 | 58 | /// 59 | /// Attempts to find and return the given resource from within the specified assembly. 60 | /// 61 | /// The embedded resource as a string. 62 | /// containing embedded resources. 63 | /// Full name of embedded resource in . 64 | public static string GetEmbeddedResourceString(Assembly assembly, string resource) 65 | { 66 | var stream = GetEmbeddedResourceStream(assembly, resource); 67 | 68 | using (var streamReader = new StreamReader(stream)) 69 | { 70 | return streamReader.ReadToEnd(); 71 | } 72 | } 73 | } 74 | } 75 | 76 | -------------------------------------------------------------------------------- /Plugin.EmbeddedResource/ResourceWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using System.Threading.Tasks; 5 | using PCLStorage; 6 | 7 | namespace Plugin.EmbeddedResource 8 | { 9 | public class ResourceWriter 10 | { 11 | private Assembly Assembly { get; set; } 12 | 13 | /// 14 | /// Creates a new 15 | /// 16 | /// containing embedded resources. 17 | public ResourceWriter (Assembly assembly) 18 | { 19 | this.Assembly = assembly; 20 | } 21 | 22 | /// 23 | /// Writes all of the embedded resources in a project folder to the target directory 24 | /// 25 | /// Path to embedded resources in assembly. 26 | /// Path to folder where resources will be written. 27 | /// Descend into subdirectories to retrieve resources. 28 | /// Recursive writes will treat "." as path delimiters (excluding a final "." for the file extension). 29 | /// Non-recursive treats all "." as part of the file name. 30 | /// 31 | /// A which will complete after the folder is written 32 | /// 33 | public async Task WriteFolder (string sourceDirectory, string targetDirectory = "", bool recursive = true, CreationCollisionOption option = CreationCollisionOption.ReplaceExisting) { 34 | await WriteFolder ( 35 | this.Assembly, 36 | sourceDirectory, 37 | targetDirectory, 38 | recursive, 39 | option); 40 | } 41 | 42 | /// 43 | /// Writes an embedded resource to the file system 44 | /// 45 | /// containing embedded resources. 46 | /// Relative path to embedded resource in . 47 | /// Path to folder where resources will be written. 48 | /// 49 | /// A which will complete after the file is written 50 | /// 51 | public async Task WriteFile (string source, string targetDirectory = "", CreationCollisionOption option = CreationCollisionOption.ReplaceExisting) { 52 | await WriteFile ( 53 | this.Assembly, 54 | source, 55 | targetDirectory, 56 | option); 57 | } 58 | 59 | /// 60 | /// Writes an embedded resource to the file system 61 | /// 62 | /// Path and name to write the resource to the filesystem. 63 | /// Full name of embedded resource in . 64 | /// 65 | /// A which will complete after the file is written 66 | /// 67 | public async Task WriteResource (string fileName, string resource, CreationCollisionOption option = CreationCollisionOption.ReplaceExisting) { 68 | await WriteResource ( 69 | this.Assembly, 70 | fileName, 71 | resource, 72 | option); 73 | } 74 | 75 | 76 | /// 77 | /// Writes all of the embedded resources in a project folder to the target directory 78 | /// 79 | /// containing embedded resources. 80 | /// Path to embedded resources in assembly. 81 | /// Path to folder where resources will be written. 82 | /// Descend into subdirectories to retrieve resources. 83 | /// Recursive writes will treat "." as path delimiters (excluding a final "." for the file extension). 84 | /// Non-recursive treats all "." as part of the file name. 85 | /// 86 | /// A which will complete after the folder is written 87 | /// 88 | public static async Task WriteFolder (Assembly assembly, string sourceDirectory, string targetDirectory = "", bool recursive = true, CreationCollisionOption option = CreationCollisionOption.ReplaceExisting) { 89 | var sourcePrefix = String.Format ("{0}.{1}.", 90 | assembly.GetName ().Name, 91 | ConvertPathToResourceName (sourceDirectory)); 92 | 93 | foreach (var resource in assembly.GetManifestResourceNames() 94 | .Where(a => a.StartsWith(sourcePrefix)) 95 | .Select(b => b)) { 96 | 97 | // strip assembly name from resource 98 | var relativePathAndFilename = resource.Substring (sourcePrefix.Length); 99 | 100 | // .NET embedded resources replace path delimiters with "." 101 | // on recursive write, turn "." into directory separators 102 | 103 | if (recursive) { 104 | // assume that the last "." is the file extension delimiter 105 | var lastDot = relativePathAndFilename.LastIndexOf ("."); 106 | 107 | // treat any other "." as a path delimiter 108 | if (lastDot > -1) 109 | relativePathAndFilename = 110 | relativePathAndFilename.Substring (0, lastDot).Replace ('.', PortablePath.DirectorySeparatorChar) + 111 | "." + 112 | relativePathAndFilename.Substring (lastDot + 1); 113 | } 114 | 115 | await WriteResource ( 116 | assembly, 117 | PortablePath.Combine (targetDirectory, relativePathAndFilename), 118 | resource, 119 | option); 120 | } 121 | } 122 | 123 | /// 124 | /// Writes an embedded resource to the file system 125 | /// 126 | /// containing embedded resources. 127 | /// Relative path to embedded resource in . 128 | /// Path to folder where resources will be written. 129 | /// 130 | /// A which will complete after the file is written 131 | /// 132 | public static async Task WriteFile (Assembly assembly, string source, string targetDirectory = "", CreationCollisionOption option = CreationCollisionOption.ReplaceExisting) { 133 | var resource = String.Format ("{0}.{1}", 134 | assembly.GetName ().Name, 135 | ConvertPathToResourceName (source)); 136 | 137 | // handle either slash as path delimiter 138 | source = source.Replace("\\", "/"); 139 | 140 | var fileName = source.Substring ( 141 | source.LastIndexOf ("/") + 1); 142 | 143 | await WriteResource ( 144 | assembly, 145 | PortablePath.Combine(targetDirectory, fileName), 146 | resource, 147 | option); 148 | } 149 | 150 | /// 151 | /// Writes an embedded resource to the file system 152 | /// 153 | /// containing embedded resources. 154 | /// Path and name to write the resource to the filesystem. 155 | /// Full name of embedded resource in . 156 | /// 157 | /// A which will complete after the file is written 158 | /// 159 | public static async Task WriteResource (Assembly assembly, string fileName, string resource, CreationCollisionOption option = CreationCollisionOption.ReplaceExisting) { 160 | var folderName = fileName.Substring ( 161 | 0, 162 | fileName.LastIndexOf (PortablePath.DirectorySeparatorChar)); 163 | 164 | var folder = GetStorageFromPath (folderName); 165 | 166 | if (folder == null) 167 | folder = FileSystem.Current.LocalStorage; 168 | else 169 | if (folderName != folder.Path) 170 | // need to create subfolder 171 | folder = await folder.CreateFolderAsync ( 172 | folderName.Substring (folder.Path.Length + 1), 173 | CreationCollisionOption.OpenIfExists); 174 | 175 | var file = await folder.CreateFileAsync( 176 | fileName.Substring(fileName.LastIndexOf (PortablePath.DirectorySeparatorChar) + 1), 177 | option); 178 | 179 | using (var input = ResourceLoader.GetEmbeddedResourceStream(assembly, resource)) 180 | using (var output = await file.OpenAsync(FileAccess.ReadAndWrite)) { 181 | byte[] buffer = new byte[1024]; 182 | int length; 183 | while ((length = input.Read (buffer, 0, 1024)) > 0) 184 | output.Write (buffer, 0, length); 185 | output.Flush (); 186 | } 187 | } 188 | 189 | /// 190 | /// Converts file system style path to resource name 191 | /// 192 | /// Path to be parsed. 193 | /// 194 | /// Resource name with path delimiters replaced with "." 195 | /// 196 | private static string ConvertPathToResourceName (string path) { 197 | return path.Replace ('/','.').Replace ('\\','.'); 198 | } 199 | 200 | /// 201 | /// Determines which storage in FileSystem.Current that is represented by the path 202 | /// 203 | /// Path to be parsed. 204 | /// 205 | /// IFolder that contains path. Returns null if there is no matching storage 206 | /// 207 | private static IFolder GetStorageFromPath (string path) { 208 | if (path.StartsWith (FileSystem.Current.LocalStorage.Path)) 209 | return FileSystem.Current.LocalStorage; 210 | 211 | if (path.StartsWith (FileSystem.Current.RoamingStorage.Path)) 212 | return FileSystem.Current.RoamingStorage; 213 | 214 | return null; 215 | } 216 | } 217 | } 218 | 219 | -------------------------------------------------------------------------------- /Plugin.EmbeddedResource/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | EmbeddedResourcePlugin 2 | ====================== 3 | Mobile applications often need to bundle files with the app, such as a SQLite database or static HTML or images, that will later be accessed from the file system. 4 | 5 | The EmbeddedResource Plugin for Xamarin and Windows is a Portable Class Library (PCL) that provides a cross-platform API to read files embedded in a .NET assembly and write these files to disk via [PCL Storage](https://pclstorage.codeplex.com/). 6 | 7 | ## Using EmbeddedResource Plugin 8 | Files included in .NET projects can be included as Embedded Resources by setting the Build Action for the file to `EmbeddedResource`. EmbeddedResource Plugin provides two main methods for writing these resources: `ResourceWriter.WriteFile` and `ResourceWriter.WriteFolder`. 9 | 10 | The full name for a resource in the root of a project will be comprised of the assembly followed by the name of the file. An embedded resource located in a subdirectory of a project will include the relative path to the file in the name, using "." as a path delimeter. 11 | 12 | For example: 13 | ``` 14 | db.sqlite 15 | ``` 16 | in the root of MyProject.dll becomes 17 | ``` 18 | MyProject.db.sqlite 19 | ``` 20 | 21 | and 22 | ``` 23 | images\icon.png 24 | ``` 25 | in the images subdirectory of MyProject.dll becomes 26 | ``` 27 | MyProject.images.icon.png 28 | ``` 29 | 30 | The `WriteFile` and `WriteFolder` methods simplify decoding this naming scheme by allowing you to provide paths to a resource in a project, along with an output path, so that writing an embedded resource feels more like performing a copy operation. E.g. `writer.WriteFile("www\index.html", "webroot");` 31 | 32 | A simple C# class that uses EmbeddedResource Plugin for Xamarin and Windows to initialize data for an app might look like: 33 | ```csharp 34 | using PCLStorage; 35 | using Plugin.EmbeddedResource; 36 | 37 | namespace Sample 38 | { 39 | public class SampleLib 40 | { 41 | public SampleLib () 42 | { 43 | } 44 | 45 | public static async Task Init(Assembly assembly) { 46 | var rootFolder = FileSystem.Current.LocalStorage; 47 | 48 | var writer = new ResourceWriter (assembly); 49 | 50 | // Only need to write the bundled files once. 51 | if(await rootFolder.CheckExistsAsync("db.sqlite") == ExistenceCheckResult.NotFound) { 52 | await writer.WriteFile ("App_data/db.sqlite", rootFolder.Path); 53 | await writer.WriteFolder ("images", rootFolder.Path); 54 | } 55 | } 56 | } 57 | } 58 | ``` 59 | 60 | Notes on usage 61 | ============== 62 | When a file is embedded as a resource, it becomes impossible to differentiate a path delimiter "." from a file extension ".". For example, www/images/big.button.png looks just like www/images/big/button.png. For this reason, `WriteFolder` always treats the final "." in a resource name as the file extension delimiter by default. Calling `WriteFolder` with `recursive = false` will do a non-recursive copy of all resources in the specified source path (i.e., all instances of "." will be treated as part of the filename for every file in the specified folder). 63 | 64 | Because some platforms^H supported by PCL Storage provide only asynchronous file access, the `ResourceWriter.WriteFile` and `ResourceWriter.WriteFolder` APIs are also asynchronous. Anything you want to do in your app that depends on these resources having been written to disk (such as showing the data from the database) will need to await the completion of these calls. 65 | --------------------------------------------------------------------------------