2017年10月25日 星期三

C# HttpClient WebAPI : 11. 使用 POST 要求與使用 Cookie 進行傳送與接收 呼叫 Web API

通常來說,Http 通訊協議是無狀態協議 (Stateless Protocol),也就是說,每次呼叫 Web API 的時候,若有些持續性要保留下來的狀態,我們就需要自己將這些狀態保留在用戶端中;而當下次要進行新的 Web API 呼叫的時候,就需要把這些狀態內容,再度傳遞後端的 Web API 伺服器上,這樣,後端 Web API 的控制器,就會知道如何繼續進行該狀態下的操作。
依據客戶要求或者當初制定系統的規格,有些時候不想透過 Header 來與後端伺服器互相傳遞資訊,而是想要透過 Cookie (使用哪種方式比較好,這不在本專題文章的討論中,有需要,請參閱相關課程)。
在這裡,我們將會使用 POST 要求與 JSON 編碼內容,搭配 Cookie 來呼叫後端兩個 Web API 服務。
  • LoginPostAsync
    這個方法,會傳送使用者的帳號與密碼到後端進行驗證,若驗證成功,將會透過 Cookie 回報給用戶端的程式。
  • LoginCheckGetAsync
    若身分驗證成功,並且取得了 Cookie,可以將 Cookie 傳遞到這個方法內,此時,就可以取得後端 Web API 的資料。

了解更多關於 [HttpClient Class] 的使用方式
了解更多關於 [使用 async 和 await 進行非同步程式設計] 的使用方式


用 POST 要求與使用 Cookie 進行傳送與接收 呼叫 Web API

