Windows Phone 8 애플리케이션에서 Web API 호출(C#)

작성자 : Robert McMurray

이 자습서에서는 Windows Phone 8 애플리케이션에 책 카탈로그를 제공하는 ASP.NET Web API 애플리케이션으로 구성된 완전한 엔드 투 엔드 시나리오를 만드는 방법을 알아봅니다.

개요

ASP.NET Web API 같은 RESTful 서비스는 서버 쪽 및 클라이언트 쪽 애플리케이션에 대한 아키텍처를 추상화하여 개발자를 위한 HTTP 기반 애플리케이션 만들기를 간소화합니다. 웹 API 개발자는 통신을 위한 독점 소켓 기반 프로토콜을 만드는 대신 애플리케이션에 필요한 HTTP 메서드(예: GET, POST, PUT, DELETE)를 게시하기만 하면 되며 클라이언트 애플리케이션 개발자는 애플리케이션에 필요한 HTTP 메서드만 사용해야 합니다.

이 엔드 투 엔드 자습서에서는 Web API를 사용하여 다음 프로젝트를 만드는 방법을 알아봅니다.

사전 요구 사항

  • Windows Phone 8 SDK가 설치된 Visual Studio 2013
  • Hyper-V가 설치된 64비트 시스템의 Windows 8 이상
  • 추가 요구 사항 목록은 Windows Phone SDK 8.0 다운로드 페이지의 시스템 요구 사항 섹션을 참조하세요.

참고

로컬 시스템에서 Web API와 Windows Phone 8 프로젝트 간의 연결을 테스트하려는 경우 로컬 컴퓨터에서 웹 API 애플리케이션에 Windows Phone 8 에뮬레이터 연결 문서의 지침에 따라 테스트 환경을 설정해야 합니다.

1단계: Web API Bookstore 프로젝트 만들기

이 엔드투엔드 자습서의 첫 번째 단계는 모든 CRUD 작업을 지원하는 Web API 프로젝트를 만드는 것입니다. 이 자습서의 2단계에서 이 솔루션에 Windows Phone 애플리케이션 프로젝트를 추가합니다.

  1. Visual Studio 2013 엽니다.

  2. 파일, 새로 만들기, 프로젝트를 차례로 클릭합니다.

  3. 새 프로젝트 대화 상자가 표시되면 설치됨, 템플릿, Visual C#, 을 차례로 확장합니다.

    메뉴의 파일 경로를 보여 주는 '새 프로젝트' 대화 상자의 스크린샷과 새 프로젝트를 만드는 단계를 강조 표시합니다.
    이미지를 클릭하여 확장
  4. ASP.NET 웹 애플리케이션을 강조 표시하고 프로젝트 이름으로 BookStore를 입력한 다음 확인을 클릭합니다.

  5. 새 ASP.NET 프로젝트 대화 상자가 표시되면 Web API 템플릿을 선택하고 확인을 클릭합니다.

    템플릿 옵션 및 템플릿 폴더 및 핵심 참조를 선택하는 검사 상자를 보여 주는 ASP dot NET 프로젝트 책장 대화 상자의 스크린샷.
    이미지를 클릭하여 확장
  6. Web API 프로젝트가 열리면 프로젝트에서 샘플 컨트롤러를 제거합니다.

    1. 솔루션 탐색기에서 Controllers 폴더를 확장합니다.
    2. ValuesController.cs 파일을 마우스 오른쪽 단추로 클릭한 다음 삭제를 클릭합니다.
    3. 삭제를 확인하라는 메시지가 표시되면 확인을 클릭합니다.
  7. Web API 프로젝트에 XML 데이터 파일을 추가합니다. 이 파일에는 bookstore 카탈로그의 내용이 포함되어 있습니다.

    1. 솔루션 탐색기에서 App_Data 폴더를 마우스 오른쪽 단추로 클릭한 다음 추가를 클릭한 다음 새 항목을 클릭합니다.

    2. 새 항목 추가 대화 상자가 표시되면 XML 파일 템플릿을 강조 표시합니다.

    3. 파일 이름을 Books.xml지정한 다음 추가를 클릭합니다.

    4. Books.xml 파일을 열면 파일의 코드를 MSDN의 샘플 books.xml 파일의 XML로 바꿉 있습니다.

      <?xml version="1.0" encoding="utf-8"?>
      <catalog>
        <book id="bk101">
          <author>Gambardella, Matthew</author>
          <title>XML Developer's Guide</title>
          <genre>Computer</genre>
          <price>44.95</price>
          <publish_date>2000-10-01</publish_date>
          <description>
            An in-depth look at creating applications
            with XML.
          </description>
        </book>
        <book id="bk102">
          <author>Ralls, Kim</author>
          <title>Midnight Rain</title>
          <genre>Fantasy</genre>
          <price>5.95</price>
          <publish_date>2000-12-16</publish_date>
          <description>
            A former architect battles corporate zombies,
            an evil sorceress, and her own childhood to become queen
            of the world.
          </description>
        </book>
        <book id="bk103">
          <author>Corets, Eva</author>
          <title>Maeve Ascendant</title>
          <genre>Fantasy</genre>
          <price>5.95</price>
          <publish_date>2000-11-17</publish_date>
          <description>
            After the collapse of a nanotechnology
            society in England, the young survivors lay the
            foundation for a new society.
          </description>
        </book>
        <book id="bk104">
          <author>Corets, Eva</author>
          <title>Oberon's Legacy</title>
          <genre>Fantasy</genre>
          <price>5.95</price>
          <publish_date>2001-03-10</publish_date>
          <description>
            In post-apocalypse England, the mysterious
            agent known only as Oberon helps to create a new life
            for the inhabitants of London. Sequel to Maeve
            Ascendant.
          </description>
        </book>
        <book id="bk105">
          <author>Corets, Eva</author>
          <title>The Sundered Grail</title>
          <genre>Fantasy</genre>
          <price>5.95</price>
          <publish_date>2001-09-10</publish_date>
          <description>
            The two daughters of Maeve, half-sisters,
            battle one another for control of England. Sequel to
            Oberon's Legacy.
          </description>
        </book>
        <book id="bk106">
          <author>Randall, Cynthia</author>
          <title>Lover Birds</title>
          <genre>Romance</genre>
          <price>4.95</price>
          <publish_date>2000-09-02</publish_date>
          <description>
            When Carla meets Paul at an ornithology
            conference, tempers fly as feathers get ruffled.
          </description>
        </book>
        <book id="bk107">
          <author>Thurman, Paula</author>
          <title>Splish Splash</title>
          <genre>Romance</genre>
          <price>4.95</price>
          <publish_date>2000-11-02</publish_date>
          <description>
            A deep sea diver finds true love twenty
            thousand leagues beneath the sea.
          </description>
        </book>
        <book id="bk108">
          <author>Knorr, Stefan</author>
          <title>Creepy Crawlies</title>
          <genre>Horror</genre>
          <price>4.95</price>
          <publish_date>2000-12-06</publish_date>
          <description>
            An anthology of horror stories about roaches,
            centipedes, scorpions  and other insects.
          </description>
        </book>
        <book id="bk109">
          <author>Kress, Peter</author>
          <title>Paradox Lost</title>
          <genre>Science Fiction</genre>
          <price>6.95</price>
          <publish_date>2000-11-02</publish_date>
          <description>
            After an inadvertant trip through a Heisenberg
            Uncertainty Device, James Salway discovers the problems
            of being quantum.
          </description>
        </book>
        <book id="bk110">
          <author>O'Brien, Tim</author>
          <title>Microsoft .NET: The Programming Bible</title>
          <genre>Computer</genre>
          <price>36.95</price>
          <publish_date>2000-12-09</publish_date>
          <description>
            Microsoft's .NET initiative is explored in
            detail in this deep programmer's reference.
          </description>
        </book>
        <book id="bk111">
          <author>O'Brien, Tim</author>
          <title>MSXML3: A Comprehensive Guide</title>
          <genre>Computer</genre>
          <price>36.95</price>
          <publish_date>2000-12-01</publish_date>
          <description>
            The Microsoft MSXML3 parser is covered in
            detail, with attention to XML DOM interfaces, XSLT processing,
            SAX and more.
          </description>
        </book>
        <book id="bk112">
          <author>Galos, Mike</author>
          <title>Visual Studio 7: A Comprehensive Guide</title>
          <genre>Computer</genre>
          <price>49.95</price>
          <publish_date>2001-04-16</publish_date>
          <description>
            Microsoft Visual Studio 7 is explored in depth,
            looking at how Visual Basic, Visual C++, C#, and ASP+ are
            integrated into a comprehensive development
            environment.
          </description>
        </book>
      </catalog>
      
    5. XML 파일을 저장하고 닫습니다.

  8. Web API 프로젝트에 bookstore 모델을 추가합니다. 이 모델에는 bookstore 애플리케이션에 대한 CRUD(만들기, 읽기, 업데이트 및 삭제) 논리가 포함되어 있습니다.

    1. 솔루션 탐색기에서 Models 폴더를 마우스 오른쪽 단추로 클릭한 다음 추가를 클릭한 다음 클래스를 클릭합니다.

    2. 새 항목 추가 대화 상자가 표시되면 클래스 파일 이름을 BookDetails.cs로 지정하고 추가를 클릭합니다.

    3. BookDetails.cs 파일이 열리면 파일의 코드를 다음으로 바꿉니다.

      using System;
      using System.Collections.Generic;
      using System.ComponentModel.DataAnnotations;
      using System.Linq;
      using System.Xml;
      using System.Xml.Linq;
      using System.Xml.XPath;
      using System.Web;
      
      namespace BookStore.Models
      {
          /// <summary>
          /// Define a class that will hold the detailed information for a book.
          /// </summary>
          public class BookDetails
          {
              [Required]
              public String Id { get; set; }
              [Required]
              public String Title { get; set; }
              public String Author { get; set; }
              public String Genre { get; set; }
              public Decimal Price { get; set; }
              public DateTime PublishDate { get; set; }
              public String Description { get; set; }
          }
      
          /// <summary>
          /// Define an interface which contains the methods for the book repository.
          /// </summary>
          public interface IBookRepository
          {
              BookDetails CreateBook(BookDetails book);
              IEnumerable<BookDetails> ReadAllBooks();
              BookDetails ReadBook(String id);
              BookDetails UpdateBook(String id, BookDetails book);
              Boolean DeleteBook(String id);
          }
      
          /// <summary>
          /// Define a class based on the book repository interface which contains the method implementations.
          /// </summary>
          public class BookRepository : IBookRepository
          {
              private string xmlFilename = null;
              private XDocument xmlDocument = null;
      
              /// <summary>
              /// Define the class constructor.
              /// </summary>
              public BookRepository()
              {
                  try
                  {
                      // Determine the path to the books.xml file.
                      xmlFilename = HttpContext.Current.Server.MapPath("~/app_data/books.xml");
                      // Load the contents of the books.xml file into an XDocument object.
                      xmlDocument = XDocument.Load(xmlFilename);
                  }
                  catch (Exception ex)
                  {
                      // Rethrow the exception.
                      throw ex;
                  }
              }
      
              /// <summary>
              /// Method to add a new book to the catalog.
              /// Defines the implementation of the POST method.
              /// </summary>
              public BookDetails CreateBook(BookDetails book)
              {
                  try
                  {
                      // Retrieve the book with the highest ID from the catalog.
                      var highestBook = (
                          from bookNode in xmlDocument.Elements("catalog").Elements("book")
                          orderby bookNode.Attribute("id").Value descending
                          select bookNode).Take(1);
                      // Extract the ID from the book data.
                      string highestId = highestBook.Attributes("id").First().Value;
                      // Create an ID for the new book.
                      string newId = "bk" + (Convert.ToInt32(highestId.Substring(2)) + 1).ToString();
                      // Verify that this book ID does not currently exist.
                      if (this.ReadBook(newId) == null)
                      {
                          // Retrieve the parent element for the book catalog.
                          XElement bookCatalogRoot = xmlDocument.Elements("catalog").Single();
                          // Create a new book element.
                          XElement newBook = new XElement("book", new XAttribute("id", newId));
                          // Create elements for each of the book's data items.
                          XElement[] bookInfo = FormatBookData(book);
                          // Add the element to the book element.
                          newBook.ReplaceNodes(bookInfo);
                          // Append the new book to the XML document.
                          bookCatalogRoot.Add(newBook);
                          // Save the XML document.
                          xmlDocument.Save(xmlFilename);
                          // Return an object for the newly-added book.
                          return this.ReadBook(newId);
                      }
                  }
                  catch (Exception ex)
                  {
                      // Rethrow the exception.
                      throw ex;
                  }
                  // Return null to signify failure.
                  return null;
              }
      
              /// <summary>
              /// Method to retrieve all of the books in the catalog.
              /// Defines the implementation of the non-specific GET method.
              /// </summary>
              public IEnumerable<BookDetails> ReadAllBooks()
              {
                  try
                  {
                      // Return a list that contains the catalog of book ids/titles.
                      return (
                          // Query the catalog of books.
                          from book in xmlDocument.Elements("catalog").Elements("book")
                          // Sort the catalog based on book IDs.
                          orderby book.Attribute("id").Value ascending
                          // Create a new instance of the detailed book information class.
                          select new BookDetails
                          {
                              // Populate the class with data from each of the book's elements.
                              Id = book.Attribute("id").Value,
                              Author = book.Element("author").Value,
                              Title = book.Element("title").Value,
                              Genre = book.Element("genre").Value,
                              Price = Convert.ToDecimal(book.Element("price").Value),
                              PublishDate = Convert.ToDateTime(book.Element("publish_date").Value),
                              Description = book.Element("description").Value
                          }).ToList();
                  }
                  catch (Exception ex)
                  {
                      // Rethrow the exception.
                      throw ex;
                  }
              }
      
              /// <summary>
              /// Method to retrieve a specific book from the catalog.
              /// Defines the implementation of the ID-specific GET method.
              /// </summary>
              public BookDetails ReadBook(String id)
              {
                  try
                  {
                      // Retrieve a specific book from the catalog.
                      return (
                          // Query the catalog of books.
                          from book in xmlDocument.Elements("catalog").Elements("book")
                          // Specify the specific book ID to query.
                          where book.Attribute("id").Value.Equals(id)
                          // Create a new instance of the detailed book information class.
                          select new BookDetails
                          {
                              // Populate the class with data from each of the book's elements.
                              Id = book.Attribute("id").Value,
                              Author = book.Element("author").Value,
                              Title = book.Element("title").Value,
                              Genre = book.Element("genre").Value,
                              Price = Convert.ToDecimal(book.Element("price").Value),
                              PublishDate = Convert.ToDateTime(book.Element("publish_date").Value),
                              Description = book.Element("description").Value
                          }).Single();
                  }
                  catch
                  {
                      // Return null to signify failure.
                      return null;
                  }
              }
      
              /// <summary>
              /// Populates a book BookDetails class with the data for a book.
              /// </summary>
              private XElement[] FormatBookData(BookDetails book)
              {
                  XElement[] bookInfo =
                  {
                      new XElement("author", book.Author),
                      new XElement("title", book.Title),
                      new XElement("genre", book.Genre),
                      new XElement("price", book.Price.ToString()),
                      new XElement("publish_date", book.PublishDate.ToString()),
                      new XElement("description", book.Description)
                  };
                  return bookInfo;
              }
      
              /// <summary>
              /// Method to update an existing book in the catalog.
              /// Defines the implementation of the PUT method.
              /// </summary>
              public BookDetails UpdateBook(String id, BookDetails book)
              {
                  try
                  {
                      // Retrieve a specific book from the catalog.
                      XElement updateBook = xmlDocument.XPathSelectElement(String.Format("catalog/book[@id='{0}']", id));
                      // Verify that the book exists.
                      if (updateBook != null)
                      {
                          // Create elements for each of the book's data items.
                          XElement[] bookInfo = FormatBookData(book);
                          // Add the element to the book element.
                          updateBook.ReplaceNodes(bookInfo);
                          // Save the XML document.
                          xmlDocument.Save(xmlFilename);
                          // Return an object for the updated book.
                          return this.ReadBook(id);
                      }
                  }
                  catch (Exception ex)
                  {
                      // Rethrow the exception.
                      throw ex;
                  }
                  // Return null to signify failure.
                  return null;
              }
      
              /// <summary>
              /// Method to remove an existing book from the catalog.
              /// Defines the implementation of the DELETE method.
              /// </summary>
              public Boolean DeleteBook(String id)
              {
                  try
                  {
                      if (this.ReadBook(id) != null)
                      {
                          // Remove the specific child node from the catalog.
                          xmlDocument
                              .Elements("catalog")
                              .Elements("book")
                              .Where(x => x.Attribute("id").Value.Equals(id))
                              .Remove();
                          // Save the XML document.
                          xmlDocument.Save(xmlFilename);
                          // Return a success status.
                          return true;
                      }
                      else
                      {
                          // Return a failure status.
                          return false;
                      }
                  }
                  catch (Exception ex)
                  {
                      // Rethrow the exception.
                      throw ex;
                  }
              }
          }
      }
      
    4. BookDetails.cs 파일을 저장하고 닫습니다.

  9. Bookstore 컨트롤러를 Web API 프로젝트에 추가합니다.

    1. 솔루션 탐색기에서 Controllers 폴더를 마우스 오른쪽 단추로 클릭한 다음 추가를 클릭한 다음 컨트롤러를 클릭합니다.

    2. 스캐폴드 추가 대화 상자가 표시되면 Web API 2 컨트롤러 - 비어 있음을 강조 표시하고 추가를 클릭합니다.

    3. 컨트롤러 추가 대화 상자가 표시되면 컨트롤러 이름을 BooksController로 지정하고 추가를 클릭합니다.

    4. BooksController.cs 파일이 열리면 파일의 코드를 다음으로 바꿉니다.

      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Net;
      using System.Net.Http;
      using System.Web.Http;
      using BookStore.Models;
      
      namespace BookStore.Controllers
      {
          public class BooksController : ApiController
          {
              private BookRepository repository = null;
      
              // Define the class constructor.
              public BooksController()
              {
                  this.repository = new BookRepository();
              }
      
              /// <summary>
              /// Method to retrieve all of the books in the catalog.
              /// Example: GET api/books
              /// </summary>
              [HttpGet]
              public HttpResponseMessage Get()
              {
                  IEnumerable<BookDetails> books = this.repository.ReadAllBooks();
                  if (books != null)
                  {
                      return Request.CreateResponse<IEnumerable<BookDetails>>(HttpStatusCode.OK, books);
                  }
                  else
                  {
                      return Request.CreateResponse(HttpStatusCode.NotFound);
                  }
              }
      
              /// <summary>
              /// Method to retrieve a specific book from the catalog.
              /// Example: GET api/books/5
              /// </summary>
              [HttpGet]
              public HttpResponseMessage Get(String id)
              {
                  BookDetails book = this.repository.ReadBook(id);
                  if (book != null)
                  {
                      return Request.CreateResponse<BookDetails>(HttpStatusCode.OK, book);
                  }
                  else
                  {
                      return Request.CreateResponse(HttpStatusCode.NotFound);
                  }
              }
      
              /// <summary>
              /// Method to add a new book to the catalog.
              /// Example: POST api/books
              /// </summary>
              [HttpPost]
              public HttpResponseMessage Post(BookDetails book)
              {
                  if ((this.ModelState.IsValid) && (book != null))
                  {
                      BookDetails newBook = this.repository.CreateBook(book);
                      if (newBook != null)
                      {
                          var httpResponse = Request.CreateResponse<BookDetails>(HttpStatusCode.Created, newBook);
                          string uri = Url.Link("DefaultApi", new { id = newBook.Id });
                          httpResponse.Headers.Location = new Uri(uri);
                          return httpResponse;
                      }
                  }
                  return Request.CreateResponse(HttpStatusCode.BadRequest);
              }
      
              /// <summary>
              /// Method to update an existing book in the catalog.
              /// Example: PUT api/books/5
              /// </summary>
              [HttpPut]
              public HttpResponseMessage Put(String id, BookDetails book)
              {
                  if ((this.ModelState.IsValid) && (book != null) && (book.Id.Equals(id)))
                  {
                      BookDetails modifiedBook = this.repository.UpdateBook(id, book);
                      if (modifiedBook != null)
                      {
                          return Request.CreateResponse<BookDetails>(HttpStatusCode.OK, modifiedBook);
                      }
                      else
                      {
                          return Request.CreateResponse(HttpStatusCode.NotFound);
                      }
                  }
                  return Request.CreateResponse(HttpStatusCode.BadRequest);
              }
      
              /// <summary>
              /// Method to remove an existing book from the catalog.
              /// Example: DELETE api/books/5
              /// </summary>
              [HttpDelete]
              public HttpResponseMessage Delete(String id)
              {
                  BookDetails book = this.repository.ReadBook(id);
                  if (book != null)
                  {
                      if (this.repository.DeleteBook(id))
                      {
                          return Request.CreateResponse(HttpStatusCode.OK);
                      }
                  }
                  else
                  {
                      return Request.CreateResponse(HttpStatusCode.NotFound);
                  }
                  return Request.CreateResponse(HttpStatusCode.BadRequest);
              }
          }
      }
      
    5. BooksController.cs 파일을 저장하고 닫습니다.

  10. Web API 애플리케이션을 빌드하여 오류를 검사.

2단계: Windows Phone 8 Bookstore 카탈로그 프로젝트 추가

이 엔드 투 엔드 시나리오의 다음 단계는 Windows Phone 8에 대한 카탈로그 애플리케이션을 만드는 것입니다. 이 애플리케이션은 기본 사용자 인터페이스에 Windows Phone Databound App 템플릿을 사용하며, 이 자습서의 1단계에서 만든 Web API 애플리케이션을 데이터 원본으로 사용합니다.

  1. 솔루션 탐색기의 에서 BookStore 솔루션을 마우스 오른쪽 단추로 클릭한 다음 추가, 새 프로젝트를 차례로 클릭합니다.

  2. 새 프로젝트 대화 상자가 표시되면 설치됨, Visual C#, Windows Phone 차례로 확장합니다.

  3. Windows Phone Databound App을 강조 표시하고 이름에 BookCatalog를 입력한 다음 확인을 클릭합니다.

  4. BookCatalog 프로젝트에 Json.NET NuGet 패키지를 추가합니다.

    1. 솔루션 탐색기에서 BookCatalog 프로젝트에 대한 참조를 마우스 오른쪽 단추로 클릭한 다음 NuGet 패키지 관리를 클릭합니다.
    2. NuGet 패키지 관리 대화 상자가 표시되면 온라인 섹션을 확장하고 nuget.org 강조 표시합니다.
    3. 검색 필드에 Json.NET 입력하고 검색 아이콘을 클릭합니다.
    4. 검색 결과에서 Json.NET 강조 표시한 다음 설치를 클릭합니다.
    5. 설치가 완료되면 닫기를 클릭합니다.
  5. BookCatalog 프로젝트에 BookDetails 모델을 추가합니다. 여기에는 bookstore 클래스의 일반 모델이 포함됩니다.

    1. 솔루션 탐색기에서 BookCatalog 프로젝트를 마우스 오른쪽 단추로 클릭한 다음 추가를 클릭한 다음 새 폴더를 클릭합니다.

    2. 새 폴더 이름을 Models로 지정 합니다.

    3. 솔루션 탐색기에서 Models 폴더를 마우스 오른쪽 단추로 클릭한 다음 추가를 클릭한 다음 클래스를 클릭합니다.

    4. 새 항목 추가 대화 상자가 표시되면 클래스 파일 이름을 BookDetails.cs로 지정하고 추가를 클릭합니다.

    5. BookDetails.cs 파일이 열리면 파일의 코드를 다음으로 바꿉니다.

      using System;
      using System.Text;
      
      namespace BookCatalog.Models
      {
          /// <summary>
          /// Define a class that will hold the detailed information for a book.
          /// </summary>
          public class BookDetails
          {
              public String Id { get; set; }
              public String Title { get; set; }
              public String Author { get; set; }
              public String Genre { get; set; }
              public Decimal Price { get; set; }
              public DateTime PublishDate { get; set; }
              public String Description { get; set; }
          }
      }
      
    6. BookDetails.cs 파일을 저장하고 닫습니다.

  6. BookStore Web API 애플리케이션과 통신하는 기능을 포함하도록 MainViewModel.cs 클래스를 업데이트합니다.

    1. 솔루션 탐색기에서 ViewModels 폴더를 확장한 다음 MainViewModel.cs 파일을 두 번 클릭합니다.

    2. MainViewModel.cs 파일이 열리면 파일의 코드를 다음으로 바꿉니다. 상수 값을 Web API의 apiUrl 실제 URL로 업데이트해야 합니다.

      using System;
      using System.Collections.ObjectModel;
      using System.ComponentModel;
      using System.Net;
      using System.Net.NetworkInformation;
      using BookCatalog.Resources;
      using System.Collections.Generic;
      using Newtonsoft.Json;
      using BookCatalog.Models;
      
      namespace BookCatalog.ViewModels
      {
          public class MainViewModel : INotifyPropertyChanged
          {
              const string apiUrl = @"http://www.contoso.com/api/Books";
      
              public MainViewModel()
              {
                  this.Items = new ObservableCollection<ItemViewModel>();
              }
      
              /// <summary>
              /// A collection for ItemViewModel objects.
              /// </summary>
              public ObservableCollection<ItemViewModel> Items { get; private set; }
      
              public bool IsDataLoaded
              {
                  get;
                  private set;
              }
      
              /// <summary>
              /// Creates and adds a few ItemViewModel objects into the Items collection.
              /// </summary>
              public void LoadData()
              {
                  if (this.IsDataLoaded == false)
                  {
                      this.Items.Clear();
                      this.Items.Add(new ItemViewModel() { ID = "0", LineOne = "Please Wait...", LineTwo = "Please wait while the catalog is downloaded from the server.", LineThree = null });
                      WebClient webClient = new WebClient();
                      webClient.Headers["Accept"] = "application/json";
                      webClient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(webClient_DownloadCatalogCompleted);
                      webClient.DownloadStringAsync(new Uri(apiUrl));
                  }
              }
      
              private void webClient_DownloadCatalogCompleted(object sender, DownloadStringCompletedEventArgs e)
              {
                  try
                  {
                      this.Items.Clear();
                      if (e.Result != null)
                      {
                          var books = JsonConvert.DeserializeObject<BookDetails[]>(e.Result);
                          int id = 0;
                          foreach (BookDetails book in books)
                          {
                              this.Items.Add(new ItemViewModel()
                              {
                                  ID = (id++).ToString(),
                                  LineOne = book.Title,
                                  LineTwo = book.Author,
                                  LineThree = book.Description.Replace("\n", " ")
                              });
                          }
                          this.IsDataLoaded = true;
                      }
                  }
                  catch (Exception ex)
                  {
                      this.Items.Add(new ItemViewModel()
                      {
                          ID = "0",
                          LineOne = "An Error Occurred",
                          LineTwo = String.Format("The following exception occured: {0}", ex.Message),
                          LineThree = String.Format("Additional inner exception information: {0}", ex.InnerException.Message)
                      });
                  }
              }
      
              public event PropertyChangedEventHandler PropertyChanged;
              private void NotifyPropertyChanged(String propertyName)
              {
                  PropertyChangedEventHandler handler = PropertyChanged;
                  if (null != handler)
                  {
                      handler(this, new PropertyChangedEventArgs(propertyName));
                  }
              }
          }
      }
      
    3. MainViewModel.cs 파일을 저장하고 닫습니다.

  7. MainPage.xaml 파일을 업데이트하여 애플리케이션 이름을 사용자 지정합니다.

    1. 솔루션 탐색기에서 MainPage.xaml 파일을 두 번 클릭합니다.

    2. MainPage.xaml 파일이 열리면 다음 코드 줄을 찾습니다.

      <StackPanel Grid.Row="0" Margin="12,17,0,28">
          <TextBlock Text="MY APPLICATION" Style="{StaticResource PhoneTextNormalStyle}"/> 
          <TextBlock Text="page name" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
      </StackPanel>
      
    3. 이러한 줄을 다음으로 바꿉 있습니다.

      <StackPanel Grid.Row="0" Margin="12,17,0,28">
          <TextBlock Text="Book Store" Style="{StaticResource PhoneTextTitle1Style}"/> 
          <TextBlock Text="Current Catalog" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle2Style}"/>
      </StackPanel>
      
    4. MainPage.xaml 파일을 저장하고 닫습니다.

  8. DetailsPage.xaml 파일을 업데이트하여 표시된 항목을 사용자 지정합니다.

    1. 솔루션 탐색기에서 DetailsPage.xaml 파일을 두 번 클릭합니다.

    2. DetailsPage.xaml 파일이 열리면 다음 코드 줄을 찾습니다.

      <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
          <TextBlock Text="MY APPLICATION" Style="{StaticResource PhoneTextNormalStyle}"/>
          <TextBlock Text="{Binding LineOne}" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
      </StackPanel>
      
    3. 이러한 줄을 다음으로 바꿉 있습니다.

      <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
          <TextBlock Text="Book Store" Style="{StaticResource PhoneTextTitle1Style}"/>
          <TextBlock Text="{Binding LineOne}" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle2Style}"/>
      </StackPanel>
      
    4. DetailsPage.xaml 파일을 저장하고 닫습니다.

  9. Windows Phone 애플리케이션을 빌드하여 오류를 검사.

