본문 바로가기

Windows Phone 7

웹에서 Windows Phone 7의 격리된 저장소로 파일 다운로드 받기

이번 글은 네이버 윈도우폰 개발자 모임에서 진행중인 Windows Phone 7 스터디에서 원격지(웹)상에 존재하는 파일을 WP7에 어떻게 저장하는가에 대한 궁금증이 생겨서 구현해보고 작성하는 블로그 포스트입니다.

Windows Phone 7 은 샌드박스 모델을 사용하는 관계로 시스템의 저장공간을 마음대로 사용할 수가 없습니다.

WP7 어플리케이션은 각 어플리케이션 마다 격리된 저장소를 할당 받고, 그 격리된 저장소 내의 파일들에 대해서만 읽고 쓰기가 가능합니다. 물론, 각종 미디어 파일(비디오, 사진 등)은 공용 스토리지를 이용하여 사용할 수 있습니다.


아래 구현된 소스는 Windows Phone 7 Developer Tools Beta 버전으로 구현되었습니다.


샘플 소스의 개요
  • 샘플 소스에는 파일 다운로드를 위한 테스트용 웹 응용프로그램 프로젝트(ASP.NET MVC 2) 가 존재하며,
    이 프로젝트를 이용하여 WP7에서 파일 다운로드를 구현합니다.
  • Windows Phone Application 프로젝트에서는 위 웹 응용프로그램에서 파일을 다운로드하여, 어플리케이션에 할당된 격리된 저장소에 파일을 저장하고, 이후 격리된 저장소에 저장된 파일들의 목록을 보여줍니다.
  • 저장소에 저장된 파일의 리스트에서 항목을 선택하면, TXT 파일일 경우에는 그 내용을 TextBlock에 출력합니다.

다운로드를 위한 테스트용 웹사이트의 구현

웹사이트는 단지 다운로드 기능만이 있으면 되므로, /Download 경로로 접속시에 지정한 파일을 전송해주는 기능만을 구현합니다.
  1. Visual Studio 2010 에서 새로운 ASP.NET MVC 2 Empty Web Application 프로젝트를 생성합니다.
  2. 웹 응용프로그램 프로젝트 루트 경로에 Files 라는 이름의 폴더를 추가하고, WP7으로 전송할 파일을 추가합니다.
    여기서는 ibatis.licence.txt 라는 파일을 추가하였습니다.
  3. Controllers 폴더에 DownloadController라는 새로운 컨트롤러 클래스를 추가합니다.

  4. DownloadController 클래스에는 아래와 같은 코드를 추가합니다.

    private void downloadButton_Click(object sender, RoutedEventArgs e)
    {
        string fileUrl = "http://localhost:2975/Download"; // 파일 다운로드를 위한 URL 경로   
        HttpWebRequest request = HttpWebRequest.Create(fileUrl) as HttpWebRequest;
        request.BeginGetResponse(new AsyncCallback(DownloadCallback), request);
    }

  5. Properties 폴더를 더블클릭하거나, 프로젝트의 속성을 선택하여 Web 탭에서 항상 동일한 포트를 이용하도록 설정하여 줍니다.

  6. 웹 프로젝트를 실행하고 /Download 경로로 접속하여 파일이 다운로드가 되는지 확인하여 주세요.

