locked
Unsure how to embed resources in a hierarchical way RRS feed

  • Question

  • I am working with a desktop forms application in Visual Studio 2019, written in VB.net
    Part of the application operates as a web-server.

    Currently, the web-server serves files from a 'Webroot' folder in the file system.
    Many of the files are organized nicely into subfolders below the Webroot  for lib, css, script, images etc...

    Having these files in the file system is great for development but I now need to embed them as resources in a release executable to make the application more resilient against tampering.

    I have dragged the Webroot folder structure into my project and set the build action on all the files to 'Embedded Resource'.

    I want to be able to access the files as resources in a similarly hierarchical way, by code like:
    Dim Target = System.Reflection.Assembly.GetExecutingAssembly.GetManifestResourceStream("Webroot.js.lib.jquery-3.2.1.min.js)

    ISSUE:
    My problem is that the compiler seems to be flattening the folder structure, putting all embedded resource files at the same level i.e. the file in the example above could only be loaded by: 
    Dim Target = System.Reflection.Assembly.GetExecutingAssembly.GetManifestResourceStream("jquery-3.2.1.min.js").


    I first saw this issue because our web designer had accidentally left copies of the same file in two sub-folders and the compiler gave error:
    "Error BC31502 Resource name 'Crimp_Centre.ajax-loader.gif' cannot be used more than once."

    It is easy to repro the issue if necessary.
    REPRO:
    In VS2019
    File..New Project  then use template {Windows Forms App (.NET Framework)  Desktop, Visual Basic, Windows }
    In Solution Explorer:
    Rt-click the project node and Add..New Folder. It should appear with default name NewFolder1
    Rt-click NewFolder1 and Add..New Folder. It should appear with default name NewFolder1. Rename it SubFolder1 to make things less confusing.
    Rt-Click SubFolder1 and Add..New Item. {General.. Text File}. It should create a file 'TextFile1.txt'.
    Click TextFile1.txt and (in properties window) change its Build Action to 'Embedded Resource'
    At this point the project will compile and run.
    Rt-Click NewFolder1 and Add..New Item. {General.. Text File}. It should create a file 'TextFile1.txt'.
    Click on the newly created TextFile1.txt and (in properties window) change its Build Action to 'Embedded Resource'
    We now have identically named files at different levels in the resource folder structure.
    Attempting to compile produces Error "BC31502 Resource name 'WindowsApp1.TextFile1.txt' cannot be used more than once."

    QUESTION:
    Please can anyone show me how to embed resources in a hierarchical way, I would like to mirror the folder structure we have in the file system?
    I understand embedded resources can be stored and referenced hierarchically in a C# project but my project has tens of thousands of lines of VB code and I have practically no chance of rewriting it in C#.
    I suspect there could be a compiler switch needs changing but I cannot see it.


    Wednesday, June 3, 2020 10:55 AM

Answers

  • Thanks for your time and efforts Karen and Max-44.
    I am too stupid or inexperienced to understand how your answers could solve my problem, however they did give me confidence and inspiration to try other approaches.
    I worked around this problem by:
    1) Packing the whole website structure into a zip file and adding that to the VB project as an embedded resource.
    2) Writing code to extract the zip file to the file system and then expand it into a folder as my application starts up.
    It only takes a couple of seconds to create the folder structure and the exe file has shrunk from 54MB to 13MB because the web files are packed into it much more efficiently.

    I will try to paste a copy of the code below in case it might help others:
        Private Function InstallWebRootFolder() As String
            ' Using File Manager, compress the website's root folder including all its subfolders into a single file WebRoot.zip 
            ' Add the zip file to the Visual Studio project and set its Build Action property to 'Embedded Resource'
            ' Call this function as the application starts up to expand the embedded resource into a File System temp folder with subfolders.
            ' This function returns the path of the root folder (for the web server to use)
            Dim ApplicationNamespace = Me.GetType.Namespace
            Dim WebRootFoldershortName = "WebRoot"
            Dim ZipFileShortName = "WebRoot.zip"
            Dim ZipFileResourceName = ApplicationNamespace & "." & ZipFileShortName
            Dim TempFolderPath = IO.Path.GetTempPath()
            Dim ApplicationTempPath = TempFolderPath & ApplicationNamespace
            Dim ZipFileFullName = ApplicationTempPath & "\" & ZipFileShortName
            Dim WebRootFolderPath = ApplicationTempPath & "\" & WebRootFoldershortName
            Dim ZipFileileStream As IO.Stream = System.Reflection.Assembly.GetExecutingAssembly.GetManifestResourceStream(ZipFileResourceName) ' get the embedded 'zipped file' resource
            IO.Directory.CreateDirectory(ApplicationTempPath) ' create application temp folder (if necessary)
            Dim fs As New IO.FileStream(ZipFileFullName, System.IO.FileMode.Create) ' write the zipfile out to the application temp folder
            ZipFileileStream.CopyTo(fs)
            ZipFileileStream.Close()
            fs.Close()
            If IO.Directory.Exists(WebRootFolderPath) Then ' nuke everything in the WebRootFolder
                For Each d In System.IO.Directory.GetDirectories(WebRootFolderPath)
                    IO.Directory.Delete(d, True)
                Next
                For Each f In System.IO.Directory.GetFiles(WebRootFolderPath)
                    IO.File.Delete(f)
                Next
            End If
            IO.Compression.ZipFile.ExtractToDirectory(ZipFileFullName, WebRootFolderPath)  ' expand zip contents to the WebRootFolder, creating the WebRootFolder if necessary
            Return WebRootFolderPath
        End Function
    

    • Marked as answer by Andy_B Wednesday, June 3, 2020 9:27 PM
    Wednesday, June 3, 2020 9:19 PM

