다음을 통해 공유


ASP.NET Core Blazor 파일 다운로드

참고 항목

이 문서의 최신 버전은 아닙니다. 현재 릴리스는 이 문서의 .NET 9 버전을 참조 하세요.

Warning

이 버전의 ASP.NET Core는 더 이상 지원되지 않습니다. 자세한 내용은 .NET 및 .NET Core 지원 정책을 참조 하세요. 현재 릴리스는 이 문서의 .NET 9 버전을 참조 하세요.

Important

이 정보는 상업적으로 출시되기 전에 실질적으로 수정될 수 있는 시험판 제품과 관련이 있습니다. Microsoft는 여기에 제공된 정보에 대해 어떠한 명시적, 또는 묵시적인 보증을 하지 않습니다.

현재 릴리스는 이 문서의 .NET 9 버전을 참조 하세요.

이 문서에서는 앱에서 Blazor 파일을 다운로드하는 방법을 설명합니다.

파일 다운로드

이 문서에서는 브라우저에서 파일을 열지 않고 클라이언트에서 다운로드하여 저장해야 하는 다음 시나리오에 대한 방법을 설명합니다.

앱이 아닌 다른 원본에서 파일을 다운로드하는 경우에는 CORS(원본 간 리소스 공유) 고려 사항이 적용됩니다. 자세한 내용은 CORS(원본 간 리소스 공유) 섹션을 참조하세요.

보안 고려 사항

사용자에게 서버에서 파일을 다운로드하는 기능을 제공할 때는 주의해야 합니다. 사이버 공격자는 DoS(서비스 거부) 공격, API 악용 공격을 실행하거나 다른 방법으로 네트워크 및 서버를 손상하려고 시도할 수 있습니다.

공격이 성공할 가능성을 줄이는 보안 단계는 다음과 같습니다.

  • 서버의 전용 파일 다운로드 영역, 가급적 비시스템 드라이브에서 파일을 다운로드합니다. 전용 위치를 사용하면 다운로드 가능한 파일에 대한 보안 제한을 더 쉽게 적용할 수 있습니다. 파일 다운로드 위치에서 실행 권한을 사용하지 않도록 설정합니다.
  • 클라이언트 쪽 보안 검사는 악의적인 사용자가 쉽게 우회할 수 있습니다. 서버에서 클라이언트 쪽 보안 검사를 항상 수행합니다.
  • 사용자나 신뢰할 수 없는 다른 원본이 보내는 파일을 받지 않고, 파일에 대한 보안 검사를 수행하지 않고 즉시 다운로드할 수 있게 합니다. 자세한 내용은 ASP.NET Core에서 파일 업로드를 참조하세요.

스트림에서 다운로드

이 섹션은 일반적으로 크기가 최대 250MB인 파일에 적용됩니다.

비교적 작은 파일(< 250MB)에 권장되는 방법은 파일 콘텐츠를 JavaScript(JS) interop를 사용하여 클라이언트의 원시 이진 데이터 버퍼로 스트리밍하는 것입니다. 이 방법은 대화형 렌더링 모드를 채택하지만 정적 SSR(정적 서버 쪽 렌더링)을 채택하는 구성 요소는 채택하지 않는 구성 요소에 효과적입니다.

비교적 작은 파일(< 250MB)에 권장되는 방법은 파일 콘텐츠를 JavaScript(JS) interop를 사용하여 클라이언트의 원시 이진 데이터 버퍼로 스트리밍하는 것입니다.

Warning

이 섹션의 접근 방식은 파일의 콘텐츠를 JS ArrayBuffer로 읽습니다. 이 방법은 전체 파일을 클라이언트의 메모리에 로드하며, 그 결과 성능이 저하될 수 있습니다. 비교적 큰 파일(>= 250 MB)을 다운로드하려면 URL에서 다운로드 섹션에 나오는 지침을 따르는 것이 좋습니다.

다음 downloadFileFromStreamJS 함수는 다음 작업을 수행합니다.

  • 제공된 스트림을 ArrayBuffer로 읽어옵니다.
  • Blob을 만들어 ArrayBuffer를 래핑합니다.
  • 파일의 다운로드 주소로 사용할 개체 URL을 만듭니다.
  • (<a>요소)를 HTMLAnchorElement 만듭니다.
  • 다운로드할 파일의 이름(fileName) 및 URL(url)을 할당합니다.
  • 앵커 요소에 이벤트를 발생 click 시켜 다운로드를 트리거합니다.
  • 앵커 요소를 제거합니다.
  • 를 호출하여 개체 URL(url)을 취소 URL.revokeObjectURL합니다. 이는 클라이언트에서 메모리가 누수되지 않도록 하기 위해 중요한 단계입니다.
