Compartir a través de


Llamar a Web API desde una aplicación de Windows Phone 8 (C#)

por Robert McMurray

En este tutorial, aprenderá a crear un escenario completo de un extremo a otro que consta de una aplicación de ASP.NET Web API que proporciona un catálogo de libros a una aplicación de Windows Phone 8.

Información general

Los servicios RESTful, como ASP.NET Web API, simplifican la creación de aplicaciones basadas en HTTP para desarrolladores mediante la abstracción de la arquitectura de las aplicaciones del lado servidor y del lado cliente. En lugar de crear un protocolo de su propiedad basado en sockets para la comunicación, los desarrolladores de Web API solo necesitan publicar los métodos HTTP necesarios para su aplicación (por ejemplo: GET, POST, PUT, DELETE) y los desarrolladores de aplicaciones cliente solo necesitan consumir los métodos HTTP necesarios para su aplicación.

En este tutorial de un extremo a otro, aprenderá a usar Web API para crear los proyectos siguientes:

Requisitos previos

  • Visual Studio 2013 con Windows Phone 8 SDK instalado.
  • Windows 8 o posterior en un sistema de 64 bits con Hyper-V instalado
  • Para obtener una lista de requisitos adicionales, consulte la sección Requisitos del sistema en la página de descarga del SDK de Windows Phone 8.0.

Nota:

Si va a probar la conectividad entre Web API y los proyectos de Windows Phone 8 en el sistema local, tendrá que seguir las instrucciones del artículo Conexión del emulador de Windows Phone 8 a aplicaciones de Web API en un equipo local para configurar el entorno de prueba.

Paso 1: Creación del proyecto Bookstore de Web API

El primer paso de este tutorial de un extremo a otro es crear un proyecto de Web API que admita todas las operaciones CRUD. Tenga en cuenta que agregará el proyecto de aplicación de Windows Phone a esta solución en el paso 2 de este tutorial.

  1. Abra Visual Studio 2013.

  2. Haga clic en Archivo, Nuevo y en Proyecto.

  3. Cuando aparezca el cuadro de diálogo Nuevo proyecto, expanda Instalado, Plantillas, Visual C# y, a continuación, Web.

    Screenshot of the 'new project' dialog box, showing the file path in the menu and highlighting the steps to create the new project.
    Haga clic en la imagen que desea expandir
  4. Resalte Aplicación web ASP.NET, escriba BookStore como nombre del proyecto y haga clic en Aceptar.

  5. Cuando aparezca el cuadro de diálogo Nuevo proyecto de ASP.NET, seleccione la plantilla Web API y, a continuación, haga clic en Aceptar.

    Screenshot of the A S P dot NET project bookstore dialog box, showing template options and check boxes to select template folder and core reference.
    Haga clic en la imagen que desea expandir
  6. Cuando se abra el proyecto de Web API, elimine el controlador de ejemplo del proyecto:

    1. Expanda la carpeta Controladores en el Explorador de soluciones.
    2. Haga clic con el botón derecho en el archivo ValuesController.cs y, a continuación, haga clic en Eliminar.
    3. Cuando se le pida que confirme la eliminación, haga clic en Aceptar.
  7. Agregue un archivo de datos XML al proyecto de Web API; este archivo incluye el contenido del catálogo de librerías:

    1. En el Explorador de soluciones, haga clic con el botón derecho en la carpeta App_Data, haga clic en Agregar y, a continuación, en Nuevo elemento.

    2. Cuando se muestre el cuadro de diálogo Agregar nuevo elemento, resalte la plantilla Archivo XML.

    3. Asigne el nombre Books.xml al archivo y, a continuación, haga clic en Agregar.

    4. Cuando se abra el archivo Books.xml, reemplace el código del archivo por el XML del archivo books.xml de ejemplo en 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. Guarde y cierre el archivo XML.

  8. Agregue el modelo de librería al proyecto de Web API; este modelo contiene la lógica de creación, lectura, actualización y eliminación (CRUD) de la aplicación bookstore:

    1. Haga clic con el botón derecho en la carpeta Modelos del Explorador de soluciones, haga clic en Agregar y, a continuación, en Clase.

    2. Cuando aparezca el cuadro de diálogo Agregar nuevo elemento, asigne el nombre BookDetails.cs al archivo de clase y, a continuación, haga clic en Agregar.

    3. Cuando se abra el archivo BookDetails.cs, reemplace el código del archivo por el siguiente:

      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. Guarde y cierre el archivo BookDetails.cs.

  9. Agregue el controlador de librería al proyecto de Web API:

    1. En el Explorador de soluciones, haga clic con el botón derecho en la carpeta Controladores, haga clic en Agregar y, a continuación, haga clic en Controlador.

    2. Cuando aparezca el cuadro de diálogo Agregar scaffold, resalte Controlador de Web API 2: Vacío y, a continuación, haga clic en Agregar.

    3. Cuando se muestre el cuadro de diálogo Agregar controlador, asigne al controlador el nombre BooksController y, a continuación, haga clic en Agregar.

    4. Cuando se abra el archivo BooksController.cs, reemplace el código del archivo por el siguiente:

      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. Guarde y cierre el archivo BooksController.cs.

  10. Cree la aplicación de Web API para comprobar si hay errores.

Paso 2: Agregar el proyecto de catálogo de librerías de Windows Phone 8

El siguiente paso de este escenario de un extremo a otro es crear la aplicación de catálogo de Windows Phone 8. Esta aplicación usará la plantilla Aplicación de entrada de datos de Windows Phone para la interfaz de usuario predeterminada y usará la aplicación de Web API que creó en el paso 1 de este tutorial como origen de datos.

  1. Haga clic con el botón derecho en la solución BookStore del Explorador de soluciones, haga clic en Agregar y, a continuación, en Nuevo proyecto.

  2. Cuando aparezca el cuadro de diálogo Nuevo proyecto, expanda Instalado, Visual C# y, a continuación, Windows Phone.

  3. Resalte Aplicación de entrada de datos de Windows Phone, escriba BookCatalog como nombre y, a continuación, haga clic en Aceptar.

  4. Agregue el paquete NuGet Json.NET al proyecto BookCatalog:

    1. Haga clic con el botón derecho en Referencias del proyecto BookCatalog del Explorador de soluciones y, a continuación, haga clic en Administrar paquetes NuGet.
    2. Cuando aparezca el cuadro de diálogo Administrar paquetes NuGet, expanda la sección En línea y resalte nuget.org.
    3. Escriba Json.NET en el campo de búsqueda y haga clic en el icono de búsqueda.
    4. Resalte Json.NET en los resultados de la búsqueda y, a continuación, haga clic en Instalar.
    5. Cuando la instalación se complete, haga clic en Cerrar.
  5. Agregue el modelo BookDetails al proyecto BookCatalog; contiene un modelo genérico de la clase de librería:

    1. Haga clic con el botón derecho en el proyecto BookCatalog del Explorador de soluciones y, a continuación, haga clic en Agregar y en Nueva carpeta.

    2. Asigne a la nueva carpeta el nombre Modelos.

    3. Haga clic con el botón derecho en la carpeta Modelos del Explorador de soluciones, haga clic en Agregar y, a continuación, en Clase.

    4. Cuando aparezca el cuadro de diálogo Agregar nuevo elemento, asigne el nombre BookDetails.cs al archivo de clase y, a continuación, haga clic en Agregar.

    5. Cuando se abra el archivo BookDetails.cs, reemplace el código del archivo por el siguiente:

      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. Guarde y cierre el archivo BookDetails.cs.

  6. Actualice la clase MainViewModel.cs para que incluya la funcionalidad para comunicarse con la aplicación BookStore de Web API:

    1. Expanda la carpeta ViewModels en el Explorador de soluciones y, a continuación, haga doble clic en el archivo MainViewModel.cs.

    2. Cuando se abra el archivo MainViewModel.cs, reemplace el código del archivo por lo siguiente; tenga en cuenta que tendrá que actualizar el valor de la constante apiUrl con la dirección URL real de Web 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. Guarde y cierre el archivo MainViewModel.cs.

  7. Actualice el archivo MainPage.xaml para personalizar el nombre de la aplicación:

    1. En el Explorador de soluciones, haga doble clic en el archivo MainPage.xaml.

    2. Cuando se abra el archivo MainPage.xaml, busque las siguientes líneas de código:

      <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. Reemplace esas líneas por lo siguiente:

      <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. Guarde y cierre el archivo MainPage.xaml.

  8. Actualice el archivo DetailsPage.xaml para personalizar los elementos mostrados:

    1. En el Explorador de soluciones, haga doble clic en el archivo DetailsPage.xaml.

    2. Cuando se abra el archivo DetailsPage.xaml, busque las siguientes líneas de código:

      <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. Reemplace esas líneas por lo siguiente:

      <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. Guarde y cierre el archivo DetailsPage.xaml.

  9. Cree la aplicación de Windows Phone para comprobar si hay errores.

Paso 3: Probar la solución de un extremo a otro

Como se ha mencionado en la sección Requisitos previos de este tutorial, si va a probar la conectividad entre Web API y los proyectos de Windows Phone 8 en el sistema local, tendrá que seguir las instrucciones del artículo Conexión del emulador de Windows Phone 8 a aplicaciones de Web API en un equipo local para configurar el entorno de prueba.

Una vez configurado el entorno de prueba, deberá establecer la aplicación de Windows Phone como proyecto de inicio. Para ello, resalte la aplicación BookCatalog en el Explorador de soluciones y, a continuación, haga clic en Establecer como proyecto de inicio:

Screenshot of the solution explorer window, showing the menu options, to set the windows phone application in the 'set as start up project' option.
Haga clic en la imagen que desea expandir

Al presionar F5, Visual Studio iniciará el emulador de Windows Phone, que mostrará un mensaje "Espere" mientras se recuperan los datos de la aplicación de Web API:

Screenshot of the solution explorer window, showing the phone emulator pop up over it, displaying the title Book Store and the 'please wait' message.
Haga clic en la imagen que desea expandir

Si todo se realiza correctamente, debería aparecer el catálogo:

Screenshot of the solution explorer window, showing the phone emulator over it, displaying the Book Store with the titles in the catalog.
Haga clic en la imagen que desea expandir

Si pulsa algún título de libro, la aplicación mostrará la descripción del libro:

Screenshot of the phone emulator, over the solution explorer window, displaying a book title and description.
Haga clic en la imagen que desea expandir

Si la aplicación no puede comunicarse con Web API, aparecerá un mensaje de error:

Screenshot of the phone emulator, displayed over the solution explorer window, showing an 'An Error Occurred' and a brief description of the error.
Haga clic en la imagen que desea expandir

Si pulsa en el mensaje de error, se mostrarán detalles adicionales:

Screenshot of the phone emulator, over the solution explorer dialog box, showing 'An Error Occurred' followed by details of the error.
Haga clic en la imagen que desea expandir