All replies

  • Hello,

    As you have learned C# and VB handling resources differently (see below) so this mean you need to consider another solution other than embedded resources e.g. create the folder structure manually then copy the files into these folders

    C# path for resources

    RootNameSpace.Path.FileName.Extension

    VB

    RootNameSpace.FileName.Extension



    Please remember to mark the replies as answers if they help and unmarked them if they provide no help, this will help others who are looking for solutions to the same or similar problem. Contact via my Twitter (Karen Payne) or Facebook (Karen Payne) via my MSDN profile but will not answer coding question on either.

    NuGet BaseConnectionLibrary for database connections.

    StackOverFlow
    profile for Karen Payne on Stack Exchange


    Wednesday, June 3, 2020 12:34 PM
  • Hi Karen,
    Thanks for answering, you have given me a lot of hope that there is an easy solution but I can't quite grasp what you mean by "create the folder structure manually then copy the files into these folders". I thought I was doing something equivalent to that in my repro.

    Where should the manually created folder structure be located and (if it isn't obvious) how should I create it?
    How should I copy the files into this structure and how should I reference them in code?
    Thanks,  Andy


     

    Wednesday, June 3, 2020 1:00 PM
  • I don't have time for a step-by-step but here are the basics.

    Create the folders needed in your project then place files in them. Next use post build events to copy the folder structure to the app folder then work with publishing to include the files.

    Here is a super simple example to ensure a folder exists, if not create it then copy files to that folder. Look at xcopy command arguments to figure out doing what you needed.

    Alternately

    https://stackoverflow.com/questions/18601639/how-to-include-custom-folders-when-publishing-a-mvc-application


    Please remember to mark the replies as answers if they help and unmarked them if they provide no help, this will help others who are looking for solutions to the same or similar problem. Contact via my Twitter (Karen Payne) or Facebook (Karen Payne) via my MSDN profile but will not answer coding question on either.

    NuGet BaseConnectionLibrary for database connections.

    StackOverFlow
    profile for Karen Payne on Stack Exchange

    Wednesday, June 3, 2020 1:44 PM
  • Rather than trying to address the resource as a File just ask the ClassLoader to return an InputStream for the resource instead via getResourceAsStream:

    InputStream in = getClass().getResourceAsStream("/file.txt"); 
    BufferedReader reader = new BufferedReader(new InputStreamReader(in));
    As long as the file.txt resource is available on the classpath then this approach will work the same way regardless of whether the file.txt resource is in a classes/ directory or inside a jar.

    The URI is not hierarchical occurs because the URI for a resource within a jar file is going to look something like this: file:/example.jar!/file.txt. You cannot read the entries within a jar (a zip file) like it was a plain old File.
    Wednesday, June 3, 2020 2:34 PM
  • Thanks for your time and efforts Karen and Max-44.
    I am too stupid or inexperienced to understand how your answers could solve my problem, however they did give me confidence and inspiration to try other approaches.
    I worked around this problem by:
    1) Packing the whole website structure into a zip file and adding that to the VB project as an embedded resource.
    2) Writing code to extract the zip file to the file system and then expand it into a folder as my application starts up.
    It only takes a couple of seconds to create the folder structure and the exe file has shrunk from 54MB to 13MB because the web files are packed into it much more efficiently.

    I will try to paste a copy of the code below in case it might help others:
        Private Function InstallWebRootFolder() As String
            ' Using File Manager, compress the website's root folder including all its subfolders into a single file WebRoot.zip 
            ' Add the zip file to the Visual Studio project and set its Build Action property to 'Embedded Resource'
            ' Call this function as the application starts up to expand the embedded resource into a File System temp folder with subfolders.
            ' This function returns the path of the root folder (for the web server to use)
            Dim ApplicationNamespace = Me.GetType.Namespace
            Dim WebRootFoldershortName = "WebRoot"
            Dim ZipFileShortName = "WebRoot.zip"
            Dim ZipFileResourceName = ApplicationNamespace & "." & ZipFileShortName
            Dim TempFolderPath = IO.Path.GetTempPath()
            Dim ApplicationTempPath = TempFolderPath & ApplicationNamespace
            Dim ZipFileFullName = ApplicationTempPath & "\" & ZipFileShortName
            Dim WebRootFolderPath = ApplicationTempPath & "\" & WebRootFoldershortName
            Dim ZipFileileStream As IO.Stream = System.Reflection.Assembly.GetExecutingAssembly.GetManifestResourceStream(ZipFileResourceName) ' get the embedded 'zipped file' resource
            IO.Directory.CreateDirectory(ApplicationTempPath) ' create application temp folder (if necessary)
            Dim fs As New IO.FileStream(ZipFileFullName, System.IO.FileMode.Create) ' write the zipfile out to the application temp folder
            ZipFileileStream.CopyTo(fs)
            ZipFileileStream.Close()
            fs.Close()
            If IO.Directory.Exists(WebRootFolderPath) Then ' nuke everything in the WebRootFolder
                For Each d In System.IO.Directory.GetDirectories(WebRootFolderPath)
                    IO.Directory.Delete(d, True)
                Next
                For Each f In System.IO.Directory.GetFiles(WebRootFolderPath)
                    IO.File.Delete(f)
                Next
            End If
            IO.Compression.ZipFile.ExtractToDirectory(ZipFileFullName, WebRootFolderPath)  ' expand zip contents to the WebRootFolder, creating the WebRootFolder if necessary
            Return WebRootFolderPath
        End Function
    

    • Marked as answer by Andy_B Wednesday, June 3, 2020 9:27 PM
    Wednesday, June 3, 2020 9:19 PM