ASP.NET Core applications have many scenarios for reading files, such as configuration files, static Web resource files (such as CSS, JavaScript, and image files) and View files for MVC applications, or even embedded resources directly compiled into assemblies. file. The reading of these files requires the use of an IFileProvider object. The IFileProvider object builds an abstract file system. We can not only use the unified API provided by it to read various types of files, but also monitor the changes of target files in time.

First, the tree hierarchy

The IFileProvider object builds a file system with a hierarchical directory structure for us. Since IFileProvider is an interface, it is an abstract file system built by it. The so-called directories and files are an abstract concept. The specific file may correspond to a physical file, may be stored in the database, or originate from the network, and may even not exist at all, and its content needs to be dynamically generated when reading. A directory is also just a logical container for organizing files. In order to give readers a general understanding of this file system, let’s first demonstrate a few simple examples.

All files managed by the file system are organized in the form of a directory, and an IFileProvider object can be considered as a mapping to a root directory. In addition to storing files, directories can also contain subdirectories, so the directories/files are hierarchically structured as a whole. Next we map an IFileProvider object to a physical directory and use it to present the structure of the directory in which it resides.

Our demo example is a normal console program. We defined the following IFileManager interface in the demo example, which uses a unique ShowStructure method to display the overall structure of the file system. This method has a parameter of type Action<int, string> that is responsible for presenting the node (directory or file) name of the file system. The two parameters of this Action<int, string> object represent the indented hierarchy and the name of the directory/file.

Public  interface IFileManager
{
    Void ShowStructure(Action< int , string > render);
}

We define the FileManager class as the default implementation of the IFileManager interface, which extracts the directory structure using the IFileProvider object represented by the read-only _fileProvider field. The overall structure of the target file system is presented recursively through the Render method, which involves a call to the GetDirectoryContents method of the IFileProvider object. This method returns an IDirectoryContents object representing the contents of the specified directory. If the corresponding directory exists, we can iterate over the object to get its subdirectories and files. The directory and file are finally represented by an IFileInfo object. As for whether the IFileInfo object corresponds to a directory or a file, it is distinguished by its IsDirectory property.

 

 

Public  class FileManager : IFileManager
{
    Private  readonly IFileProvider _fileProvider;
     public FileManager(IFileProvider fileProvider) => _fileProvider = fileProvider;
     public  void ShowStructure(Action< int , string > render)
    {
        Int indent = - 1 ;
        Render( "" );
         void Render( string subPath)
        {
            indent ++ ;
             the foreach ( var the fileInfo in _fileProvider.GetDirectoryContents (subPath))
            {
                Render(indent, fileInfo.Name);
                If (fileInfo.IsDirectory)
                {
                    Render($ @" {subPath}\{fileInfo.Name} " .TrimStart( ' \\ ' ));
                }
            }
            Indent -- ;
        }
    }        
}

 

 

Next we build a local physical directory “c:\test\” and create the corresponding subdirectories and files under it as shown in the following figure. We will map this directory to an IFileProvider object and use it to create the FileManager object above. We finally call the ShowStructure method of this FileManager object to render the directory structure.


ASP.NET_Core_3__File_System[1]__Abstract__File_System_2.png

 

The entire demo is reflected in the code snippet below. We created a PhysicalFileProvider object representing the physical file system for the directory “c:\test\” and registered it on the created ServiceCollection object. In addition, service registration for IFileManager/FileManager has been added to the ServiceCollection object.

 

 

Class Program
{
    Static  void Main()
    {
        Static  void Print( int layer, string name) => Console.WriteLine($ " {new string(' ', layer * 4)}{name} " );        
         new ServiceCollection()
            .AddSingleton <IFileProvider>( new PhysicalFileProvider( @" c:\test " ))
            .AddSingleton <IFileManager, FileManager> ()
            .BuildServiceProvider()
            .GetRequiredService <IFileManager> ()
            .ShowStructure(Print);
    }
}

 

 

We finally use the IServiceProvider object generated by ServiceCollection to get the FileManager object, and call the object’s ShowStructure method to render the directory structure of the PhysicalFileProvider object mapping. When we run the program, the console will present the output shown below, which shows us the actual structure of the mapped physical directory. (S501)


ASP.NET_Core_3__File_System[1]__Abstract__File_System_5.png

 

Second, read the file content

Earlier we demonstrated how to use the IFileProvider object to fully present the structure of the file system. Next we will demonstrate how to use it to read the contents of a physical file. We define the following ReadAllTextAsync method for IFileManager to read the contents of the specified file asynchronously. The parameters of the method represent the path of the file. As shown in the following code snippet, the ReadAllTextAsync method takes the specified file path as a parameter and calls the GetFileInfo method of the IFileProvider object to get an IFileInfo object. We finally call the CreateReadStream method of this IFileInfo object to get the output stream of the read file, and then get the real content of the file.

 

 

Public  interface IFileManager
{
    ...
    Task < string > ReadAllTextAsync( string path);
}

Public  class FileManager : IFileManager
{
    ...
    Public  async Task< string > ReadAllTextAsync( string path)
    {
        Byte [] buffer;
         using ( var stream = _fileProvider.GetFileInfo(path).CreateReadStream())
        {
            Buffer = new new  byte [Stream.length];
             the await stream.ReadAsync (Buffer, 0 , buffer.Length);
        }
        Return Encoding.Default.GetString(buffer);
    }
}

 

 

