ASP.NET Core 파일 공급자

등록일시: 2017-09-04 08:00,  수정일시: 2017-10-25 12:29
조회수: 4,038
이 문서는 ASP.NET Core 기술을 널리 알리고자 하는 개인적인 취지로 제공되는 번역문서입니다. 이 문서에 대한 모든 저작권은 마이크로소프트에 있으며 요청이 있을 경우 언제라도 게시가 중단될 수 있습니다. 번역 내용에 오역이 존재할 수 있고 주석은 번역자 개인의 의견일 뿐이며 마이크로소프트는 이에 관한 어떠한 보장도 하지 않습니다. 번역이 완료된 이후에도 대상 제품 및 기술이 개선되거나 변경됨에 따라 원문의 내용도 변경되거나 보완되었을 수 있으므로 주의하시기 바랍니다.
본문에서는 ASP.NET Core의 세 가지 파일 공급자인 물리적 공급자, 임베디드 공급자 및 복합 공급자에 관해서 살펴봅니다.

ASP.NET Core는 파일 공급자를 통해서 파일 시스템에 대한 접근을 추상화합니다.

예제 코드 살펴보기 및 다운로드

파일 공급자 추상

파일 공급자는 파일 시스템에 대한 추상화입니다. 그리고 그 중 가장 핵심적인 인터페이스는 IFileProvider입니다. IFileProvider는 파일 정보(IFileInfo)나 디렉터리 정보(IDirectoryContents)를 얻고, 변경 알림을 설정하기 위한 메서드를 (IChangeToken을 이용하는) 노출합니다.

IFileInfo는 개별 파일 및 디렉터리에 관한 메서드와 속성들을 제공합니다. 두 가지 불리언 속성인 Exists 속성과 IsDirectory 속성을 비롯해서 파일 이름 (Name), 크기 (Length, 바이트), 마지막 수정 날짜 (LastModified) 및 물리적 경로를 (PhysicalPath) 기술하는 속성들을 갖고 있습니다. CreateReadStream 메서드를 사용해서 파일을 읽을 수도 있습니다.

파일 공급자 구현

IFileProvider의 구현으로는 물리적 공급자, 임베디드 공급자 그리고 복합 공급자의 세 가지 공급자를 사용할 수 있습니다. 먼저 물리적 공급자는 시스템의 실제 파일에 접근하기 위해서 사용됩니다. 그리고 임베디드 공급자는 어셈블리에 포함된 파일에 접근하기 위해서 사용됩니다. 마지막으로 복합 공급자는 하나 이상의 개별 공급자로부터 얻어진 파일 및 디렉터리에 대한 복합적인 접근을 지원하기 위해서 사용됩니다.

PhysicalFileProvider

PhysicalFileProvider는 물리적 파일 시스템에 대한 접근을 제공합니다. System.IO.File 형식을 (물리적 공급자에 대한) 래핑하고, 특정 디렉터리와 그 하위의 자식들에 대한 모든 경로를 대상 범위로 지정합니다. 이렇게 범위를 지정함으로써 특정 디렉터리 및 그 하위 자식들에 대한 접근만 허용하여 경계 외부의 파일 시스템에 대한 접근을 차단합니다. 이 공급자의 인스턴스를 생성하려면 공급자를 대상으로 하는 모든 요청에 대한 기본 경로 역할을 하는 (그리고 해당 경로 외부에 대한 접근은 차단하는) 디렉터리 경로를 반드시 제공해야 합니다. ASP.NET Core 응용 프로그램에서는 PhysicalFileProvider의 인스턴스를 직접 생성하거나, 컨트롤러 또는 서비스의 생성자에서 의존성 주입을 통해서 IFileProvider를 요청할 수 있습니다. 일반적으로 후자의 접근 방식이 보다 유연하고 테스트 가능한 솔루션을 만들어줍니다.

다음 예제는 PhysicalFileProvider를 생성하는 방법을 보여줍니다.

IFileProvider provider = new PhysicalFileProvider(applicationRoot);
IDirectoryContents contents = provider.GetDirectoryContents(""); // the applicationRoot contents
IFileInfo fileInfo = provider.GetFileInfo("wwwroot/js/site.js"); // a file under applicationRoot

또한 하위 경로를 지정해서 디렉터리의 내용을 반복 조회하거나 특정 파일의 정보를 가져올 수도 있습니다.

컨트롤러에서 공급자를 사용하려면 공급자를 컨트롤러의 생성자 매개 변수로 지정한 다음, 전달받은 공급자 개체를 필드에 할당합니다. 그리고 액션 메서드에서는 이 지역 인스턴스를 사용합니다:

public class HomeController : Controller
{
    private readonly IFileProvider _fileProvider;