3단계: 엔드 투 엔드 솔루션 테스트

이 자습서의 필수 구성 요소 섹션에서 설명한 대로 로컬 시스템에서 Web API와 Windows Phone 8 프로젝트 간의 연결을 테스트하는 경우 로컬 컴퓨터의 Web API 애플리케이션에 Windows Phone 8 에뮬레이터 연결 문서의 지침에 따라 테스트 환경을 설정해야 합니다.

테스트 환경을 구성한 후에는 Windows Phone 애플리케이션을 시작 프로젝트로 설정해야 합니다. 이렇게 하려면 솔루션 탐색기에서 BookCatalog 애플리케이션을 강조 표시한 다음 시작 프로젝트로 설정을 클릭합니다.

'시작 프로젝트로 설정' 옵션에서 Windows Phone 애플리케이션을 설정하는 메뉴 옵션을 보여 주는 솔루션 탐색기 창의 스크린샷.
이미지를 클릭하여 확장

F5 키를 누르면 Visual Studio가 Windows Phone 에뮬레이터를 모두 시작합니다. 그러면 애플리케이션 데이터가 Web API에서 검색되는 동안 "잠시 기다려 주세요" 메시지가 표시됩니다.

휴대폰 에뮬레이터가 팝업되고 제목 Book Store 및 '기다려주세요' 메시지가 표시되는 솔루션 탐색기 창의 스크린샷
이미지를 클릭하여 확장

모든 것이 성공하면 카탈로그가 표시됩니다.

카탈로그에 제목이 있는 Book Store를 표시하는 휴대폰 에뮬레이터를 보여 주는 솔루션 탐색기 창의 스크린샷
이미지를 클릭하여 확장

책 제목을 탭하면 애플리케이션에 책 설명이 표시됩니다.

책 제목과 설명이 표시된 솔루션 탐색기 창에서 휴대폰 에뮬레이터의 스크린샷
이미지를 클릭하여 확장

애플리케이션이 Web API와 통신할 수 없는 경우 오류 메시지가 표시됩니다.

'오류가 발생했습니다'와 오류에 대한 간략한 설명을 보여 주는 솔루션 탐색기 창에 표시되는 휴대폰 에뮬레이터의 스크린샷.
이미지를 클릭하여 확장

오류 메시지를 탭하면 오류에 대한 추가 세부 정보가 표시됩니다.

솔루션 탐색기 대화 상자 위에 있는 휴대폰 에뮬레이터의 스크린샷에는 '오류 발생'과 오류 세부 정보가 표시됩니다.
이미지를 클릭하여 확장