<script>
  window.downloadFileFromStream = async (fileName, contentStreamReference) => {
    const arrayBuffer = await contentStreamReference.arrayBuffer();
    const blob = new Blob([arrayBuffer]);
    const url = URL.createObjectURL(blob);
    const anchorElement = document.createElement('a');
    anchorElement.href = url;
    anchorElement.download = fileName ?? '';
    anchorElement.click();
    anchorElement.remove();
    URL.revokeObjectURL(url);
  }
</script>

참고 항목

위치 및 프로덕션 앱에 JS 대한 권장 사항에 대한 일반적인 지침은 ASP.NET Core Blazor 앱의 JavaScript 위치를 참조하세요.

다음 구성 요소는

  • 네이티브 바이트 스트리밍 interop를 사용하여 파일을 클라이언트로 효율적으로 전송합니다.
  • 클라이언트에 다운로드한 파일에 대한 Stream을 검색하는 GetFileStream이라는 메서드가 있습니다. 대체 방법은 스토리지에서 파일을 검색하거나 C# 코드에서 파일을 동적으로 생성하는 것입니다. 이 데모에서 앱은 새 바이트 배열(new byte[])에서 임의의 데이터로 구성된 50KB 파일을 만듭니다. 바이트는 예제에서 동적으로 생성되는 이진 파일 역할을 하기 위해 MemoryStream으로 래핑됩니다.
  • DownloadFileFromStream 메서드는 다음 작업을 수행합니다.
    • GetFileStream에서 Stream을 검색합니다.
    • 파일이 사용자의 컴퓨터에 저장되는 경우 파일 이름을 지정합니다. 다음 예는 파일의 이름을 quote.txt으로 지정합니다.
    • DotNetStreamReferenceStream을 래핑하면 파일 데이터를 클라이언트에 스트리밍할 수 있습니다.
    • 클라이언트의 downloadFileFromStreamJS 데이터를 수락하는 함수를 호출합니다.

FileDownload1.razor:

@page "/file-download-1"
@using System.IO
@inject IJSRuntime JS

<PageTitle>File Download 1</PageTitle>

<h1>File Download Example 1</h1>

<button @onclick="DownloadFileFromStream">
    Download File From Stream
</button>

@code {
    private Stream GetFileStream()
    {
        var randomBinaryData = new byte[50 * 1024];
        var fileStream = new MemoryStream(randomBinaryData);

        return fileStream;
    }

    private async Task DownloadFileFromStream()
    {
        var fileStream = GetFileStream();
        var fileName = "log.bin";

        using var streamRef = new DotNetStreamReference(stream: fileStream);

        await JS.InvokeVoidAsync("downloadFileFromStream", fileName, streamRef);
    }
}
@page "/file-download-1"
@using System.IO
@inject IJSRuntime JS

<PageTitle>File Download 1</PageTitle>

<h1>File Download Example 1</h1>

<button @onclick="DownloadFileFromStream">
    Download File From Stream
</button>

@code {
    private Stream GetFileStream()
    {
        var randomBinaryData = new byte[50 * 1024];
        var fileStream = new MemoryStream(randomBinaryData);

        return fileStream;
    }

    private async Task DownloadFileFromStream()
    {
        var fileStream = GetFileStream();
        var fileName = "log.bin";

        using var streamRef = new DotNetStreamReference(stream: fileStream);

        await JS.InvokeVoidAsync("downloadFileFromStream", fileName, streamRef);
    }
}
@page "/file-download-1"
@using System.IO
@inject IJSRuntime JS

<h1>File Download Example</h1>

<button @onclick="DownloadFileFromStream">
    Download File From Stream
</button>

@code {
    private Stream GetFileStream()
    {
        var randomBinaryData = new byte[50 * 1024];
        var fileStream = new MemoryStream(randomBinaryData);

        return fileStream;
    }

    private async Task DownloadFileFromStream()
    {
        var fileStream = GetFileStream();
        var fileName = "log.bin";

        using var streamRef = new DotNetStreamReference(stream: fileStream);

        await JS.InvokeVoidAsync("downloadFileFromStream", fileName, streamRef);
    }
}
@page "/file-download-1"
@using System.IO
@inject IJSRuntime JS

<h1>File Download Example</h1>

<button @onclick="DownloadFileFromStream">
    Download File From Stream
</button>

@code {
    private Stream GetFileStream()
    {
        var randomBinaryData = new byte[50 * 1024];
        var fileStream = new MemoryStream(randomBinaryData);

        return fileStream;
    }

    private async Task DownloadFileFromStream()
    {
        var fileStream = GetFileStream();
        var fileName = "log.bin";

        using var streamRef = new DotNetStreamReference(stream: fileStream);

        await JS.InvokeVoidAsync("downloadFileFromStream", fileName, streamRef);
    }
}