    public HomeController(IFileProvider fileProvider)
    {
        _fileProvider = fileProvider;
    }

    public IActionResult Index()
    {
        var contents = _fileProvider.GetDirectoryContents("");
        return View(contents);
    }

그런 다음, 응용 프로그램의 Startup 클래스에서 공급자를 생성합니다:

using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;

namespace FileProviderSample
{
    public class Startup
    {
        private IHostingEnvironment _hostingEnvironment;
        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
                .AddEnvironmentVariables();
            Configuration = builder.Build();

            _hostingEnvironment = env;
        }

        public IConfigurationRoot Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // Add framework services.
            services.AddMvc();

            var physicalProvider = _hostingEnvironment.ContentRootFileProvider;
            var embeddedProvider = new EmbeddedFileProvider(Assembly.GetEntryAssembly());
            var compositeProvider = new CompositeFileProvider(physicalProvider, embeddedProvider);

            // choose one provider to use for the app and register it
            services.AddSingleton<IFileProvider>(physicalProvider);
            //services.AddSingleton<IFileProvider>(embeddedProvider);
            //services.AddSingleton<IFileProvider>(compositeProvider);
        }

다음 Index.cshtml 뷰는 전달된 IDirectoryContents를 반복 조회합니다:

@using Microsoft.Extensions.FileProviders
@model IDirectoryContents

<h2>Folder Contents</h2>

<ul>
    @foreach (IFileInfo item in Model)
    {
        if (item.IsDirectory)
        {
            <li><strong>@item.Name</strong></li>
        }
        else
        {
            <li>@item.Name - @item.Length bytes</li>
        }
    }
</ul>

그 결과는 다음과 같습니다:

File provider sample application listing physical files and folders

EmbeddedFileProvider

EmbeddedFileProvider는 어셈블리에 포함된 파일에 접근하기 위한 용도로 사용됩니다. .NET Core에서는 .csproj 파일의 <EmbeddedResource> 요소에 지정된 파일들이 어셈블리에 포함됩니다:

<ItemGroup>
  <EmbeddedResource Include="Resource.txt;**\*.js" Exclude="bin\**;obj\**;**\*.xproj;packages\**;@(EmbeddedResource)" />
  <Content Update="wwwroot\**\*;Views\**\*;Areas\**\Views;appsettings.json;web.config">
    <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
  </Content>
</ItemGroup>

어셈블리에 포함시킬 파일들을 지정할 때, Globbing 패턴을 사용할 수 있습니다. 이 패턴을 사용하면 하나 이상의 파일을 지정할 수 있습니다.

노트

실제로 프로젝트의 모든 .js 파일을 어셈블리에 포함시키고 싶지는 않을 것입니다. 이번 예제는 단지 설명을 위한 것입니다.

EmbeddedFileProvider를 생성할 때, 생성자에게 읽고자 하는 어셈블리를 전달해야 합니다.

var embeddedProvider = new EmbeddedFileProvider(Assembly.GetEntryAssembly());

이 스니핏은 현재 실행중인 어셈블리에 접근하는 EmbeddedFileProvider를 생성하는 방법을 보여줍니다.

앞의 예제 응용 프로그램을 EmbeddedFileProvider를 사용하도록 변경하면 다음과 같은 결과가 출력됩니다:

File provider sample application listing embedded files

노트

어셈블리에 포함된 리소스는 디렉터리를 노출하지 않는 반면, 파일명 자체에 . 구분자를 이용한 리소스의 경로가 (네임스페이스로) 포함되어 있습니다.

EmbeddedFileProvider의 생성자에 선택적 baseNamespace 매개 변수를 전달할 수도 있습니다. 이 매개 변수를 지정하면 전달된 네임스페이스 하위의 리소스들만을 대상으로 GetDirectoryContents 호출의 범위가 제한됩니다.

CompositeFileProvider

CompositeFileProviderIFileProvider의 인스턴스들을 결합해서 다수의 공급자를 이용한 파일 작업을 처리할 수 있는 단일 인터페이스를 제공합니다. CompositeFileProvider를 생성할 때는 생성자에 하나 이상의 IFileProvider 인스턴스를 전달합니다:

var physicalProvider = _hostingEnvironment.ContentRootFileProvider;
var embeddedProvider = new EmbeddedFileProvider(Assembly.GetEntryAssembly());
var compositeProvider = new CompositeFileProvider(physicalProvider, embeddedProvider);

이전 절들에서 구성한 물리적 공급자와 임베디드 공급자를 모두 포함하는 CompositeFileProvider를 사용하도록 예제 응용 프로그램을 수정해보면 다음과 같은 결과가 출력됩니다:

File provider sample application listing both physical files and folders and embedded files

변경 사항 감지하기

IFileProviderWatch 메서드를 이용하면 여러 파일 및 디렉터리의 변경 사항을 감지할 수 있습니다. 이 메서드는 Globbing 패턴을 이용해서 복수의 파일을 지정할 수 있는 경로 문자열을 전달받고 IChangeToken을 반환합니다. 이 토큰은 변경 여부 확인에 사용할 수 있는 HasChanged 속성과, 지정된 경로 문자열에서 변경 내용이 감지되면 호출되는 RegisterChangeCallback 메서드를 제공합니다. 각 변경 토큰은 단일 변경에 대한 응답으로 자신과 연결된 콜백만 호출한다는 점에 주의하시기 바랍니다. 모니터링을 지속적으로 수행하기 위해서는 다음 예제처럼 TaskCompletionSource를 활용하거나 변경 사항에 대한 응답에서 다시 IChangeToken 인스턴스를 생성해야 합니다.

다음 예제는 텍스트 파일이 수정될 때마다 콘솔 응용 프로그램이 메시지를 표시하도록 구성됩니다:

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Primitives;

namespace WatchConsole
{
    public class Program
    {
        private static PhysicalFileProvider _fileProvider = 
            new PhysicalFileProvider(Directory.GetCurrentDirectory());
        public static void Main(string[] args)
        {
            Console.WriteLine("Monitoring quotes.txt for changes (ctrl-c to quit)...");
            while (true)
            {
                MainAsync().GetAwaiter().GetResult();
            }
        }

