共用方式為


使用 AJAX 實作對應實例

Microsoft 提供

下載 PDF

這是免費的 "NerdDinner" 應用程式教學課程的第 11 個步驟,詳細介紹了如何使用 ASP.NET MVC 1 建置一個小型但完整的 Web 應用程式。

步驟 11 示範如何將 AJAX 對應支援整合到我們的 NerdDinner 應用程式中,讓建立、編輯或檢視 Dinners 的使用者以圖形方式查看 Dinner 的位置。

如果使用 ASP.NET MVC 3,建議遵循 MVC 3 使用者入門MVC Music 市集教學課程。

NerdDinner 步驟 11:整合 AJAX 對應

現在,我們將透過整合 AJAX 對應支援來使應用程式在視覺上更加令人興奮。 這可讓建立、編輯或檢視 Dinners 的使用者以圖形方式查看 Dinner 的位置。

建立地圖部分檢視

我們將在應用程式中的數個位置使用對應功能。 為了保持我們的程式碼 DRY,我們會將常見對應功能封裝在單一部分範本內,可以跨多個控制器動作和檢視重複使用。 我們將此部分檢視命名為 "map.ascx",並在 \Views\Dinners 目錄中建立它。

我們可以在 \Views\Dinners 目錄上按一下滑鼠右鍵,然後選擇新增 -> 檢視功能表命令建立 map.ascx 部分。 我們將該檢視命名為 "Map.ascx",勾選為部分檢視,並指出我們將傳遞一個強型別的 "Dinner" 模型類別。

[新增檢視] 對話方塊的螢幕擷取畫面。Nerd Dinner dot Models dot Dinner 在 [檢視資料類別] 對話方塊中寫入。

當我們按一下 [新增] 按鈕時,將會建立部分範本。 然後,我們會更新 Map.ascx 檔案以取得下列內容:

<script src="http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.2" type="text/javascript"></script>
<script src="/Scripts/Map.js" type="text/javascript"></script>

<div id="theMap">
</div>

<script type="text/javascript">
   
    $(document).ready(function() {
        var latitude = <%=Model.Latitude%>;
        var longitude = <%=Model.Longitude%>;
                
        if ((latitude == 0) || (longitude == 0))
            LoadMap();
        else
            LoadMap(latitude, longitude, mapLoaded);
    });
      
   function mapLoaded() {
        var title = "<%=Html.Encode(Model.Title) %>";
        var address = "<%=Html.Encode(Model.Address) %>";
    
        LoadPin(center, title, address);
        map.SetZoomLevel(14);
    } 
      
</script>

第一個<指令碼>參考會指向 Microsoft Virtual Earth 6.2 對應資料庫。 第二個<指令碼>參考指向我們很快就會建立的 map.js 檔案,以封裝常見的 Javascript 對應邏輯。 <div id=“theMap”> 項目是 Virtual Earth 將用來裝載地圖的 HTML 容器。

然後,我們有內嵌<指令碼>區塊,其中包含此檢視專屬的兩個 JavaScript 函式。 第一個函式會使用 jQuery 來連接當頁面準備好執行用戶端指令碼時執行的函式。 它會呼叫 LoadMap() 協助程式函式,我們將在 Map.js 指令碼檔案中定義,以載入 Virtual Earth 地圖控制項。 第二個函式是回撥事件處理常式,會在地圖上新增一個圖釘,以標示某個位置。

請注意,我們在用戶端指令碼區塊中使用伺服器端 <%= %> 區塊,以內嵌我們想要對應至 JavaScript 的 Dinner 緯度和經度。 這是輸出用戶端指令碼可以使用的動態值的實用技術 (不需要個別的 AJAX 回撥伺服器來擷取值,讓其速度更快)。 當檢視在伺服器上轉譯時,<%= %> 區塊將會執行,因此 HTML 的輸出最終會包含內嵌的 JavaScript 值 (例如:var latitude = 47.64312;)。

建立 Map.js 公用程式庫

現在讓我們建立 Map.js 檔案用來封裝地圖的 JavaScript 功能 (並實作上述的 LoadMap 和 LoadPin 方法)。 我們可以以滑鼠右鍵按一下專案內的 \Scripts 目錄,然後選擇 [新增 -> 新項目] 功能表命令、選取 JScript 項目,並將其命名為 “Map.js”。

