A physical file can be embedded as a resource directly into the compiled assembly. With the help of EmbeddedFileProvider, we can use a unified programming method to read embedded resource files. This type is defined in the NuGet package “Microsoft.Extensions.FileProviders.Embedded”. Before introducing EmbeddedFileProvider formally, we must know how to embed a project file as a resource into a compiled assembly.

First, turn the project file into an embedded resource

By default, the static files we add to a .NET Core project do not become embedded resource files for the target assembly. If you need to use a static file as an embedded file of the target assembly, we need to modify the .csproj file corresponding to the current project. Specifically, we need to add <ItemGroup> / <EmbeddedResource> elements to the .csproj file as shown in the previous example, and use the Include attribute to explicitly include the corresponding resource file. When we directly use Visual Studio to set the Build Action property of the resource file to “Embedded resource”, the IDE will automatically help us modify the project file.


ASP.NET_Core_3_File_System_[4]_Embedded_File_System_0.png

 

The Include attribute of <EmbeddedResource> can set multiple paths, and semi-colons (“;”) are used as delimiters between paths. The directory structure shown in the above figure is taken as an example. If we need to use the four files in the root directory as the embedded files of the assembly, we can modify the .csproj file and include the paths of the four files as follows.

 

 

< Project Sdk = "Microsoft.NET.Sdk" >
    ...
    < ItemGroup > 
        < EmbeddedResource  
             Include = `` root / dir1 / foobar / foo.txt; root / dir1 / foobar / bar.txt; root / dir1 / baz.txt; root / dir2 / qux.txt " > </ EmbeddedResource >  
    < / ItemGroup > 
</ Project >

 

 

In addition to specifying the path of each resource file that needs to be embedded, we can also use a Globbing Pattern expression based on wildcards “*” and “**” to include a set of matching files in batches. Similarly, all files in the root directory are used as embedded files in the assembly. The following definition method is much simpler.

< Project Sdk = "Microsoft.NET.Sdk" >
    ...
    < ItemGroup > 
        < EmbeddedResource   Include = "root / **" > </ EmbeddedResource >  
    </ ItemGroup > 
</ Project >

<EmbeddedResource> In addition to having an Include attribute to add embedded resource files, it also has another Exclude attribute responsible for excluding files that do not meet the requirements. Taking the previous project as an example, for the four files in the root directory, if we do not want the file baz.txt as an embedded resource file, we can exclude it as follows.

< Project Sdk = "Microsoft.NET.Sdk" >
    ...
    < ItemGroup > 
        < EmbeddedResource   Include = "root / **" Exclude = "root / dir1 / baz.txt" > </ EmbeddedResource >  
    </ ItemGroup > 
</ Project >

Reading resource files

Each assembly has a manifest file. One of its important functions is to record all the file members that make up the assembly. In general, an assembly is mainly composed of two types of files, which are a managed module file that carries IL code and a resource file embedded at compile time. For the project structure shown in the figure above, if we embed four text files in the form of resource files into the generated assembly (App.dll), the manifest file of the assembly will be recorded in the form shown below they.

 

 

.mresource public App.root.dir1.baz.txt
{
  // Offset: 0x00000000 Length: 0x0000000C
}
.mresource public App.root.dir1.foobar.bar.txt
{
  // Offset: 0x00000010 Length: 0x0000000C
}
.mresource public App.root.dir1.foobar.foo.txt
{
  // Offset: 0x00000020 Length: 0x0000000C
}
.mresource public App.root.dir2.qgux.txt
{
  // Offset: 0x00000030 Length: 0x0000000C
}

 

 

Although the files have a hierarchical directory structure in the original project, when they are successfully transferred to the compiled assembly, the directory structure will no longer exist, and all embedded files will be stored in the same container. If we open the assembly through Reflector, the flat storage of resource files will be clear at a glance. To avoid naming conflicts, the compiler will rename the resource file according to the path where the original file is located. The specific rule is ” {BaseNamespace}. {Path} “, and the directory separator will be converted into “.”. It is worth emphasizing that the prefix of the resource file name is not the name of the assembly, but the name of the base namespace we set for the project .


ASP.NET_Core_3_File_System_[4]_Embedded_File_System_5.png

 

The Assembly object representing the assembly defines the following methods to extract the relevant information of the file of the embedded resource and read the content of the specified resource file. The GetManifestResourceNames method helps us to obtain the resource file names recorded in the assembly manifest file, and another method, GetManifestResourceInfo, is used to obtain the description information of the specified resource file. If we need to read the content of a resource file, we can call the GetManifestResourceStream method with the resource file name as a parameter . This method will return a Stream object that reads the content of the file.

public  abstract  class Assembly
{   
    public  virtual  string [] GetManifestResourceNames ();
     public  virtual ManifestResourceInfo GetManifestResourceInfo ( string resourceName);
     public  virtual Stream GetManifestResourceStream ( string name);
}

Also for the directory structure corresponding to the previous demo project, when the four files are successfully transferred as embedded files to the compiled assembly, we can call the assembly object’s GetManifestResourceNames method to obtain the resources of the four embedded files. name. If you call the GetManifestResourceStream method with the resource name (“App.root.dir1.foobar.foo.txt”) as a parameter, we can read the content of the resource file. The specific demonstration is shown below.

 

 