在 LoginPostAsync 方法中,就是一般的 POST 要求呼叫 Web API 的程式碼作法,在這裡會將 LoginInformation 類別的物件進行 JSON 序列化之後,傳送到後端相對應的 Web API 控制器之動作(Action)內,進行身分驗證。
當 Cookie 成功回傳到用戶端之後,我們需要透過 HttpClientHandler 所生成的物件來進行取回 Cookie 內容。
我們使用 IEnumerable<Cookie> responseCookies = handler.CookieContainer.GetCookies(BaseAddress).Cast<Cookie>();敘述,取得 Http 回應的所有 Cookie 到 responseCookies 列舉集合物件內。
接著,我們搭配使用 LINQ 取出 Cookie 名稱為 .AspNetCore.Cookies 內容,所以,我們使用這樣的敘述來做到這樣需求:var fooresponseCookie = responseCookies.FirstOrDefault(x => x.Name == CookieName);
private static async Task<APIResult> LoginPostAsync(LoginInformation loginInformation)
{
    APIResult fooAPIResult;
    using (HttpClientHandler handler = new HttpClientHandler())
    {
        using (HttpClient client = new HttpClient(handler))
        {
            try
            {
                #region 呼叫遠端 Web API
                string FooUrl = RemoteLoginUrl;
                HttpResponseMessage response = null;

                #region  設定相關網址內容
                var fooFullUrl = $"{FooUrl}";

                // Accept 用於宣告客戶端要求服務端回應的文件型態 (底下兩種方法皆可任選其一來使用)
                //client.DefaultRequestHeaders.Accept.TryParseAdd("application/json");
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

                // Content-Type 用於宣告遞送給對方的文件型態
                //client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json");

                var fooJSON = JsonConvert.SerializeObject(loginInformation);
                using (var fooContent = new StringContent(fooJSON, Encoding.UTF8, "application/json"))
                {
                    response = await client.PostAsync(fooFullUrl, fooContent);
                }
                #endregion
                #endregion

                #region 處理呼叫完成 Web API 之後的回報結果
                if (response != null)
                {
                    if (response.IsSuccessStatusCode == true)
                    {
                        #region 取得 Cookie
                        var ResponseCookie = "";
                        IEnumerable<Cookie> responseCookies = handler.CookieContainer.GetCookies(BaseAddress).Cast<Cookie>();
                        var fooresponseCookie = responseCookies.FirstOrDefault(x => x.Name == CookieName);
                        if (fooresponseCookie != null)
                        {
                            ResponseCookie = fooresponseCookie.Value.ToString();
                        }
                        else
                        {
                            ResponseCookie = "";
                        }
                        #endregion

                        // 取得呼叫完成 API 後的回報內容
                        String strResult = await response.Content.ReadAsStringAsync();

                        fooAPIResult = JsonConvert.DeserializeObject<APIResult>(strResult, new JsonSerializerSettings { MetadataPropertyHandling = MetadataPropertyHandling.Ignore });
                        fooAPIResult.Payload = ResponseCookie;
                    }
                    else
                    {
                        fooAPIResult = new APIResult
                        {
                            Success = false,
                            Message = string.Format("Error Code:{0}, Error Message:{1}", response.StatusCode, response.RequestMessage),
                            Payload = null,
                        };
                    }
                }
                else
                {
                    fooAPIResult = new APIResult
                    {
                        Success = false,
                        Message = "應用程式呼叫 API 發生異常",
                        Payload = null,
                    };
                }
                #endregion
            }
            catch (Exception ex)
            {
                fooAPIResult = new APIResult
                {
                    Success = false,
                    Message = ex.Message,
                    Payload = ex,
                };
            }
        }
    }

    return fooAPIResult;
}
在這個 LoginCheckGetAsync 方法中,需要將之前取得的 Cookie 傳遞到這個方法內;因為這個方法,需要將傳遞進來的 Cookie 內容,加入到 Http 封包內容中,所以,我們同樣的需要 HttpClientHandler 類別物件。
我們使用了敘述 handler.CookieContainer.Add(baseAddress, new Cookie(CookieName, responseCookie)) 來完成這樣的需求,其中 CookieName 字串變數為 .AspNetCore.Cookies,而 responseCookie 則是傳入該方法內的字串物件值。
private static async Task<APIResult> LoginCheckGetAsync(string responseCookie)
{
    APIResult fooAPIResult;
    using (HttpClientHandler handler = new HttpClientHandler())
    {
        using (HttpClient client = new HttpClient(handler))
        {
            try
            {
                #region 呼叫遠端 Web API
                string FooUrl = RemoteLoginCheckUrl;
                HttpResponseMessage response = null;

                #region  設定相關網址內容
                var fooFullUrl = $"{FooUrl}";

                // Accept 用於宣告客戶端要求服務端回應的文件型態 (底下兩種方法皆可任選其一來使用)
                //client.DefaultRequestHeaders.Accept.TryParseAdd("application/json");
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

                // Content-Type 用於宣告遞送給對方的文件型態
                //client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json");

                if (string.IsNullOrEmpty(responseCookie) == false)
                {
                    var baseAddress = BaseAddress;
                    var cookieContainer = handler.CookieContainer;
                    cookieContainer.Add(baseAddress, new Cookie(CookieName, responseCookie));
                }

                response = await client.GetAsync(fooFullUrl);
                #endregion
                #endregion

                #region 處理呼叫完成 Web API 之後的回報結果
                if (response != null)
                {
                    // Throws an exception if the System.Net.Http.HttpResponseMessage.IsSuccessStatusCode property 
                    // for the HTTP response is false
                    // 請試著將底下程式碼解除註解,看看會有何結果
                    //response.EnsureSuccessStatusCode();

                    if (response.IsSuccessStatusCode == true)
                    {
                        // 取得呼叫完成 API 後的回報內容
                        String strResult = await response.Content.ReadAsStringAsync();
                        fooAPIResult = JsonConvert.DeserializeObject<APIResult>(strResult, new JsonSerializerSettings { MetadataPropertyHandling = MetadataPropertyHandling.Ignore });
                    }
                    else
                    {
                        fooAPIResult = new APIResult
                        {
                            Success = false,
                            Message = string.Format("Error Code:{0}, Error Message:{1}", response.StatusCode, response.RequestMessage),
                            Payload = null,
                        };
                    }
                }
                else
                {
                    fooAPIResult = new APIResult
                    {
                        Success = false,
                        Message = "應用程式呼叫 API 發生異常",
                        Payload = null,
                    };
                }
                #endregion
            }
            catch (Exception ex)
            {
                fooAPIResult = new APIResult
                {
                    Success = false,
                    Message = ex.Message,
                    Payload = ex,
                };
            }
        }
    }

    return fooAPIResult;
}

觸發的 Web API 動作