        private static async Task MainAsync()
        {
            IChangeToken token = _fileProvider.Watch("quotes.txt");
            var tcs = new TaskCompletionSource<object>();
            token.RegisterChangeCallback(state => 
                ((TaskCompletionSource<object>)state).TrySetResult(null), tcs);
            await tcs.Task.ConfigureAwait(false);
            Console.WriteLine("quotes.txt changed");
        }
    }
}

파일을 여러 번 저장한 결과는 다음과 같습니다:

Command window after executing dotnet run shows the application monitoring the quotes.txt file for changes and that the file has changed five times.

노트

Docker 컨테이너나 네트워크 공유 같은 일부 파일 시스템은 변경 알림을 안정적으로 전송할 수 없습니다. 그러나 DOTNET_USE_POLLINGFILEWATCHER 환경 변수를 1이나 true로 설정하면 파일 시스템이 4 초마다 폴링됩니다.

Globbing 패턴

파일 시스템 경로는 Globbing 패턴이라고도 부르는 와일드 카드 패턴을 사용합니다. 이 단순 패턴을 이용해서 파일 그룹을 지정할 수 있습니다. 두 개의 와일드 카드 문자는 *** 입니다.

*

현재 폴더 수준의 모든 항목, 모든 파일명 또는 모든 파일 확장자와 일치합니다. 파일 경로의 /. 문자에 의해서 일치가 중단됩니다.

**

여러 디렉터리 수준의 모든 항목과 일치합니다. 디렉터리 계층 내에서 많은 파일들을 재귀적으로 일치시키기 위해서 사용할 수 있습니다.

Globbing 패턴 예제

directory/file.txt

지정한 디렉터리의 지정한 파일과 일치합니다.

directory/*.txt

지정한 디렉터리에 존재하는 확장자가 .txt인 모든 파일과 일치합니다.

directory/*/bower.json

directory 디렉터리의 한 수준 아래의 모든 하위 디렉터리들의 모든 bower.json 파일과 일치합니다.

directory/**/*.txt

directory 디렉터리의 하위의 모든 위치에 존재하는 확장자가 .txt인 모든 파일과 일치합니다.

ASP.NET Core의 파일 공급자 활용

ASP.NET Core는 다양한 부분에서 파일 공급자를 활용합니다. 예를 들어서, IHostingEnvironment는 응용 프로그램의 콘텐츠 루트와 웹 루트를 IFileProvider 형식으로 노출합니다. 정적 파일 미들웨어는 파일 공급자를 이용해서 정적 파일을 찾습니다. Razor는 뷰를 찾기 위해 IFileProvider를 빈번히 사용합니다. Dotnet의 게시 기능은 파일 공급자와 Globbing 패턴을 사용해서 게시해야 할 파일들을 지정합니다.

응용 프로그램에서 사용시 권장 사항

ASP.NET Core 응용 프로그램에서 파일 시스템에 접근해야 할 때, 본문에서 살펴본 것처럼 의존성 주입을 통해서 IFileProvider의 인스턴스를 요청한 다음, 해당 인스턴스의 메서드를 사용해서 접근 작업을 수행할 수 있습니다. 이 방식을 사용하면 응용 프로그램이 시작될 때 한 번만 공급자를 구성해서 응용 프로그램이 생성하는 구현 형식의 인스턴스 개수를 줄일 수 있습니다.