.NET Magazine國際中文電子雜誌
作 者:許薰尹
審 稿:張智凱
文章編號: N180219201
出刊日期: 2018/2/7
目前遇到一個需求,需要在ASP.NET Web Forms網站(Web Site)之中設計一個Web API,將採用表單式驗證(FormsAuthentication)來驗證用戶端程式,然後在另一個Web Form網站使用「HttpClient」類別來呼叫這個Web API。
因為預設Visual Studio 沒有網站類型的Web API範本專案,因此需要將Web API的路由與相關套件手動加入,本文將介紹如何在ASP.NET Web Forms網站(Web Site)安裝Web API所需的套件,並手動設定Web API路由,最後達到使用「HttpClient」類別進行表單驗證,並叫用Web API取回執行結果。本文所提及的技術適用於ASP.NET Web Form與.NET Framework 4.6以上版本。
設計Web API網站
首先讓我們從設計Web API網站開始。從 Visual Studio 2017的主選單點選「File」-「New」-「Web Site」項目,開啟「New Web Site」對話窗,請參考下圖所示:

圖 1:開啟「New Web Site」對話窗。
若Visual Studio 2017更新到15.5.1版或以上版本,則「New」-「Web Site」選單的位置搬家了,需要從 Visual Studio 2017的主選單點選「File」-「New」-「Project」,接著會開啟「New Project」對話窗。選取「Visual C#」-「Web」-「Web Site」項目,請參考下圖所示:

圖 2:開啟「New Project」對話窗。
在「New Web Site」對話窗確認視窗上方.NET Framework的目標版本為「.NET Framework 4.6.1以上」,選取使用的程式語言 (Language)為「Visual C#」,然後選取「ASP.NET Empty Web Site」,將「Web Location」設為「File System」,選取檔案存放資料夾設定名稱為「WebAPIWebSite」,請參考下圖所示:

圖 3:建立「ASP.NET Empty Web Site」。
安裝「Microsoft.AspNet.WebApi.WebHost」套件
使用Nuget套件管理員下載「Microsoft.AspNet.WebApi.WebHost」套件。自Visual Studio 2017開發工具選單選取「Website」-「Manage NuGet Packages」項目,開啟「「NuGet Packages Manage」。在「Browse」分頁上方文字方塊輸入「webapi」當作篩選條件,找出套件之後,只要點選名稱旁邊的「Install」按鈕安裝,請參考下圖所示:

圖 4:安裝「Microsoft.AspNet.WebApi.WebHost」套件。
安裝「Microsoft.AspNet.WebApi.WebHost」套件會順便幫你安裝「Microsoft.AspNet.WebApi.Client」、「Microsoft.AspNet.WebApi.Core」與「Newtonsoft.Json」套件,請參考下圖所示:

圖 5:安裝WebApi 相關套件。
從「Solution Explorer」視窗 -「WebAPIWebSite」資料夾上方,按滑鼠右鍵,從快捷選單選擇「Add」- 「Add New Item」項目,請參考下圖所示:

圖 6:「Add New Item」。
從「Add New Item」對話盒中,選取「Visual C#」-「Class」項目,然後在下方將「Name」設定為「WebApiConfig.cs」最後按下「Add」按鈕,請參考下圖所示:

圖 7:加入「WebApiConfig.cs」類別。
此時Visual Studio開發工具會提示是否將此類別放在「App_Code」資料夾,在此按下「Yes」按鈕,請參考下圖所示:

圖 8:建立「App_Code」資料夾。
在「WebApiConfig.cs」檔案中加入程式碼,設定Web API路由,參考以下範例程式碼:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
public static class WebApiConfig {
public static void Register( HttpConfiguration config ) {
config.MapHttpAttributeRoutes( );
config.Routes.MapHttpRoute(
name: "DefaultApi" ,
routeTemplate: "api/{controller}/{id}" ,
defaults: new { id = RouteParameter.Optional }
);
}
}
從「Solution Explorer」視窗 「WebAPIWebSite」網站資料夾上方按滑鼠右鍵,從快捷選單選擇「Add」- 「Add New Item」選項,選取「Visual C#」分類中的「Global Application Class」項目,檔案名稱設定為「Global.asax」,然後按下「Add」按鈕,請參考下圖所示:

圖 9:加入「Global Application Class」檔案。
在「Global.asax」檔案最上方引用「System.Web.Http」命名空間,並在「Application_Start」事件處理常式中加入程式碼,註冊Web API路由:
<%@ Application Language="C#" %>
<%@ Import Namespace="System.Web.Http" %>
<script RunAt="server">
void Application_Start( object sender , EventArgs e ) {
GlobalConfiguration.Configure( WebApiConfig.Register );
}
void Application_End( object sender , EventArgs e ) {
}
void Application_Error( object sender , EventArgs e ) {
}
void Session_Start( object sender , EventArgs e ) {
}
void Session_End( object sender , EventArgs e ) {
}
</script>
在「App_Code」資料夾加入Web API。從「Solution Explorer」視窗 -「WebAPIWebSite」-「App_Code」資料夾上方按滑鼠右鍵,從快捷選單選擇「Add」- 「Add New Item」項目,開啟「Add New Item」對話盒,選取「Visual C#」分類下的「Web API Controller Class (v2.1)」,然後按下「Add」按鈕,請參考下圖所示:

圖 10:加入「Web API Controller Class (v2.1)」。
在「MyAPIController」類別階層宣告一個「employees」集合,並新增多筆員工資料,然後在「Get」方法中回傳「employees」物件所成的集合,參考以下範例程式碼:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
public class MyAPIController : ApiController {
static List<string> employees =
new List<string> { "Mary" , "Candy" , "Lilly" , "Betty" , "Jessica" };
public IEnumerable<string> Get( ) {
return employees;
}
}
選取Visual Studio 開發工具「Build」-「Build Solution」編譯目前的專案,確認程式碼能正確編譯。按CTRL+F5執行網站首頁(請注意:埠號可能會依據實際上的操作而有所不同,請修改為實際的埠號),然後在瀏覽器輸入以下URL:
http://localhost:65235/api/myapi
執行結果參考如下,以下是使用Chrome瀏覽器呼叫時,回傳的XML資料:

圖 11:呼叫Web API測試。
設計「Generic Handler」進行驗證
我們需要在伺服端提供表單驗證,這次我選擇使用「Generic Handler 」來進行驗證。從「Solution Explorer」視窗 -「WAPIWebsite」資料夾上方,按滑鼠右鍵,從快捷選單選擇「Add」- 「Add New Item」項目,從「Add New Item」對話盒中,選取「Visual C#」分類下的「Generic Handler」項目,然後在下方將「Name」設定為「MyHandler」最後按下「Add」按鈕,請參考下圖所示:

圖 12:加入「Generic Handler」。
參考以下範例程式碼,修改「MyHandler」類別的「ProcessRequest」方法,先取得用戶端傳入的帳號(username)與密碼(password),本例假設帳號為「AAA」密碼為「111」時代表驗證成功,驗證成功後利用「FormsAuthentication」類別的「SetAuthCookie」方法設定驗證Cookie,然後設定HTTP狀態碼為「200」;若帳號、密碼不相符則送出狀態碼「401」代表驗證不通過。
<%@ WebHandler Language="C#" Class="MyHandler" %>
using System;
using System.Web;
using System.Web.Security;
public class MyHandler : IHttpHandler {
public void ProcessRequest( HttpContext context ) {
var username = context.Request.Params ["username"];
var password = context.Request.Params ["password"];
if ( username == "AAA" && password == "111" ) {
FormsAuthentication.SetAuthCookie( username , true );
context.Response.StatusCode = 200;
context.Response.StatusDescription = "OK";
} else {
context.Response.StatusCode = 401;
context.Response.StatusDescription = "Unauthorized";
}
}
public bool IsReusable {
get {
return false;
}
}
}
使用Web.config組態檔啟用表單驗證
最後透過Web.config組態檔啟用表單驗證,在Web.config檔案中加入< authentication>與< authorization >標籤。< authentication>的「mode」設定為「Forms」表示使用表單驗證,「loginUrl」則用來設定驗證的程式為「MyHandler.ashx」。< authorization >標籤中使用<deny>項目設定拒絕匿名者存取(?號代表匿名者),參考以下範例程式碼:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.web>
<authentication mode="Forms">
<forms loginUrl="~/MyHandler.ashx" />
</authentication>
<authorization>
<deny users="?"/>
</authorization>
<compilation debug="true" targetFramework="4.6.1">
<assemblies>
<add assembly="System.Net.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A" />
</assemblies>
</compilation>
<httpRuntime targetFramework="4.6.1" />
</system.web>
<system.codedom>
<compilers>
<compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.7.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:default /nowarn:1659;1699;1701" />
<compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.7.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:default /nowarn:41008 /define:_MYTYPE=\"Web\" /optionInfer+" />
</compilers>
</system.codedom>
<system.webServer>
<handlers>
<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
<remove name="OPTIONSVerbHandler" />
<remove name="TRACEVerbHandler" />
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>
</system.webServer>
</configuration>
選取Visual Studio 開發工具「Build」-「Build Solution」編譯目前的專案,確認程式碼能正確編譯。按CTRL+F5執行網站首頁(請注意:埠號可能會依據實際上的操作而有所不同,請修改為實際的埠號),然後在瀏覽器輸入以下URL:
http://localhost:65235/api/myapi
這次的執行結果參考如下,以下是使用Chrome瀏覽器呼叫時回傳的結果,因為沒有提供驗證所需的帳號與密碼來驗證,你將得到一個「401」號錯誤:

圖 13:驗證發生錯誤。
設計ASP.NET Web Form網站
接下來我們要設計ASP.NET Web Form網站當做Web API的用戶端程式。先在「Visual Studio 2017」目前方案中,加入一個新的網站。從「Solution Explorer」視窗點選「Solution ...」項目,按滑鼠右鍵,從快捷選單選擇「Add」- 「New Web Site」項目,開啟「Add New Web Site」對話窗,請參考下圖所示:

圖 14:「Add New Web Site」。
確認「Add New Web Site」視窗上方.NET Framework的目標版本為「.NET Framework 4.6.1以上」,選取使用的程式語言 (Language)-「 Visual C#」,然後選取「ASP.NET Empty Web Site」,將「Web Location」設為「File System」,並設定資料夾名稱為「WebFormWebSite」,請參考下圖所示:

圖 15:加入Web Form用戶端。
安裝「Microsoft.AspNet.WebApi.Client」套件
在ASP.NET Web Form網站,使用Nuget套件管理員下載「Microsoft.AspNet.WebApi.Client」套件。自Visual Studio開發工具選單選取「Website」-「Manage NuGet Packages」項目,開啟「「NuGet Packages Manage」。在「Browse」分頁上方文字方塊輸入「webapi」當作篩選條件,找出套件之後,只要點選名稱旁邊的「Install」按鈕安裝,請參考下圖所示:

圖 16:安裝「Microsoft.AspNet.WebApi.Client」套件。
安裝「Microsoft.AspNet.WebApi.WebHost」套件會順便幫你安裝「Newtonsoft.Json」套件,請參考下圖所示:

圖 17:安裝「Newtonsoft.Json」套件。
選取「I Accept」接受授權,請參考下圖所示:

圖 18:接受授權。
加入「System.Net.Http」組件參考
ASP.NET Web Form網站加入「System.Net.Http」組件參考。從「Soluiton Explorer」視窗,「WebFormWebSite」網站名稱上按滑鼠右鍵,從快捷選單選擇「Add」-「Reference」項目,開啟「Reference Manager」視窗,選取「Assemblies」分類,勾選「System.Net.Http」組件,然後按下「OK」按鈕,請參考下圖所示:

圖 19:加入「System.Net.Http」組件參考。
在「Solution Explorer」視窗,點選「WebFormWebSite」網站,按滑鼠右鍵,從快捷選單選擇「Add」-「Add New Item...」。從「Add New Item」對話盒中,選取「Visual C#」-「Web Form」項目,然後在下方將「Name」設定為「Default.aspx」,勾選「Place code in separate file」核取方塊,最後按下「Add」按鈕建立網頁,請參考下圖所示:

圖 20:加入「Default.aspx」。
修改Default.aspx網頁,從「Toolbox」拖曳一個「GridView」與「Label」控制項到表單<form>標籤之中,然後將「Label」控制項的「ID」設為「msg」;將「Text」屬性設為空字串,目前「Default.aspx」檔案程式碼看起來如下:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:GridView ID="GridView1" runat="server">
</asp:GridView>
</div>
<asp:Label ID="msg" runat="server" Text=""></asp:Label>
</form>
</body>
</html>
修改「Default.aspx.cs」檔案,在Page_Load事件處理常式中加入以下程式碼,範例中利用「HttpClient」類別的「SendAsync」方法,送出兩個請求,第一個請求將帳號與密碼送到Web API進行驗證,以取得驗證Cookie,第二個請求則是叫用Web API取回員工清單,參考以下範例程式碼:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
public partial class _Default : System.Web.UI.Page {
protected void Page_Load( object sender , EventArgs e ) {
var username = "AAA";
var password = "111";
var client = new HttpClient( );
var authRequest = new HttpRequestMessage( ) {
RequestUri = new Uri( "http://localhost:65235/MyHandler.ashx" ) ,
Method = HttpMethod.Post ,
Content = new FormUrlEncodedContent(
new List<KeyValuePair<string , string>> {
new KeyValuePair<string, string>("Username", username),
new KeyValuePair<string, string>("Password", password)
} )
};
var authResponse = client.SendAsync( authRequest ).Result;
if ( authResponse.IsSuccessStatusCode ) {
var authCookies = FormsAuthentication.GetAuthCookie( username , false );
var request = new HttpRequestMessage( ) {
RequestUri = new Uri( "http://localhost:65235/api/myapi" )
};
Response.AppendCookie( authCookies );
var response = client.SendAsync( request ).Result;
var r = response.Content.ReadAsAsync<IEnumerable<string>>( ).Result;
GridView1.DataSource = r;
GridView1.DataBind( );
} else {
msg.Text = "發生錯誤! ";
msg.Text += "<br/> ";
msg.Text += "錯誤狀態碼 : " + authResponse.StatusCode;
}
}
}
測試
使用Visual Studio開發工具進行測試,選取「Solution Explorer」視窗中的「Solution…」項目,按滑鼠右鍵,從快捷選單選擇「Properties」項目,請參考下圖所示:

圖 21:設定專案屬性。
在「Property Pages」對話盒中,選取「Common Properties」-「Startup Project」分頁。設定「Multiple startup projects」,將「WebAPIWebSite」與「WEbFormWebSite」的「Action」設為「Start」,記得順序需要先「WebAPIWebSite」網站再「WEbFormWebSite」網站,若順序有錯可以使用右方的箭頭按鈕進行調整,請參考下圖所示:

圖 22:設定起始專案。
選取Visual Studio 開發工具「Build」-「Build Solution」編譯目前的專案,確認程式碼能正確編譯。按CTRL+F5執行,此時應該可以看到「WebFormWebSite」網站的ASP.NET Web Form(Default.aspx)可以叫用到Web API,並取回員工清單顯示在「GridView」控制項上,請參考下圖所示:

圖 23:呼叫Web API結果。
若帳號、密碼不相符將會看到以下錯誤訊息:

圖 24:呼叫Web API失敗。
最後再提醒,若選擇使用此種方式做驗證,建議再搭配SSL 才能確保帳號與密碼的安全。