從 Windows Phone 8 應用程式呼叫 Web API (C#)
在本教學課程中,您將瞭解如何建立完整的端對端案例,其中包含提供書籍目錄給Windows Phone 8 個應用程式的 ASP.NET Web API應用程式。
概觀
ASP.NET Web API等 RESTful 服務藉由抽象化伺服器端和用戶端應用程式的架構,簡化開發人員的 HTTP 型應用程式建立。 Web API 開發人員只需要發佈其應用程式的必要 HTTP 方法, (例如:GET、POST、PUT、DELETE) 和用戶端應用程式開發人員只需要取用其應用程式所需的 HTTP 方法,而不是建立專屬通訊端型通訊協定。
在此端對端教學課程中,您將瞭解如何使用 Web API 來建立下列專案:
- 在本教學課程的第一個部分中,您將建立 ASP.NET Web API應用程式,以支援所有建立、讀取、更新和刪除 (CRUD) 作業來管理書籍目錄。 此應用程式會使用 MSDN 的 範例 XML 檔案 (books.xml) 。
- 在本教學課程的第二個部分中,您將建立互動式Windows Phone 8 個應用程式,從 Web API 應用程式擷取資料。
必要條件
- 已安裝 Windows Phone 8 SDK 的 Visual Studio 2013
- 已安裝 Hyper-V 的 64 位系統上的 Windows 8 或更新版本
- 如需其他需求的清單,請參閱Windows Phone SDK 8.0下載頁面上的系統需求一節。
注意
如果您要測試 Web API 與本機系統上Windows Phone 8 個專案之間的連線能力,您必須遵循在本機電腦上將 Windows Phone 8 模擬器連線至 Web API 應用程式一文中的指示,以設定測試環境。
步驟 1:建立 Web API 書籍存放區專案
此端對端教學課程的第一個步驟是建立支援所有 CRUD 作業的 Web API 專案;請注意,您將在本教學課程的步驟 2中,將Windows Phone應用程式專案新增至此解決方案。
開啟Visual Studio 2013。
按一下 [ 檔案]、[ 新增] 和 [ 專案]。
顯示 [ 新增專案 ] 對話方塊時,展開 [ 已安裝]、[ 範本]、[ Visual C#] 和 [ Web]。
按一下影像以展開 醒目提示 ASP.NET Web 應用程式,輸入BookStore以取得專案名稱,然後按一下 [確定]。
當 [ 新增 ASP.NET 專案 ] 對話方塊顯示時,請選取 Web API 範本,然後按一下 [ 確定]。
按一下影像以展開 當 Web API 專案開啟時,請從專案移除範例控制器:
- 展開方案總管中的 Controllers 資料夾。
- 以滑鼠右鍵按一下 ValuesController.cs 檔案,然後按一下 [ 刪除]。
- 出現提示以確認刪除時,按一下 [ 確定 ]。
將 XML 資料檔案新增至 Web API 專案;此檔案包含書籍存放區目錄的內容:
以滑鼠右鍵按一下方案總管中的 App_Data 資料夾,然後按一下 [ 新增],然後按一下 [ 新增專案]。
顯示 [ 加入新專案 ] 對話方塊時,醒目提示 XML 檔案 範本。
將檔案 命名Books.xml,然後按一下 [ 新增]。
開啟 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>
儲存並關閉 XML 檔案。
將書籍存放區模型新增至 Web API 專案;此模型包含書籍存放區應用程式的建立、讀取、更新和刪除 (CRUD) 邏輯:
以滑鼠右鍵按一下方案總管中的 Models 資料夾,然後按一下 [ 新增],然後按一下 [ 類別]。
顯示 [ 新增專案 ] 對話方塊時,將類別檔案命名為 BookDetails.cs,然後按一下 [ 新增]。
開啟 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; } } } }
儲存並關閉 BookDetails.cs 檔案。
將書籍存放區控制器新增至 Web API 專案:
以滑鼠右鍵按一下方案總管中的 Controllers 資料夾,然後按一下 [ 新增],然後按一下 [ 控制器]。
顯示 [ 新增 Scaffold ] 對話方塊時,醒目提示 [Web API 2 控制器 - 空白],然後按一下 [ 新增]。
顯示 [ 新增控制器 ] 對話方塊時,將控制器命名為 BooksController,然後按一下 [ 新增]。
開啟 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); } } }
儲存並關閉 BooksController.cs 檔案。
建置 Web API 應用程式以檢查錯誤。
步驟 2:新增Windows Phone 8 個書籍存放區目錄專案
此端對端案例的下一個步驟是建立Windows Phone 8 的目錄應用程式。 此應用程式會針對預設使用者介面使用Windows Phone Databound 應用程式範本,並使用您在本教學課程的步驟 1中建立的 Web API 應用程式作為資料來源。
以滑鼠右鍵按一下方案總管中的 BookStore 方案,然後按一下 [ 新增],然後按一下 [ 新增專案]。
顯示 [新增專案] 對話方塊時,展開 [已安裝],然後展開 [Visual C#],然後Windows Phone。
醒目提示Windows Phone Databound App,輸入BookCatalog以取得名稱,然後按一下 [確定]。
將 Json.NET NuGet 套件新增至 BookCatalog 專案:
- 以滑鼠右鍵按一下方案總管中BookCatalog專案的[參考],然後按一下 [管理 NuGet 套件]。
- 顯示 [ 管理 NuGet 套件 ] 對話方塊時,展開 [ 線上 ] 區段,並反白 顯示 nuget.org。
- 在搜尋欄位中輸入 Json.NET ,然後按一下搜尋圖示。
- 在搜尋結果中 反白顯示 Json.NET ,然後按一下 [ 安裝]。
- 安裝完成時,按一下 [ 關閉]。
將 BookDetails 模型新增至 BookCatalog 專案;這包含書籍存放區類別的泛型模型:
以滑鼠右鍵按一下方案總管中的 BookCatalog 專案,然後按一下 [ 新增],然後按一下 [ 新增資料夾]。
將新資料夾命名為 Models。
以滑鼠右鍵按一下方案總管中的 Models 資料夾,然後按一下 [ 新增],然後按一下 [ 類別]。
顯示 [ 新增專案 ] 對話方塊時,將類別檔案命名為 BookDetails.cs,然後按一下 [ 新增]。
開啟 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; } } }
儲存並關閉 BookDetails.cs 檔案。
更新 MainViewModel.cs 類別,以包含與 BookStore Web API 應用程式通訊的功能:
展開方案總管中的 ViewModels 資料夾,然後按兩下 MainViewModel.cs 檔案。
開啟 MainViewModel.cs 檔案時,請將檔案中的程式碼取代為下列專案;請注意,您必須使用 Web API 的實際 URL 來更新常數的值
apiUrl
: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)); } } } }
儲存並關閉 MainViewModel.cs 檔案。
更新 MainPage.xaml 檔案以自訂應用程式名稱:
按兩下方案總管中的 MainPage.xaml 檔案。
開啟 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>
將這些行取代為下列專案:
<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>
儲存並關閉 MainPage.xaml 檔案。
更新 DetailsPage.xaml 檔案以自訂顯示的專案:
按兩下方案總管中的 DetailsPage.xaml 檔案。
當 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>
將這些行取代為下列專案:
<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>
儲存並關閉 DetailsPage.xaml 檔案。
建置Windows Phone應用程式以檢查錯誤。
步驟 3:測試端對端解決方案
如本教學課程的必要條件一節所述,當您在本機系統上測試 Web API 與 Windows Phone 8 個專案之間的連線時,您必須遵循在本機電腦上將 Windows Phone 8 模擬器連線至 Web API 應用程式一文中的指示來設定測試環境。
設定測試環境之後,您必須將Windows Phone應用程式設定為啟動專案。 若要這樣做,請在方案總管中醒目提示 BookCatalog 應用程式,然後按一下 [ 設定為啟始專案]:
按一下影像以展開 |
當您按下 F5 時,Visual Studio 會啟動Windows Phone模擬器,這會在應用程式資料從 Web API 擷取時顯示「請等候」訊息:
按一下影像以展開 |
如果一切都成功,您應該會看到目錄顯示:
按一下影像以展開 |
如果您點選任何書籍標題,應用程式會顯示書籍描述:
按一下影像以展開 |
如果應用程式無法與您的 Web API 通訊,將會顯示錯誤訊息:
按一下影像以展開 |
如果您點選錯誤訊息,將會顯示有關錯誤的任何其他詳細資料:
按一下影像以展開 |
意見反應
https://aka.ms/ContentUserFeedback。
即將登場:在 2024 年,我們將逐步淘汰 GitHub 問題作為內容的意見反應機制,並將它取代為新的意見反應系統。 如需詳細資訊,請參閱:提交並檢視相關的意見反應