這個範例中,若要進行身分驗證,並且取得合法身分授權的 Cookie,將會需要指向 URL http://vulcanwebapi.azurewebsites.net/api/values/Login ,此時,將會觸發 Web API 伺服器上的 Values 控制器(Controller)的 public async Task<APIResult> Login([FromBody]LoginInformation loginInformation) 動作(Action),其該動作的原始碼如下所示。
這個 Web API 動作,將會回傳一個 APIData 的 JSON 資料。
[HttpPost("Login")]
public async Task<APIResult> Login([FromBody]LoginInformation loginInformation)
{
    APIResult foo;

    if (loginInformation.Account == "Vulcan" &&
        loginInformation.Password == "123")
    {
        var claims = new List<Claim>() {
        new Claim(ClaimTypes.Name, "Herry"),
        new Claim(ClaimTypes.Role, "Users")
    };
        var claimsIdentity = new ClaimsIdentity(claims, "myTest");
        var principal = new ClaimsPrincipal(claimsIdentity);
        try
        {
            await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
                principal,
                new AuthenticationProperties
                {
                    ExpiresUtc = DateTime.UtcNow.AddMinutes(20),
                    IsPersistent = true,
                    AllowRefresh = true
                });
            foo = new APIResult()
            {
                Success = true,
                Message = "這個帳號與密碼正確無誤",
                Payload = null
            };
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            foo = new APIResult()
            {
                Success = false,
                Message = "這個帳號與密碼不正確",
                Payload = null
            };
        }
    }
    else
    {
        foo = new APIResult()
        {
            Success = false,
            Message = "這個帳號與密碼不正確",
            Payload = null
        };
    }

    return foo;
}
一旦取得 Cookie之後,想要取得 Web API 伺服器須要有使用者通過身分驗證的,將會需要指向 URL http://vulcanwebapi.azurewebsites.net/api/values/LoginCheck ,此時,將會觸發 Web API 伺服器上的 Values 控制器(Controller)的 public APIResult LoginCheck() 動作(Action),其該動作的原始碼如下所示。若沒有傳送過來通過身分驗證的 Cookie 內容,您是無法呼叫這個方法的。
這個 Web API 動作,將會回傳一個 APIData 的 JSON 資料。
[Authorize]
[HttpGet("LoginCheck")]
public APIResult LoginCheck()
{
    APIResult foo = new APIResult()
    {
        Success = true,
        Message = "成功得所有資料集合",
        Payload = new List<APIData>()
        {
            new APIData()
            {
                Id =777,
                Name = "Vulcan01"
            },
            new APIData()
            {
                Id =234,
                Name ="Vulcan02"
            }
        }
    };
    return foo;
}

進行測試

在這裡,我們一共進行了四次 Web API 的呼叫:
  • 第1次
    這裡將會呼叫 LoginPostAsync 方法,並且傳送一個正確的帳號與密碼到遠端 Web API 伺服器上,這樣就可以取得通過身分驗證的 Cookie。回傳的 Cookie 的內容,可以透過 APIResult.Payload 屬性來得到。
  • 第2次
    這裡將會呼叫 LoginPostAsync 方法,並且傳送一個不正確的帳號與密碼到遠端 Web API 伺服器上,這樣就無法取得通過身分驗證,並且,也無法看到任何 Cookie。
  • 第3次
    這裡將會呼叫 LoginCheckGetAsync 方法,並且將剛剛取得的 Cookie 傳遞到該方法內,此時,將可以取得後端 Web API 回傳的資料。
  • 第4次
    這裡將會呼叫 LoginCheckGetAsync 方法,但是,並沒有傳送任何 Cookie 傳遞到該方法內,此時,將會看有例外異常訊息顯示出來。
static void Main(string[] args)
{
    string ResponseCookie = "";
    var fooLoginInformation = new LoginInformation()
    {
        Account = "Vulcan",
        Password = "123",
    };
    var foo = LoginPostAsync(fooLoginInformation).Result;
    ResponseCookie = foo.Payload.ToString();
    Console.WriteLine($"使用 Post 方法,取得 Cookie,呼叫 Web API 的結果");
    Console.WriteLine($"結果狀態 : {foo.Success}");
    Console.WriteLine($"結果訊息 : {foo.Message}");
    Console.WriteLine($"Payload : {foo.Payload}");
    Console.WriteLine($"");

    Console.WriteLine($"Press any key to Exist...{Environment.NewLine}");
    Console.ReadKey();

    fooLoginInformation = new LoginInformation()
    {
        Account = "Vuln",
        Password = "13",
        VerifyCode = "123"
    };
    foo = LoginPostAsync(fooLoginInformation).Result;
    Console.WriteLine($"使用 Post 方法,取得 Cookie,呼叫 Web API 的結果");
    Console.WriteLine($"結果狀態 : {foo.Success}");
    Console.WriteLine($"結果訊息 : {foo.Message}");
    Console.WriteLine($"Payload : {foo.Payload}");
    Console.WriteLine($"");

    Console.WriteLine($"Press any key to Exist...{Environment.NewLine}");
    Console.ReadKey();

    foo = LoginCheckGetAsync(ResponseCookie).Result;
    Console.WriteLine($"使用 Get 方法,傳送 Cookie,呼叫 Web API 的結果");
    Console.WriteLine($"結果狀態 : {foo.Success}");
    Console.WriteLine($"結果訊息 : {foo.Message}");
    Console.WriteLine($"Payload : {foo.Payload}");
    Console.WriteLine($"");

    Console.WriteLine($"Press any key to Exist...{Environment.NewLine}");
    Console.ReadKey();

    foo = LoginCheckGetAsync(null).Result;
    Console.WriteLine($"使用 Get 方法,沒有傳送 Cookie,呼叫 Web API 的結果");
    Console.WriteLine($"結果狀態 : {foo.Success}");
    Console.WriteLine($"結果訊息 : {foo.Message}");
    Console.WriteLine($"Payload : {foo.Payload}");
    Console.WriteLine($"");

    Console.WriteLine($"Press any key to Exist...{Environment.NewLine}");
    Console.ReadKey();
}