class Program
{
    static  void Main ()
    {
        var assembly = typeof (Program) .Assembly;
         var resourceNames = assembly.GetManifestResourceNames ();
        Debug.Assert (resourceNames.Contains ( " App.root.dir1.foobar.foo.txt " ));
        Debug.Assert (resourceNames.Contains ( " App.root.dir1.foobar.bar.txt " ));
        Debug.Assert (resourceNames.Contains ( " App.root.dir1.baz.txt " ));
        Debug.Assert (resourceNames.Contains ( " App.root.dir2.qgux.txt " ));

        var stream = assembly.GetManifestResourceStream ( " App.root.dir1.foobar.foo.txt " );
         var buffer = new  byte [stream.Length];
        stream.Read (buffer, 0 , buffer.Length);
         var content = Encoding.Default.GetString (buffer);  
        Debug.Assert (content == File.ReadAllText ( " App / root / dir1 / foobar / foo.txt " ));
    }
}

 

 

Third, EmbeddedFileProvider

After a general understanding of the resource files embedded in the assembly, the implementation principle of EmbeddedFileProvider is well understood. Because the resource file embedded in the assembly uses a flat storage form, there is no concept of a directory hierarchy in the file system built by the EmbeddedFileProvider. We can think that all the resource files are stored in the “root directory” of the assembly. For the file system built by EmbeddedFileProvider, the IFileInfo object it provides is always a description of a specific resource file. This is an EmbeddedResourceFileInfo object with the following definition.

 

 

public  class EmbeddedResourceFileInfo: IFileInfo
{
    private  readonly Assembly _assembly;
     private  long ? _length;
     private  readonly  string   _resourcePath;

    public EmbeddedResourceFileInfo (Assembly assembly, string resourcePath, string name, DateTimeOffset lastModified)
    {
        _assembly = assembly;
        _resourcePath = resourcePath;
         this .Name = name;
         this .LastModified = lastModified;
    }

    public Stream CreateReadStream ()
    {
        Stream stream = _assembly.GetManifestResourceStream (_resourcePath);
         if (! This ._length.HasValue)
        {
            this ._length = new  long ? (stream.Length);
        }
        return stream;
    }
    
    public  bool Exists => true ;
     public  bool IsDirectory => false ;
     public DateTimeOffset LastModified { get ;}    

    public  string Name { get ;}
     public  string PhysicalPath => null ;
     public  long Length
    {
        get
        {
            if (! _length.HasValue)
            {
                using (Stream stream = _assembly.GetManifestResourceStream ( this ._resourcePath))
                {
                    _length = stream.Length;
                }
            }
            rReturn _length.Value;
        }
    }
}

 

 

As shown in the above code snippet, when we create an EmbeddedResourceFileInfo object, we need to specify the path (resourcePath) of the embedded resource file in the manifest file, the assembly it is in, the name of the resource file (name), and the last modification as the file DateTimeOffset object of time. Because an EmbeddedResourceFileInfo object always corresponds to a specific embedded resource file, its Exists property always returns True, and IsDirectory property returns False. Because the resource file system does not have a hierarchical directory structure, its so-called physical path is meaningless, so the PhysicalPath property returns Null directly. The CreateReadStream method returns the output stream returned by the assembly’s GetManifestResourceStream method, and Length, which represents the length of the file, returns the length of this Stream object.

The definition of EmbeddedFileProvider is shown below. When we create an EmbeddedFileProvider object, in addition to specifying the assembly where the resource file is located, we can also specify a base namespace. If the namespace is not explicitly set, the name of the assembly is used as the namespace by default, that is, if we specify a base namespace for the project that is different from the assembly name, then when creating the EmbeddedFileProvider object, You must specify this namespace.

 

 

public  class EmbeddedFileProvider: IFileProvider
{   
    public EmbeddedFileProvider (Assembly assembly);
     public EmbeddedFileProvider (Assembly assembly, string baseNamespace);

    public IDirectoryContents GetDirectoryContents ( string subpath);
     public IFileInfo GetFileInfo ( string subpath);
     public IChangeToken Watch ( string pattern);
}

 

 

When we call the GetFileInfo method of EmbeddedFileProvider and specify the logical name of the resource file, this method will together with the namespace form the name of the resource file in the assembly manifest (the path separator will be replaced with “.”). If the corresponding resource file exists, an EmbeddedResourceFileInfo will be created and returned, otherwise a NotFoundFileInfo object will be returned. For the embedded resource file system, there is no such thing as a file update problem, so its Watch method returns an IChangeToken object whose HasChanged property is always False.

Because the resource file embedded in the assembly is always read-only, its so-called last modification time is actually the date when the assembly was generated. Therefore, when the EmbeddedFileProvider provides the EmbeddedResourceFileInfo object, the last update time of the assembly file is used as the resource file. Last updated time. If this time cannot be parsed correctly, the LastModified property of EmbeddedResourceFileInfo will be set to the current UTC time.

Because the embedded resource file system built by EmbeddedFileProvider does not have a hierarchical directory structure, all resource files can be considered as all stored in the “root directory” of the assembly, so its GetDirectoryContents method is only available when we specify an empty string or “/” (The empty string and “/” both represent “root directory”) will return a DirectoryContents object describing this “root directory”, which is actually a collection of EmbeddedResourceFileInfo objects. In other cases, the GetDirectoryContents method of the EmbeddedFileProvider always returns a NotFoundDirectoryContents object.

ASP.Net Core3 File system[1] Abstract File System

ASP.Net Core3 File system[2] Overall design

ASP.Net Core 3 File System [3] Physical File System

ASP.Net Core3 File system[4] Embedded File systeme System

 

Orignal link:https://www.cnblogs.com/artech/p/inside-asp-net-core-04-04.html