Window Phone 7 에서 웹 다운로드 구현
  1. Visual Studio 2010 Express for Windows Phone 이나 Visual Studio 2010 에서 Silverlight for Windows Phone 프로젝트 템플릿 그룹에서 Windows Phone Application 을 선택하여 새로운 프로젝트를 생성합니다.
  2. MainPage.xaml 페이지의 UI는 아래와 같이 구현하였습니다.

  3. downloadButtonClick 이벤트에 대한 이벤트 핸들러를 등록하고, 코드 비하인드 파일에서 아래와 같은 코드를 추가하여 주세요.

    private void downloadButton_Click(object sender, RoutedEventArgs e)
    {
        string fileUrl = "http://localhost:2975/Download"; // 파일 다운로드를 위한 URL 경로   
        HttpWebRequest request = HttpWebRequest.Create(fileUrl) as HttpWebRequest;
        request.BeginGetResponse(new AsyncCallback(DownloadCallback), request);
    }


    위 코드에서는 파일을 다운로드 받기 위해서 HttpWebRequest 클래스를 이용하도록 하였습니다.
    주의할 점은 Silverlight 는 HttpWebRequest 의 동기방식의 호출을 지원하지 않습니다.
    그러므로, HttpWebRequest 객체에서 HttpWebResponse 객체를 얻기위해서는 BeginGetResponse 메서드와 EndGetResponse 메서드를 이용하여 비동기 방식으로 HttpWebResponse 객체를 가져와야 합니다.

  4. HttpWebResponse 객체를 얻기 위하여 BeginGetResponse 메서드를 호출하였으니, HTTP 요청이 완료된 시점에 실행할 DownloadCallback 메서드를 구현하여야 합니다.
    위 코드에서 BeginGetResponse 메서드 호출시에 콜백메서드로 DownloadCallback 이라는 이름의 메서드를 지정하였음을 보실 수 있을 겁니다. 이 메서드는 아래와 같이 구현합니다.

    private void DownloadCallback(IAsyncResult asyncronousResult)
    {
        // BeginGetResponse 메서드의 state 매개변수로부터 전달받은 HttpWebRequest객체의
        // EndGetResponse 메서드를 호출하여 HttpWebResponse 객체를 획득한다.
        HttpWebRequest request = (HttpWebRequest)asyncronousResult.AsyncState; 
        HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asyncronousResult);

        // 저장할 파일명을 가져옵니다.
        string filename = GetFileName(response.Headers["Content-Disposition"]);

        try
        {
            // HttpWebResponse의 스트림을 이용하여 격리된 저장소에 파일을 쓴다.
            using (Stream stream = response.GetResponseStream())
            {
                IsolatedStorageFile appStorage = IsolatedStorageFile.GetUserStoreForApplication();
                using (IsolatedStorageFileStream fileStream =
                        new IsolatedStorageFileStream(filename, FileMode.OpenOrCreate, appStorage))
                {
                    byte[] buffer = new byte[32768];
                    int readBytes;

                    while ((readBytes = stream.Read(buffer, 0, buffer.Length)) > 0)
                    {
                        fileStream.Write(buffer, 0, readBytes);
                    }
                }
            }
        }
        catch (Exception) { }

        // UI Thread에 접근하려고 할 경우에는 UnauthorizedAccessException이 발생한다.
        this.Dispatcher.BeginInvoke(delegate()
        {
            DisplayFiles();
        });
    }

    BeginGetResponse 메서드를 호출할 때 두번째 매개변수로 생성하였던 HttpWebRequest 객체를 전달하였으므로, 콜백메서드에서는 IAsyncResult 매개변수에서 AsyncState 속성을 통하여 전달하였던 HttpWebRequest 객체를 획득할 수 있습니다.
    이제, HttpWebRequest 객체에서 EndGetResponse 메서드를 호출하여 웹 요청에 대한 응답인 HttpWebResponse 객체를 가져올 수 있습니다.

    이제 다운로드 받은 파일을 WP7 로컬 스토리지에 파일을 저장하여야 합니다.
    서두에 말씀드렸다시피, WP7에서는 각 어플리케이션에 할당된 격리된 저장소로의 파일 저장 및 읽기를 지원하므로, 다운로드 받은 파일은 격리된 저장소에 저장하여야 합니다.
    Silverlight에서는 격리된 저장소에 접근하기 위해 IsolatedStorageFile 클래스를 제공하고 있습니다.
    IsolateStorageFile 클래스의 정적메서드인 GetUserStoreForApplication 메서드를 이용하면, 현재 어플리케이션에 할당된 격리된 저장소의 정보를 가져올 수 있습니다.
    그리고, 웹에서 다운로드한 파일을 저장하기 위하여 코드에서는 IsolateStorageFileStream을 이용하여 저장소에 파일을 저장하고 있습니다.

    마지막 부분에서는 파일의 저장이 완료되면 ListBox에 다운로드 받은 파일의 리스트를 출력하기 위하여 DisplayFiles 메서드를 호출하고 있습니다.
    여기서 만일 바로 DisplayFiles 메서드를 호출하게 된다면, UnauthorizedAccessException이 발생하게 됩니다.
    이는 지금 실행되고 있는 DownloadCallback 메서드는 비동기 호출로 인하여 UI 스레드인 메인스레드와는 다른  스레드에서 실행되고 있으므로 UI 스레드의 컨트롤에 접근하는 코드를 가진 DisplayFiles 메서드에 바로 접근하려고 하면 오류가 발생하는 것입니다.
    이럴때는 코드와 같이 Dispatcher를 이용하여 메서드를 실행하면 문제를 해결할 수 있습니다.

  5. 격리된 저장소에 저장된 파일의 리스트를 fileListBox 컨트롤에 출력하기 위한 DisplayFiles 메서드는 아래와 같이 구현합니다.

    private void DisplayFiles()
    {
        // 격리된 저장소에 저장된 전체 파일들의 이름을 ListBox에 출력한다.
        IsolatedStorageFile appStorage = IsolatedStorageFile.GetUserStoreForApplication();

        fileListBox.Items.Clear();

        string[] files = appStorage.GetFileNames();

        foreach (string filename in files)
        {
            fileListBox.Items.Add(filename);
        }
    }

    여기서도 저장소에 접근하기 위해서 IsolateStorageFile 을 이용하고 있으며, 파일 리스트를 얻어오기 위하여 GetFileNames 메서드를 활용하였습니다.

  6. fileListBox에 출력된 파일을 선택하였을때 contentTextBlock 에 내용을 출력하기 위하여 fileListBox의 SelectionChanged 이벤트 핸들러를 등록하고, 아래와 같은 코드를 추가하여 주십시오.

    private void fileListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (fileListBox.SelectedItem != null)
        {
            // 선택된 파일을 읽어서 출력한다.
            string selectedFilename = fileListBox.SelectedItem.ToString();

            if (System.IO.Path.GetExtension(selectedFilename) == ".txt")
            {
                IsolatedStorageFile appStorage = IsolatedStorageFile.GetUserStoreForApplication();

                using (StreamReader reader = new StreamReader(
                        new IsolatedStorageFileStream(selectedFilename, FileMode.Open, appStorage)))
                {
                    string content = reader.ReadToEnd();

                    contentTextBlock.Text = content;
                }
            }
        }
    }


  7. 실행결과는 아래와 같습니다.


부족한 부분은 샘플 프로젝트 파일을 첨부하니 참고하십시오.



격리된 저장소에 저장된 파일이나 디렉터리에 대한 정보를 가져오기 위한 FileInfo 클래스나 DirectoryInfo 클래스를 가져올 수 있는 방법이 없었습니다.
WP7 에서는 어플리케이션의 격리된 저장소와 공용 저장소 외에는 따로 접근할수가 없는듯 한데, 파일이나 디렉터리에 대한 정보를 가져올 수 없다는 점은 상당히 아쉬운 부분입니다.

끝까지 읽어주셔서 감사합니다.

즐거운 하루되십시오.