執行結果

這個測試將會輸出底下內容
使用 Post 方法,取得 Cookie,呼叫 Web API 的結果
結果狀態 : True
結果訊息 : 這個帳號與密碼正確無誤
Payload : CfDJ8IH9Z5ddETZGrIcaxOsO-eXfEHmgF5yeQ8ppzfPM2rFnj4wA23jxcLtWZ7YVt6UfmodE_AZPiYkYnKSG47GckqU0wGW7PtvA05HQrbrtvjILdxih3gk3xReBVtFtul-kgnjLLmr-lmAhIhsDhlJrgqzjbp6ebfzBwqorkdx88CWlIyqgUdEiWGGTZzc15XHSHBJQNR1xW792STk5O6LGEraFAMMhqpVdrQNxg7dEpVBDjFUFmd5mXeoUURHhiq8_X-P0kT4e05zuWIN3SOGv6fuVkTu479w1IdmmiMq3k8RwSng0bHPxKjdLO_-L3XDa_HNuNXp-euzOOvMS3UHbldrA3md_lW6KvgXqQT23PLPsvBkrIcKoNNvt0G1_v5TwSWzzG88H3qEO1DdjH_2QObgjp_efuy7Oxk-KELhLJtn-ZcNEAKxOjiVARr7Euk1igA

Press any key to Exist...

使用 Post 方法,取得 Cookie,呼叫 Web API 的結果
結果狀態 : False
結果訊息 : 這個帳號與密碼不正確
Payload :

Press any key to Exist...

使用 Get 方法,傳送 Cookie,呼叫 Web API 的結果
結果狀態 : True
結果訊息 : 成功得所有資料集合
Payload : [
  {
    "id": 777,
    "name": "Vulcan01",
    "filename": null
  },
  {
    "id": 234,
    "name": "Vulcan02",
    "filename": null
  }
]

Press any key to Exist...

使用 Get 方法,沒有傳送 Cookie,呼叫 Web API 的結果
結果狀態 : False
結果訊息 : Error Code:NotFound, Error Message:Method: GET, RequestUri: 'http://vulcanwebapi.azurewebsites.net/auth/login?ReturnUrl=%2Fapi%2FMy%2FLoginCheck', Version: 1.1, Content: <null>, Headers:
{
  Accept: application/json
}
Payload :

Press any key to Exist...

HTTP 傳送與接收原始封包

讓我們來看看,這個 Web API 的呼叫動作中,在請求 (Request) 與 反應 (Response) 這兩個階段,會在網路上傳送了那些 HTTP 資料
  • 請求 (Request)
    這裡,我們使用 POST 要求,進行使用者身分的驗證,我們將使用者的帳號與密碼物件,使用 JSON 編碼技術進行編碼,並且傳送到遠端 Web API 伺服器上。
POST http://vulcanwebapi.azurewebsites.net/api/Values/Login HTTP/1.1
Accept: application/json
Content-Type: application/json; charset=utf-8
Host: vulcanwebapi.azurewebsites.net
Content-Length: 55
Expect: 100-continue
Connection: Keep-Alive