以下是我們將新增至 Map.js 檔案的 JavaScript 程式碼,該檔案會與 Virtual Earth 互動以顯示我們的地圖,並為我們的 Dinners 新增位置圖釘:

var map = null;
var points = [];
var shapes = [];
var center = null;

function LoadMap(latitude, longitude, onMapLoaded) {
    map = new VEMap('theMap');
    options = new VEMapOptions();
    options.EnableBirdseye = false;

    // Makes the control bar less obtrusize.
    map.SetDashboardSize(VEDashboardSize.Small);
    
    if (onMapLoaded != null)
        map.onLoadMap = onMapLoaded;

    if (latitude != null && longitude != null) {
        center = new VELatLong(latitude, longitude);
    }

    map.LoadMap(center, null, null, null, null, null, null, options);
}

function LoadPin(LL, name, description) {
    var shape = new VEShape(VEShapeType.Pushpin, LL);

    //Make a nice Pushpin shape with a title and description
    shape.SetTitle("<span class=\"pinTitle\"> " + escape(name) + "</span>");
    if (description !== undefined) {
        shape.SetDescription("<p class=\"pinDetails\">" + 
        escape(description) + "</p>");
    }
    map.AddShape(shape);
    points.push(LL);
    shapes.push(shape);
}

function FindAddressOnMap(where) {
    var numberOfResults = 20;
    var setBestMapView = true;
    var showResults = true;

    map.Find("", where, null, null, null,
           numberOfResults, showResults, true, true,
           setBestMapView, callbackForLocation);
}

function callbackForLocation(layer, resultsArray, places,
            hasMore, VEErrorMessage) {
            
    clearMap();

    if (places == null) 
        return;

    //Make a pushpin for each place we find
    $.each(places, function(i, item) {
        description = "";
        if (item.Description !== undefined) {
            description = item.Description;
        }
        var LL = new VELatLong(item.LatLong.Latitude,
                        item.LatLong.Longitude);
                        
        LoadPin(LL, item.Name, description);
    });

    //Make sure all pushpins are visible
    if (points.length > 1) {
        map.SetMapView(points);
    }

    //If we've found exactly one place, that's our address.
    if (points.length === 1) {
        $("#Latitude").val(points[0].Latitude);
        $("#Longitude").val(points[0].Longitude);
    }
}

function clearMap() {
    map.Clear();
    points = [];
    shapes = [];
}

將地圖與 [建立] 和 [編輯] 表單整合

我們現在將 [地圖] 支援與現有的 [建立] 和 [編輯] 案例整合。 好消息是,這很容易完成,而且不需要變更任何控制器程式碼。 由於我們的 [建立] 和 [編輯] 檢視共用常見的 "DinnerForm" 部分檢視來實作 Dinner 表單的 UI,我們只需在一個地方新增地圖,就能讓 [建立] 和 [編輯] 案例都使用它。

我們只需要開啟 \Views\Dinners\DinnerForm.ascx 部分檢視,並更新它以包含我們的新地圖部分。 以下是新增地圖後更新的 DinnerForm 的外觀 (請注意:為求簡潔,下面的程式碼片段已省略 HTML 表單項目):

<%= Html.ValidationSummary() %>
 
<% using (Html.BeginForm()) { %>
 
    <fieldset>

        <div id="dinnerDiv">
            <p>
               [HTML Form Elements Removed for Brevity]
            </p>                 
            <p>
               <input type="submit" value="Save"/>
            </p>
        </div>
        
        <div id="mapDiv">    
            <%Html.RenderPartial("Map", Model.Dinner); %>
        </div> 
            
    </fieldset>

    <script type="text/javascript">

        $(document).ready(function() {
            $("#Address").blur(function(evt) {
                $("#Latitude").val("");
                $("#Longitude").val("");

                var address = jQuery.trim($("#Address").val());
                if (address.length < 1)
                    return;

                FindAddressOnMap(address);
            });
        });
    
    </script>

<% } %>

上述 DinnerForm 部分採用類型為 "DinnerFormViewModel" 的物件作為其模型類型 (因為它需要 Dinner 物件,以及 SelectList 以填入國家/地區的下拉式清單)。 我們的 [地圖] 部分只需要一個 "Dinner" 類型的物件作為其模型類型,因此當我們轉譯這個地圖部分視圖時,我們只需將 DinnerFormViewModel 的 Dinner 子屬性傳遞給它:

<% Html.RenderPartial("Map", Model.Dinner); %>

我們新增至部分的 JavaScript 函式會使用 jQuery 將 [模糊] 事件附加至 [地址] HTML 文字方塊。 您可能聽說過 [焦點] 事件,這些事件會在使用者按一下或用鍵盤切換到文字方塊時觸發。 [模糊] 事件則與其相反,是在使用者退出文字方塊時觸發。 上述事件處理常式會在發生這種情況時清除緯度和經度文字方塊值,然後在我們的地圖上繪製新的地址位置。 我們在 map.js 檔案中定義的回撥事件處理常式,會根據提供的地址使用虛擬地球返回的資料,更新表單上的經度和緯度文字方塊。

現在,當我們再次執行應用程式,然後按一下 "Host Dinner" 索引標籤時,就會看到顯示的預設地圖以及標準 Dinner 表單項目:

顯示預設地圖的 Host Dinner 頁面的螢幕擷取畫面。

當我們輸入地址,然後選取索引標籤時,地圖會動態更新以顯示位置,而我們的事件處理常式會以位置值填入緯度/經度文字方塊:

顯示地圖的 Nerd Dinners 頁面的螢幕擷取畫面。

如果我們儲存新的 Dinner,然後再次開啟它進行編輯,就會發現地圖位置會在頁面載入時顯示:

Nerd Dinners 網站上的 [編輯] 頁面螢幕擷取畫面。

每次變更地址欄位時,地圖和緯度/經度座標都會更新。

現在地圖會顯示 Dinner 位置,我們也可以將緯度和經度表單欄位從可見文字方塊變更為隱藏項目 (因為地圖會在每次輸入地址時自動更新它們)。 若要這麼做,我們將從使用 Html.TextBox() HTML 協助程式切換為使用 Html.Hidden() 協助程式方法:

<p>
    <%= Html.Hidden("Latitude", Model.Dinner.Latitude)%>
    <%= Html.Hidden("Longitude", Model.Dinner.Longitude)%>
</p>

現在我們的表單更方便使用者使用,避免顯示原始緯度/經度 (同時仍將它們與每個 Dinner 儲存在資料庫中):

Nerd Dinners 頁面上地圖的螢幕擷取畫面。

將地圖與詳細資料檢視整合

既然我們已將地圖與 [建立] 和 [編輯] 案例整合,讓我們也將其與 [詳細資料] 案例整合。 我們需要做的就是在 [詳細資料] 檢視中呼叫 <% Html.RenderPartial("map"); %>。

以下是完整 [詳細資料] 檢視的原始程式碼 (具有地圖整合) 的外觀:

<asp:Content ID="Title" ContentPlaceHolderID="TitleContent"runat="server">
    <%= Html.Encode(Model.Title) %>
</asp:Content>

<asp:Content ID="details" ContentPlaceHolderID="MainContent" runat="server">

    <div id="dinnerDiv">

        <h2><%=Html.Encode(Model.Title) %></h2>
        <p>
            <strong>When:</strong> 
            <%=Model.EventDate.ToShortDateString() %> 

            <strong>@</strong>
            <%=Model.EventDate.ToShortTimeString() %>
        </p>
        <p>
            <strong>Where:</strong> 
            <%=Html.Encode(Model.Address) %>,
            <%=Html.Encode(Model.Country) %>
        </p>
         <p>
            <strong>Description:</strong> 
            <%=Html.Encode(Model.Description) %>
        </p>       
        <p>
            <strong>Organizer:</strong> 
            <%=Html.Encode(Model.HostedBy) %>
            (<%=Html.Encode(Model.ContactPhone) %>)
        </p>
    
        <%Html.RenderPartial("RSVPStatus"); %>
        <%Html.RenderPartial("EditAndDeleteLinks"); %>
 
    </div>
    
    <div id="mapDiv">
        <%Html.RenderPartial("map"); %>    
    </div>   
         
</asp:Content>

現在當使用者瀏覽至 /Dinners/Details/[id] URL 時,他們會看到 Dinner 的詳細資料、地圖上的 Dinner 位置 (並包含一個圖釘圖示,當滑鼠懸停時會顯示 Dinner 的標題和地址),並有一個 AJAX 連結到 RSVP:

Nerd Dinners 網頁的螢幕擷取畫面。顯示地圖。

在我們的資料庫和存放庫中實作位置搜尋

為了完成 AJAX 實作,讓我們將 [地圖] 新增至應用程式的首頁,讓使用者以圖形方式搜尋附近的 Dinners。

Nerd Dinners 首頁的螢幕擷取畫面。顯示地圖。

我們一開始會在資料庫和資料存放庫層內實作支援,以有效率地執行以位置為基礎的 Dinners 半徑搜尋。 我們可以使用 SQL 2008 的新地理空間功能來實作這項功能,或者,也可以使用 Gary Dryden 在本文中討論的 SQL 函式方法:http://www.codeproject.com/KB/cs/distancebetweenlocations.aspx

為了實作這項技術,我們會在 Visual Studio 中開啟 [伺服器總管],選取 NerdDinner 資料庫,然後在其下方的 [函式] 子節點上按一下滑鼠右鍵,然後選擇建立新的 [純量值函式]:

Visual Studio 中 [伺服器總管] 的螢幕擷取畫面。選取了 Nerd Dinner 資料庫,並選取函式子節點。[純量值函式] 會醒目提示。

然後,我們會貼上下列 DistanceBetween 函式:

CREATE FUNCTION [dbo].[DistanceBetween](@Lat1 as real,
                @Long1 as real, @Lat2 as real, @Long2 as real)
RETURNS real
AS
BEGIN

DECLARE @dLat1InRad as float(53);
SET @dLat1InRad = @Lat1 * (PI()/180.0);
DECLARE @dLong1InRad as float(53);
SET @dLong1InRad = @Long1 * (PI()/180.0);
DECLARE @dLat2InRad as float(53);
SET @dLat2InRad = @Lat2 * (PI()/180.0);
DECLARE @dLong2InRad as float(53);
SET @dLong2InRad = @Long2 * (PI()/180.0);

DECLARE @dLongitude as float(53);
SET @dLongitude = @dLong2InRad - @dLong1InRad;
DECLARE @dLatitude as float(53);
SET @dLatitude = @dLat2InRad - @dLat1InRad;
/* Intermediate result a. */
DECLARE @a as float(53);
SET @a = SQUARE (SIN (@dLatitude / 2.0)) + COS (@dLat1InRad)
                 * COS (@dLat2InRad)
                 * SQUARE(SIN (@dLongitude / 2.0));
/* Intermediate result c (great circle distance in Radians). */
DECLARE @c as real;
SET @c = 2.0 * ATN2 (SQRT (@a), SQRT (1.0 - @a));
DECLARE @kEarthRadius as real;
/* SET kEarthRadius = 3956.0 miles */
SET @kEarthRadius = 6376.5;        /* kms */

DECLARE @dDistance as real;
SET @dDistance = @kEarthRadius * @c;
return (@dDistance);
END

接著,我們將在 SQL Server 中建立名為 “NearestDinners” 的新資料表值函式:

S Q L 伺服器的螢幕擷取畫面。資料表值函式會醒目提示。

這個 “NearestDinners” 資料表函式會使用 DistanceBetween 協助程式函式,傳回我們提供的緯度和經度 100 英里範圍內的所有 Dinners:

CREATE FUNCTION [dbo].[NearestDinners]
      (
      @lat real,
      @long real
      )
RETURNS  TABLE
AS
      RETURN
      SELECT Dinners.DinnerID
      FROM   Dinners 
      WHERE  dbo.DistanceBetween(@lat, @long, Latitude, Longitude) <100

若要呼叫此函式,我們會先按兩下 \Models 目錄中的 NerdDinner.dbml 檔案,以開啟 LINQ to SQL 設計工具:

[模型] 目錄中 Nerd Dinner dot d b m l 檔案的螢幕擷取畫面。

接著,我們會將 NearestDinners 和 DistanceBetween 函式拖曳到 LINQ to SQL 設計工具上,這會導致它們被新增為 LINQ to SQL NerdDinnerDataContext 類別的方法:

最接近 Dinners 與函式之間距離的螢幕擷取畫面。

然後,我們可以在 DinnerRepository 類別上公開 “FindByLocation” 查詢方法,該類別會使用 NearestDinner 函式傳回位於指定位置 100 英里內即將推出的 Dinners:

public IQueryable<Dinner> FindByLocation(float latitude, float longitude) {

   var dinners = from dinner in FindUpcomingDinners()
                 join i in db.NearestDinners(latitude, longitude)
                 on dinner.DinnerID equals i.DinnerID
                 select dinner;

   return dinners;
}

實作以 JSON 為基礎的 AJAX 搜尋動作方法

我們現在將實作控制器動作方法,利用新的 FindByLocation() 存放庫方法傳回可用來填入地圖的 Dinner 資料清單。 我們將讓此動作方法以 JSON (JavaScript 物件標記法) 格式傳回 Dinner 資料,以便可以在客戶端上使用 JavaScript 輕鬆操作它。

為了實作此動作,我們將以滑鼠右鍵按一下 \Controllers 目錄並選擇 [新增 -> 控制器] 功能表命令,以建立新的 “SearchController” 類別。 接著,我們將在新的 SearchController 類別內實作 “SearchByLocation” 動作方法,如下所示:

public class JsonDinner {
    public int      DinnerID    { get; set; }
    public string   Title       { get; set; }
    public double   Latitude    { get; set; }
    public double   Longitude   { get; set; }
    public string   Description { get; set; }
    public int      RSVPCount   { get; set; }
}

public class SearchController : Controller {

    DinnerRepository dinnerRepository = new DinnerRepository();

    //
    // AJAX: /Search/SearchByLocation

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult SearchByLocation(float longitude, float latitude) {

        var dinners = dinnerRepository.FindByLocation(latitude,longitude);

        var jsonDinners = from dinner in dinners
                          select new JsonDinner {
                              DinnerID = dinner.DinnerID,
                              Latitude = dinner.Latitude,
                              Longitude = dinner.Longitude,
                              Title = dinner.Title,
                              Description = dinner.Description,
                              RSVPCount = dinner.RSVPs.Count
                          };

        return Json(jsonDinners.ToList());
    }
}

SearchController 的 SearchByLocation 動作方法會在內部呼叫 DinnerRepository 上的 FindByLocation 方法,以取得附近 Dinners 的清單。 不過,它不是直接將 Dinner 物件傳回給用戶端,而是傳回 JsonDinner 物件。 JsonDinner 類別會公開 Dinner 屬性的子集 (例如:出於安全原因,它不會透露已回覆 Dinner 的人員的姓名)。 它也包含一個不存在於 Dinner 上的 RSVPCount 屬性,其會藉由計算與特定 Dinner 相關聯的 RSVP 物件數目來動態計算。

接著,我們會在 Controller 基底類別上使用 Json() 協助程式方法,以使用 JSON 型 Wire 格式傳回 Dinners 的序列。 JSON 是表示簡單資料結構的標準文字格式。 以下是從我們的動作方法傳回時,兩個 JsonDinner 物件的 JSON 格式清單的外觀範例:

[{"DinnerID":53,"Title":"Dinner with the Family","Latitude":47.64312,"Longitude":-122.130609,"Description":"Fun dinner","RSVPCount":2}, 
{"DinnerID":54,"Title":"Another Dinner","Latitude":47.632546,"Longitude":-122.21201,"Description":"Dinner with Friends","RSVPCount":3}]

使用 jQuery 呼叫以 JSON 為基礎的 AJAX 方法

我們現在已準備好更新 NerdDinner 應用程式的首頁,以使用 SearchController 的 SearchByLocation 動作方法。 若要這樣做,我們將開啟 /Views/Home/Index.aspx 檢視範本,並將其更新為具有文字方塊、搜尋按鈕、我們的地圖,以及名為 dinnerList 的 <div> 項目:

<h2>Find a Dinner</h2>

<div id="mapDivLeft">

    <div id="searchBox">
        Enter your location: <%=Html.TextBox("Location") %>
        <input id="search" type="submit" value="Search"/>
    </div>

    <div id="theMap">
    </div>

</div>

<div id="mapDivRight">
    <div id="dinnerList"></div>
</div>

然後,我們可以將兩個 JavaScript 函式新增至頁面:

<script type="text/javascript">

    $(document).ready(function() {
        LoadMap();
    });

    $("#search").click(function(evt) {
        var where = jQuery.trim($("#Location").val());
        if (where.length < 1) 
            return;

        FindDinnersGivenLocation(where);
    });