Suppose we still map the IFileProvider used by FileManager to the directory “c:\test\”. Now we create a text file named data.txt in the directory and write some content in the file. Next, we wrote the following program in the Main method to use the dependency injection method to get the FileManager object and read the contents of the file data.txt. The final debug assertion is intended to determine what is actually being read by the IFileProvider. (S502)

 

 

Class Program
{
    Static  async Task Main()
    {
        Var content = await  new ServiceCollection()
            .AddSingleton <IFileProvider>( new PhysicalFileProvider( @" c:\test " ))
            .AddSingleton <IFileManager, FileManager> ()
            .BuildServiceProvider()
            .GetRequiredService <IFileManager> ()
            .ReadAllTextAsync( " data.txt " );

        Debug.Assert(content == File.ReadAllText( @" c:\test\data.txt " ));
    }
}

 

 

Third, the embedded file system

We have been emphasizing that the IFileProvider structure is an abstract file system with a directory structure. The way a specific file is provided depends on how a particular IFileProvider object is. We demonstrate that the FileManager defined by the instance does not limit the specific type of IFileProvider that is specified in the application by dependency injection. Since the above application injects a PhysicalFileProvider object, we can use it to read a file in the corresponding physical directory. Suppose now that this data.txt is compiled directly into the assembly as a resource file, we need to use another implementation type called EmbeddedFileProvider . Now we will add this data.txt file directly to the project root of the console application. By default, when we compile a project, such a file does not become a resource file embedded in the target assembly. We need to use VS to set the “Build Action” property of the file as follows: Embedded resource”.


ASP.NET_Core_3__File_System[1]__Abstract__File_System_10.png

 

The settings shown above will be reflected in the project file (.csproj file). Specifically, the project file will add an <EmbeddedResource> element to set the file data.txt to the embedded resource file embedded in the compiled assembly.

< Project Sdk ="Microsoft.NET.Sdk" >
  ...
  < ItemGroup > 
      < EmbeddedResource Include ="data.txt" />    
  </ ItemGroup > 
</ Project >

We have written the following program to demonstrate the reading of resource files embedded in an assembly. We first get the current entry assembly and use it to create an EmbeddedFileProvider object that is registered with the ServiceCollection instead of the original PhysicalFileProvider object. We then use a completely consistent programming approach to get the FileManager object and use it to read the contents of the embedded file data.txt. In order to verify that the read target file is accurate, we obtained the content of the embedded file data.txt by directly reading the resource file, and used a debug assertion to determine the consistency of the two. (S503)

 

 

Class Program
{
    Static  async Task Main()
    {
        Var assembly = Assembly.GetEntryAssembly();

        Var content1 = await  new ServiceCollection()
            .AddSingleton <IFileProvider>( new EmbeddedFileProvider(assembly))
            .AddSingleton <IFileManager, FileManager> ()
            .BuildServiceProvider()
            .GetRequiredService <IFileManager> ()
            .ReadAllTextAsync( " data.txt " );

        Var stream = assembly.GetManifestResourceStream($ " {assembly.GetName().Name}.data.txt " );
         var buffer = new  byte [stream.Length];
        stream.Read(buffer, 0 , buffer.Length);
         var content2 = Encoding.Default.GetString(buffer);

        Debug.Assert(content1 == content2);
    }
}

 

 

Fourth, the monitoring file changes

In the file reading scenario, it is a common requirement to determine the consistency of the data loaded into memory with the source file and to automatically synchronize. For example, we define the configuration in a JSON file. When the application starts, it will read the file and convert it into the corresponding Options object. In many cases, if we change the configuration file, the latest configuration data will not take effect until the application is restarted. If we can monitor the configuration file in an efficient way and send notifications to the application if it changes, the application can re-read the configuration file without restarting, thus implementing the Options object. The content is fully synchronized with the original configuration file.

Monitoring the file system and sending notifications when it changes are also one of the core features provided by the IFileProvider object. Next we still use the previous program to demonstrate how to use PhysicalFileProvider to monitor a physical file and re-read the new content when the content of the target file changes.

 

 

Class Program
{
    Static  async Task Main()
    {
        Using ( var fileProvider = new PhysicalFileProvider( @" c:\test " ))
        {
            String original = null ;
            ChangeToken.OnChange(() => fileProvider.Watch( " data.txt " ), Callback);
             while ( true )
            {
                File.WriteAllText( @" c:\test\data.txt " , DateTime.Now.ToString());
                 await Task.Delay( 5000 );
            }

            Async  void Callback()
            {
                Var stream = fileProvider.GetFileInfo( " data.txt " ).CreateReadStream();
                {
                    Var buffer = new  byte [stream.Length];
                     await stream.ReadAsync(buffer, 0 , buffer.Length);
                     string current = Encoding.Default.GetString(buffer);
                     if (current != original)
                    {
                        Console.WriteLine(original = current);
                    }
                }
            }
        }
    }
}

 

 

As shown in the code snippet above, we created a PhysicalFileProvider object for the directory “c:\test” and called its Watch method to monitor the specified file data.txt. This method returns an IChangeToken object, and we are using this object to receive notifications of file changes. We call ChangeToken ‘s static method OnChange to register a callback for this object to re-read and display the source file. When the source file changes, the registered callback will be executed automatically. We modify the file data.txt every 5 seconds, and the content of the file is the current time. So when our program starts, the current time will be presented on the console as shown below every 5 seconds.


ASP.NET_Core_3__File_System[1]__Abstract__File_System_15.png

 

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-01.html