물리적 파일에 대해 반환 Stream 해야 하는 서버 쪽 앱의 구성 요소의 경우 다음 예제와 같이 구성 요소가 호출 File.OpenRead할 수 있습니다.

private Stream GetFileStream() => File.OpenRead(@"{PATH}");

앞의 예제에서 {PATH} 자리 표시자는 파일 경로입니다. @ 접두사는 문자열이 축자 문자열 리터럴임을 나타냅니다. 이 리터럴은 Windows OS 경로에 백슬래시(\)를 사용하고 경로의 작은따옴표에 큰따옴표("")를 포함할 수 있습니다. 또는 문자열 리터럴(@)을 피하고 다음 방법 중 하나를 사용합니다.

  • 이스케이프된 백슬래시(\\) 및 따옴표(\")를 사용합니다.
  • 경로에서 슬래시(/)를 사용합니다. 이 슬래시는 ASP.NET Core 앱의 플랫폼에서 지원되며 이스케이프된 따옴표(\")입니다.

URL에서 다운로드

이 섹션은 비교적 큰 파일(일반적으로 250MB 이상)에 적용됩니다.

대화형으로 렌더링된 구성 요소 또는 정적으로 렌더링된 구성 요소에 대한 크기의 파일을 사용하여 비교적 큰 파일(>= 250MB)을 다운로드하는 데 권장되는 방법은 파일의 이름과 URL을 사용하여 앵커 요소를 트리거하는 데 사용하는 JS 것입니다.

비교적 큰 파일(>= 250MB)을 다운로드하는 데 권장되는 방법은 파일의 이름과 URL을 사용하여 앵커 요소를 트리거하는 데 사용하는 JS 것입니다.

이 섹션의 예제에서는 앱의 웹 루트(wwwroot 폴더)에 있는 files라는 폴더에 배치되는 quote.txt라는 다운로드 파일을 사용합니다. files 폴더는 시연 목적으로만 사용합니다. 다운로드 가능한 파일을 선호하는 웹 루트(wwwroot 폴더)에 있는 아무 폴더 레이아웃으로 정리할 수 있습니다(예: wwwroot 폴더에서 파일을 바로 제공).

wwwroot/files/quote.txt:

When victory is ours, we'll wipe every trace of the Thals and their city from the face of this land. We will avenge the deaths of all Kaleds who've fallen in the cause of right and justice and build a peace which will be a monument to their sacrifice. Our battle cry will be "Total extermination of the Thals!"

- General Ravon (Guy Siner, http://guysiner.com/)
  Dr. Who: Genesis of the Daleks (https://www.bbc.co.uk/programmes/p00vd5g2)
  Copyright 1975 BBC (https://www.bbc.co.uk/)
When victory is ours, we'll wipe every trace of the Thals and their city from the face of this land. We will avenge the deaths of all Kaleds who've fallen in the cause of right and justice and build a peace which will be a monument to their sacrifice. Our battle cry will be "Total extermination of the Thals!"

- General Ravon (Guy Siner, http://guysiner.com/)
  Dr. Who: Genesis of the Daleks (https://www.bbc.co.uk/programmes/p00vd5g2)
  Copyright 1975 BBC (https://www.bbc.co.uk/)
When victory is ours, we'll wipe every trace of the Thals and their city from the face of this land. We will avenge the deaths of all Kaleds who've fallen in the cause of right and justice and build a peace which will be a monument to their sacrifice. Our battle cry will be "Total extermination of the Thals!"

- General Ravon (Guy Siner, http://guysiner.com/)
  Dr. Who: Genesis of the Daleks (https://www.bbc.co.uk/programmes/p00vd5g2)
  Copyright 1975 BBC (https://www.bbc.co.uk/)
When victory is ours, we'll wipe every trace of the Thals and their city from the face of this land. We will avenge the deaths of all Kaleds who've fallen in the cause of right and justice and build a peace which will be a monument to their sacrifice. Our battle cry will be "Total extermination of the Thals!"

- General Ravon (Guy Siner, http://guysiner.com/)
  Dr. Who: Genesis of the Daleks (https://www.bbc.co.uk/programmes/p00vd5g2)
  Copyright 1975 BBC (https://www.bbc.co.uk/)

다음 triggerFileDownloadJS 함수는 다음 작업을 수행합니다.

  • (<a>요소)를 HTMLAnchorElement 만듭니다.
  • 다운로드할 파일의 이름(fileName) 및 URL(url)을 할당합니다.
  • 앵커 요소에 이벤트를 발생 click 시켜 다운로드를 트리거합니다.
  • 앵커 요소를 제거합니다.
<script>
  window.triggerFileDownload = (fileName, url) => {
    const anchorElement = document.createElement('a');
    anchorElement.href = url;
    anchorElement.download = fileName ?? '';
    anchorElement.click();
    anchorElement.remove();
  }
</script>

참고 항목

위치 및 프로덕션 앱에 JS 대한 권장 사항에 대한 일반적인 지침은 ASP.NET Core Blazor 앱의 JavaScript 위치를 참조하세요.

다음 예제 구성 요소는 앱에서 사용하는 원본과 동일한 원본에서 파일을 다운로드합니다. 다른 원본에서 파일 다운로드를 시도하는 경우에는 CORS(원본 간 리소스 공유)를 구성합니다. 자세한 내용은 CORS(원본 간 리소스 공유) 섹션을 참조하세요.

FileDownload2.razor:

@page "/file-download-2"
@inject IJSRuntime JS

<PageTitle>File Download 2</PageTitle>

<h1>File Download Example 2</h1>

<button @onclick="DownloadFileFromURL">
    Download File From URL
</button>

@code {
    private async Task DownloadFileFromURL()
    {
        var fileName = "quote.txt";
        var fileURL = "/files/quote.txt";
        await JS.InvokeVoidAsync("triggerFileDownload", fileName, fileURL);
    }
}

대화형 구성 요소의 경우 이전 예제의 단추는 처리기를 호출 DownloadFileFromURL 하여 JavaScript(JS) 함수를 호출합니다 triggerFileDownload.

구성 요소가 정적 서버 쪽 렌더링(정적 SSR)을 채택하는 경우 정적 SSR(정적 서버 쪽 렌더링)을 사용하여 ASP.NET Core Blazor JavaScript의 지침에 따라 호출 triggerFileDownload 할 단추addEventListener(MDN 설명서)에 대한 이벤트 처리기를 추가합니다.

@page "/file-download-2"
@inject IJSRuntime JS

<PageTitle>File Download 2</PageTitle>

<h1>File Download Example 2</h1>

<button @onclick="DownloadFileFromURL">
    Download File From URL
</button>

@code {
    private async Task DownloadFileFromURL()
    {
        var fileName = "quote.txt";
        var fileURL = "/files/quote.txt";
        await JS.InvokeVoidAsync("triggerFileDownload", fileName, fileURL);
    }
}

대화형 구성 요소의 경우 이전 예제의 단추는 처리기를 호출 DownloadFileFromURL 하여 JavaScript(JS) 함수를 호출합니다 triggerFileDownload.

구성 요소가 정적 서버 쪽 렌더링(정적 SSR)을 채택하는 경우 정적 SSR(정적 서버 쪽 렌더링)을 사용하여 ASP.NET Core Blazor JavaScript의 지침에 따라 호출 triggerFileDownload 할 단추addEventListener(MDN 설명서)에 대한 이벤트 처리기를 추가합니다.

@page "/file-download-2"
@inject IJSRuntime JS

<h1>File Download Example 2</h1>

<button @onclick="DownloadFileFromURL">
    Download File From URL
</button>

@code {
    private async Task DownloadFileFromURL()
    {
        var fileName = "quote.txt";
        var fileURL = "https://localhost:5001/files/quote.txt";
        await JS.InvokeVoidAsync("triggerFileDownload", fileName, fileURL);
    }
}

환경의 localhost 개발 포트와 일치하도록 이전 예제의 포트를 변경합니다.

@page "/file-download-2"
@inject IJSRuntime JS

<h1>File Download Example 2</h1>

<button @onclick="DownloadFileFromURL">
    Download File From URL
</button>

@code {
    private async Task DownloadFileFromURL()
    {
        var fileName = "quote.txt";
        var fileURL = "https://localhost:5001/files/quote.txt";
        await JS.InvokeVoidAsync("triggerFileDownload", fileName, fileURL);
    }
}

환경의 localhost 개발 포트와 일치하도록 이전 예제의 포트를 변경합니다.

교차 출처 리소스 공유(CORS)

앱과는 원본이 다른 파일에 CORS(원본 간 리소스 공유)를 사용하도록 설정하는 추가 단계를 수행하지 않으면, 파일 다운로드가 브라우저에서 수행한 CORS 검사를 통과하지 못합니다.

다운로드할 파일을 호스트하는 ASP.NET Core 앱 및 기타 Microsoft 제품과 서비스의 CORS에 대한 자세한 내용은 다음 리소스를 참조하세요.

추가 리소스