</script>

第一個 JavaScript 函式會在該頁面第一次載入時載入地圖。 第二個 JavaScript 函式會在搜尋按鈕上連接 JavaScript 按一下事件處理常式。 按下按鈕時,它會呼叫我們將新增至 Map.js 檔案的 FindDinnersGivenLocation() JavaScript 函式:

function FindDinnersGivenLocation(where) {
    map.Find("", where, null, null, null, null, null, false,
       null, null, callbackUpdateMapDinners);
}

此 FindDinnersGivenLocation() 函式會呼叫地圖。在 [Virtual Earth 控制] 上尋找 () 將它置中於輸入的位置。 當虛擬地球地圖服務傳回時,map.Find() 方法會叫用我們作為最後一個引數傳遞的 callbackUpdateMapDinners 回撥方法。

callbackUpdateMapDinners() 方法是實際工作完成的位置。 它會使用 jQuery 的 $.post() 協助程式方法,對 SearchController 的 SearchByLocation() 動作方法執行 AJAX 呼叫 ,以傳遞最新置中地圖的緯度和經度。 它會定義內嵌函式,當 $.post() 協助程式方法完成時會呼叫,而 SearchByLocation() 動作方法傳回的 JSON 格式 Dinner 結果將會使用名為 “dinners” 的變數來傳遞。 然後,它會在每個傳回的 Dinner 上執行 foreach,並使用 Dinner 的緯度和經度和其他屬性在地圖上新增釘選。 它也會將 Dinner 項目新增至地圖右側的 Dinners HTML 清單。 然後,它會連接圖釘和 HTML 清單的滑鼠暫留事件,讓使用者將滑鼠停留在他們上方時,會顯示 Dinner 的詳細資料:

function callbackUpdateMapDinners(layer, resultsArray, places, hasMore, VEErrorMessage) {

    $("#dinnerList").empty();
    clearMap();
    var center = map.GetCenter();

    $.post("/Search/SearchByLocation", { latitude: center.Latitude, 
                                         longitude: center.Longitude },     
    function(dinners) {
        $.each(dinners, function(i, dinner) {

            var LL = new VELatLong(dinner.Latitude, 
                                   dinner.Longitude, 0, null);

            var RsvpMessage = "";

            if (dinner.RSVPCount == 1)
                RsvpMessage = "" + dinner.RSVPCount + "RSVP";
            else
                RsvpMessage = "" + dinner.RSVPCount + "RSVPs";

            // Add Pin to Map
            LoadPin(LL, '<a href="/Dinners/Details/' + dinner.DinnerID + '">'
                        + dinner.Title + '</a>',
                        "<p>" + dinner.Description + "</p>" + RsvpMessage);

            //Add a dinner to the <ul> dinnerList on the right
            $('#dinnerList').append($('<li/>')
                            .attr("class", "dinnerItem")
                            .append($('<a/>').attr("href",
                                      "/Dinners/Details/" + dinner.DinnerID)
                            .html(dinner.Title))
                            .append(" ("+RsvpMessage+")"));
        });

        // Adjust zoom to display all the pins we just added.
        map.SetMapView(points);

        // Display the event's pin-bubble on hover.
        $(".dinnerItem").each(function(i, dinner) {
            $(dinner).hover(
                function() { map.ShowInfoBox(shapes[i]); },
                function() { map.HideInfoBox(shapes[i]); }
            );
        });
    }, "json");

現在,當我們執行應用程式並造訪首頁時,我們將會看到地圖。 當我們輸入城市名稱時,地圖將會顯示附近即將推出的 Dinners:

顯示地圖的 Nerd Dinner 首頁螢幕擷取畫面。

將滑鼠暫留在 Dinner 上會顯示有關它的詳細資料。

按一下泡泡中的 Dinner 標題或右側 HTML 清單中的標題都會將我們導覽到該 Dinner,然後我們可以選擇性地回覆:

Nerd Dinner 詳細資料頁面的螢幕擷取畫面,其中顯示導覽至 Dinner 的地圖。

後續步驟

我們現在已實作 NerdDinner 應用程式的所有應用程式功能。 現在讓我們看看如何啟用它的自動化單元測試。