Вызов веб-API из приложения Windows Phone 8 (C#)

Роберт Макмюррей (Robert McMurray)

В этом руководстве вы узнаете, как создать полный комплексный сценарий, состоящий из веб-API ASP.NET приложения, которое предоставляет каталог книг для приложения Windows Phone 8.

Общие сведения

Службы RESTful, такие как веб-API ASP.NET упрощают создание приложений на основе HTTP для разработчиков, абстрагируя архитектуру для серверных и клиентских приложений. Вместо создания проприетарного протокола на основе сокетов для обмена данными разработчикам веб-API просто необходимо опубликовать необходимые методы HTTP для своего приложения (например, GET, POST, PUT, DELETE), а разработчикам клиентских приложений необходимо использовать только методы HTTP, необходимые для приложения.

В этом комплексном руководстве вы узнаете, как использовать веб-API для создания следующих проектов:

Предварительные требования

  • Visual Studio 2013 с установленным пакетом SDK для Windows Phone 8
  • Windows 8 или более поздней версии в 64-разрядной системе с установленным Hyper-V
  • Список дополнительных требований см. в разделе Требования к системе на странице скачивания пакета SDK для Windows Phone 8.0.

Примечание

Если вы собираетесь проверить подключение между веб-API и проектами Windows Phone 8 в локальной системе, вам потребуется выполнить инструкции из статьи Подключение эмулятора Windows Phone 8 к приложениям веб-API на локальном компьютере, чтобы настроить среду тестирования.

Шаг 1. Создание проекта bookstore веб-API

Первым шагом этого комплексного руководства является создание проекта веб-API, который поддерживает все операции CRUD. Обратите внимание, что вы добавите проект приложения Windows Phone в это решение на шаге 2 этого руководства.

  1. Откройте Visual Studio 2013.

  2. Выберите Файл, Создать, а затем Проект.

  3. Когда откроется диалоговое окно Новый проект , разверните узел Установленные, Затем Шаблоны, Visual C# и Интернет.

    Снимок экрана: диалоговое окно
    Щелкните изображение, чтобы развернуть
  4. Выделите ASP.NET веб-приложение, введите Имя проекта BookStore и нажмите кнопку ОК.

  5. Когда откроется диалоговое окно Новый проект ASP.NET , выберите шаблон Веб-API и нажмите кнопку ОК.

    Снимок экрана: диалоговое окно bookstore проекта AS P NET с параметрами шаблона и полями проверка для выбора папки шаблона и основной ссылки.
    Щелкните изображение, чтобы развернуть
  6. Когда откроется проект веб-API, удалите пример контроллера из проекта:

    1. Разверните папку Контроллеры в обозревателе решений.
    2. Щелкните правой кнопкой мыши файл ValuesController.cs и выберите команду Удалить.
    3. Нажмите кнопку ОК при появлении запроса на подтверждение удаления.
  7. Добавление XML-файла данных в проект веб-API; Этот файл содержит содержимое каталога bookstore:

    1. Щелкните правой кнопкой мыши папку App_Data в обозревателе решений, выберите команду Добавить, а затем — Новый элемент.

    2. Когда откроется диалоговое окно Добавление нового элемента , выделите шаблон XML-файл .

    3. Присвойте файлу имяBooks.xmlи нажмите кнопку Добавить.

    4. После открытия файлаBooks.xml замените код в файле XML из примера файлаbooks.xml на сайте MSDN:

      <?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. Добавьте модель bookstore в проект веб-API; Эта модель содержит логику создания, чтения, обновления и удаления (CRUD) для приложения bookstore:

    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 в проект веб-API:

    1. Щелкните правой кнопкой мыши папку Контроллеры в обозревателе решений, выберите команду Добавить, а затем контроллер.

    2. Когда откроется диалоговое окно Добавление шаблона , выберите Контроллер Веб-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. Создайте приложение веб-API для проверка ошибок.

Шаг 2. Добавление проекта каталога bookstore Windows Phone 8

Следующим шагом этого комплексного сценария является создание приложения каталога для Windows Phone 8. Это приложение будет использовать шаблон приложения Windows Phone databound для пользовательского интерфейса по умолчанию, а также приложение веб-API, созданное на шаге 1 этого руководства, в качестве источника данных.

  1. Щелкните правой кнопкой мыши решение BookStore в обозревателе решений, а затем выберите Добавить, а затем — Создать проект.

  2. Когда откроется диалоговое окно Новый проект, разверните узел Установленные, затем Visual C#, а затем Windows Phone.

  3. Выделите Windows Phone Databound App, введите Имя BookCatalog и нажмите кнопку ОК.

  4. Добавьте пакет NuGet Json.NET в проект BookCatalog :

    1. Щелкните правой кнопкой мыши ссылки для проекта BookCatalog в обозревателе решений и выберите пункт Управление пакетами NuGet.
    2. Когда откроется диалоговое окно Управление пакетами NuGet , разверните раздел Интернет и выделите nuget.org.
    3. Введите Json.NET в поле поиска и щелкните значок поиска.
    4. Выделите Json.NET в результатах поиска и нажмите кнопку Установить.
    5. После завершения установки нажмите кнопку Закрыть.
  5. Добавьте модель BookDetails в проект BookCatalog ; содержит универсальную модель класса 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. Обновите класс MainViewModel.cs , чтобы включить функции для взаимодействия с приложением веб-API BookStore:

    1. Разверните папку ViewModels в обозревателе решений и дважды щелкните файл MainViewModel.cs .

    2. При открытии файла MainViewModel.cs замените код в файле следующим кодом: Обратите внимание, что необходимо обновить значение apiUrl константы, указав фактический URL-адрес веб-API:

      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. Тестирование комплексного решения

Как упоминалось в разделе Предварительные требования этого руководства, при тестировании подключения между веб-API и проектами Windows Phone 8 в локальной системе необходимо выполнить инструкции из статьи Подключение эмулятора Windows Phone 8 к приложениям веб-API на локальном компьютере, чтобы настроить среду тестирования.

После настройки среды тестирования необходимо задать Windows Phone приложение в качестве запускаемого проекта. Для этого выделите приложение BookCatalog в обозревателе решений и нажмите кнопку Задать в качестве запускаемого проекта:

Снимок экрана: окно обозревателя решений с параметрами меню для настройки приложения Windows Phone в параметре
Щелкните изображение, чтобы развернуть

При нажатии клавиши F5 Visual Studio запустит эмулятор Windows Phone, который отобразит сообщение "Подождите", пока данные приложения будут получены из веб-API:

Снимок экрана: окно обозревателя решений, на котором отображается всплывающее окно эмулятора телефона с заголовком Book Store и сообщением
Щелкните изображение, чтобы развернуть

Если все прошло успешно, отобразится каталог:

Снимок экрана: окно обозревателя решений с эмулятором телефона, на котором отображается магазин книг с названиями в каталоге.
Щелкните изображение, чтобы развернуть

Если коснуться названия книги, приложение отобразит описание книги:

Снимок экрана: эмулятор телефона в окне обозревателя решений с заголовком и описанием книги.
Щелкните изображение, чтобы развернуть

Если приложению не удается связаться с веб-API, отобразится сообщение об ошибке:

Снимок экрана: эмулятор телефона, отображаемый в окне обозревателя решений с сообщением
Щелкните изображение, чтобы развернуть

Если нажать на сообщение об ошибке, отобразятся все дополнительные сведения об ошибке:

Снимок экрана: эмулятор телефона в диалоговом окне обозревателя решений с сообщением
Щелкните изображение, чтобы развернуть