{"Account":"Vulcan","Password":"123","VerifyCode":null}
  • 反應 (Response)
    這裡的回應將會是有通過身分驗證的結果,因此,您將會看到有 Set-Cookie Http Header 出現在封包內,這個 Header 的,標示了,他的 Cookie名稱(.AspNetCore.Cookies)與實際內容。
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Kestrel
Set-Cookie: .AspNetCore.Cookies=CfDJ8IH9Z5ddETZGrIcaxOsO-eXfEHmgF5yeQ8ppzfPM2rFnj4wA23jxcLtWZ7YVt6UfmodE_AZPiYkYnKSG47GckqU0wGW7PtvA05HQrbrtvjILdxih3gk3xReBVtFtul-kgnjLLmr-lmAhIhsDhlJrgqzjbp6ebfzBwqorkdx88CWlIyqgUdEiWGGTZzc15XHSHBJQNR1xW792STk5O6LGEraFAMMhqpVdrQNxg7dEpVBDjFUFmd5mXeoUURHhiq8_X-P0kT4e05zuWIN3SOGv6fuVkTu479w1IdmmiMq3k8RwSng0bHPxKjdLO_-L3XDa_HNuNXp-euzOOvMS3UHbldrA3md_lW6KvgXqQT23PLPsvBkrIcKoNNvt0G1_v5TwSWzzG88H3qEO1DdjH_2QObgjp_efuy7Oxk-KELhLJtn-ZcNEAKxOjiVARr7Euk1igA; expires=Sun, 22 Oct 2017 09:09:40 GMT; path=/; samesite=lax; httponly
X-Powered-By: ASP.NET
Set-Cookie: ARRAffinity=9d3635139ab6649f453417d1e9047b7ed7a79b7bef031b04afeb6a2c58b33d4e;Path=/;HttpOnly;Domain=vulcanwebapi.azurewebsites.net
Date: Sun, 22 Oct 2017 08:49:41 GMT

4d
{"success":true,"message":"這個帳號與密碼正確無誤","payload":null}
0
  • 請求 (Request)
    這裡,我們使用 POST 要求,進行使用者身分的驗證,我們將使用者的帳號與密碼物件,使用 JSON 編碼技術進行編碼,並且傳送到遠端 Web API 伺服器上;不過,我們所傳送過的帳號與密碼,是不正確的。
POST http://vulcanwebapi.azurewebsites.net/api/Values/Login HTTP/1.1
Accept: application/json
Content-Type: application/json; charset=utf-8
Host: vulcanwebapi.azurewebsites.net
Content-Length: 53
Expect: 100-continue

{"Account":"Vuln","Password":"13","VerifyCode":"123"}
  • 反應 (Response)
    這裡的回應將會是沒有通過身分驗證的結果,因此,您將不會看到有 Cookie名稱為 .AspNetCore.Cookies 的 Cookie 內容。
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Server: Kestrel
X-Powered-By: ASP.NET
Set-Cookie: ARRAffinity=9d3635139ab6649f453417d1e9047b7ed7a79b7bef031b04afeb6a2c58b33d4e;Path=/;HttpOnly;Domain=vulcanwebapi.azurewebsites.net
Date: Sun, 22 Oct 2017 08:49:46 GMT

4b
{"success":false,"message":"這個帳號與密碼不正確","payload":null}
0
  • 請求 (Request)
    這裡使用剛剛取得的 Cookie,也就是 Cookie 名稱為 .AspNetCore.Cookies 的內容,進行呼叫 Web API,我們看到了,在 Http Header 中,有個名稱為 Cookie Header 欄位存在。
GET http://vulcanwebapi.azurewebsites.net/api/My/LoginCheck HTTP/1.1
Accept: application/json
Host: vulcanwebapi.azurewebsites.net
Cookie: .AspNetCore.Cookies=CfDJ8IH9Z5ddETZGrIcaxOsO-eXfEHmgF5yeQ8ppzfPM2rFnj4wA23jxcLtWZ7YVt6UfmodE_AZPiYkYnKSG47GckqU0wGW7PtvA05HQrbrtvjILdxih3gk3xReBVtFtul-kgnjLLmr-lmAhIhsDhlJrgqzjbp6ebfzBwqorkdx88CWlIyqgUdEiWGGTZzc15XHSHBJQNR1xW792STk5O6LGEraFAMMhqpVdrQNxg7dEpVBDjFUFmd5mXeoUURHhiq8_X-P0kT4e05zuWIN3SOGv6fuVkTu479w1IdmmiMq3k8RwSng0bHPxKjdLO_-L3XDa_HNuNXp-euzOOvMS3UHbldrA3md_lW6KvgXqQT23PLPsvBkrIcKoNNvt0G1_v5TwSWzzG88H3qEO1DdjH_2QObgjp_efuy7Oxk-KELhLJtn-ZcNEAKxOjiVARr7Euk1igA
  • 反應 (Response)
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Server: Kestrel
X-Powered-By: ASP.NET
Set-Cookie: ARRAffinity=9d3635139ab6649f453417d1e9047b7ed7a79b7bef031b04afeb6a2c58b33d4e;Path=/;HttpOnly;Domain=vulcanwebapi.azurewebsites.net
Date: Sun, 22 Oct 2017 08:49:47 GMT

9e
{"success":true,"message":"成功得所有資料集合","payload":[{"id":777,"name":"Vulcan01","filename":null},{"id":234,"name":"Vulcan02","filename":null}]}
0
  • 請求 (Request)
    這裡沒有傳送任何 Cookie 名稱為 .AspNetCore.Cookies 的內容到遠端 Web API 伺服器上。
GET http://vulcanwebapi.azurewebsites.net/api/My/LoginCheck HTTP/1.1
Accept: application/json
Host: vulcanwebapi.azurewebsites.net
  • 反應 (Response)
    因為呼叫這個方法,使用者沒有通過身分驗證(也就是無法提供有效的 Cookie 內容),在我們的後端系統中,將會回傳狀態碼為 302,與 Location 這個 Http Header 欄位,這個欄位值標示了要重新導向到這個網址
    您可以嘗試將 LoginCheckGetAsync 方法內的 handler.AllowAutoRedirect = false; 敘述解除註解,看看會有甚麼不同結果。
HTTP/1.1 302 Found
Content-Length: 0
Location: http://vulcanwebapi.azurewebsites.net/auth/login?ReturnUrl=%2Fapi%2FMy%2FLoginCheck
Server: Kestrel
X-Powered-By: ASP.NET
Set-Cookie: ARRAffinity=9d3635139ab6649f453417d1e9047b7ed7a79b7bef031b04afeb6a2c58b33d4e;Path=/;HttpOnly;Domain=vulcanwebapi.azurewebsites.net
Date: Sun, 22 Oct 2017 08:49:50 GMT
  • 請求 (Request)
    因為剛剛收到了要重新導向到 http://vulcanwebapi.azurewebsites.net/auth/login?ReturnUrl=%2Fapi%2FMy%2FLoginCheck 這個 URL,因此,就會產生這樣的呼叫。
GET http://vulcanwebapi.azurewebsites.net/auth/login?ReturnUrl=%2Fapi%2FMy%2FLoginCheck HTTP/1.1
Accept: application/json
Host: vulcanwebapi.azurewebsites.net
Cookie: ARRAffinity=9d3635139ab6649f453417d1e9047b7ed7a79b7bef031b04afeb6a2c58b33d4e
  • 反應 (Response)
HTTP/1.1 404 Not Found
Content-Length: 0
Server: Kestrel
X-Powered-By: ASP.NET
Date: Sun, 22 Oct 2017 08:49:50 GMT

相關文章索引

C# HttpClient WebAPI 系列文章索引

了解更多關於 [HttpClient Class] 的使用方式
了解更多關於 [使用 async 和 await 進行非同步程式設計] 的使用方式



關於 Xamarin 在台灣的學習技術資源

Xamarin 實驗室 粉絲團
歡迎加入 Xamarin 實驗室 粉絲團,在這裡,將會經常性的貼出各種關於 Xamarin / Visual Studio / .NET 的相關消息、文章、技術開發等文件,讓您可以隨時掌握第一手的 Xamarin 方面消息。
Xamarin.Forms @ Taiwan
歡迎加入 Xamarin.Forms @ Taiwan,這是台灣的 Xamarin User Group,若您有任何關於 Xamarin / Visual Studio / .NET 上的問題,都可以在這裡來與各方高手來進行討論、交流。
Xamarin 實驗室 部落格
Xamarin 實驗室 部落格 是作者本身的部落格,這個部落格將會專注於 Xamarin 之跨平台 (Android / iOS / UWP) 方面的各類開技術探討、研究與分享的文章,最重要的是,它是全繁體中文。
Xamarin.Forms 系列課程
Xamarin.Forms 系列課程 想要快速進入到 Xamarin.Forms 的開發領域,學會各種 Xamarin.Forms 跨平台開發技術,例如:MVVM、Prism、Data Binding、各種 頁面 Page / 版面配置 Layout / 控制項 Control 的用法等等,千萬不要錯過這些 Xamarin.Forms 課程


沒有留言:

張貼留言