2017年10月28日 星期六

C# HttpClient WebAPI : 13. 上傳本機圖片檔案到遠端伺服器上

在上一篇文章中,我們展示了如何使用 GET 要求,從網站中下載一個圖片檔案到本機電腦上,現在,我們需要進行二進位檔案型上傳的練習,要上傳的圖片檔案,我們已將包含在 Visual Studio 專案內。


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


上傳本機圖片檔案到遠端伺服器上

當要從用戶端上傳檔案到後端 Web API 伺服器上,我們需要使用 multipart/form-data 編碼方式,所以,我們使用 var content = new MultipartFormDataContent() 敘述,建立一個 MultipartFormDataContent 類別物件。
接者,我們使用 File.Open 方法,得到要上傳圖片的 FileStream 物件,然後,我們建立一個 StreamContent 的物件(需要把剛剛取得的 FileStream 物件設定為建構函式的引數),設定這個物件的 Content-Type 與 Content-Disposition 描述定義,這樣,就完成了上傳圖片檔案的準備工作。
var streamContent = new StreamContent(fs);
streamContent.Headers.Add("Content-Type", "application/octet-stream");
streamContent.Headers.Add("Content-Disposition", "form-data; name=\"files\"; filename=\"" + fooSt + "\"");
content.Add(streamContent, "file", filename);
最後,使用 PostAsync 方法,把剛剛產生的 MultipartFormDataContent 物件,設定為該方法的引數即可。
public static async Task<APIResult> UploadImageAsync(string filename)
{
    APIResult fooAPIResult;
    using (HttpClientHandler handler = new HttpClientHandler())
    {
        using (HttpClient client = new HttpClient(handler))
        {
            try
            {
                #region 呼叫遠端 Web API
                string FooUrl = $"http://vulcanwebapi.azurewebsites.net/api/Upload";
                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");

                #region 將剛剛拍照的檔案,上傳到網路伺服器上(使用 Multipart 的規範)
                // 規格說明請參考 https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html
                using (var content = new MultipartFormDataContent())
                {
                    var rootPath = Directory.GetCurrentDirectory();
                    // 取得這個圖片檔案的完整路徑
                    var path = Path.Combine(rootPath, filename);

                    // 開啟這個圖片檔案,並且讀取其內容
                    using (var fs = File.Open(path, FileMode.Open))
                    {
                        var fooSt = $"My{filename}";
                        var streamContent = new StreamContent(fs);
                        streamContent.Headers.Add("Content-Type", "application/octet-stream");
                        streamContent.Headers.Add("Content-Disposition", "form-data; name=\"files\"; filename=\"" + fooSt + "\"");
                        content.Add(streamContent, "file", filename);

                        // 上傳到遠端伺服器上
                        response = await client.PostAsync(fooFullUrl, content);
                    }
                }
                #endregion
                #endregion
                #endregion

                #region 處理呼叫完成 Web API 之後的回報結果
                if (response != null)
                {
                    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 動作

這個範例中,將會指向 URL http://vulcanwebapi.azurewebsites.net/api/Upload ,此時,將會觸發 Web API 伺服器上的 Upload 控制器(Controller)的 public async Task<APIResult> Post(List<IFormFile> files)動作(Action),其該動作的原始碼如下所示。
由這個方法參數可以看的出來,這個 Web API 動作 (Action)方法,需要使用 POST 請求來呼叫,並且,可以接受一次上傳多個檔案的請求。(在用戶端若想要上傳多個檔案,可以建立多個 StreamContent 物件,並且將這些物件都加入到 MultipartFormDataContent 物件內即可)
這個 Web API 動作,將會回傳一個 APIData 的 JSON 資料。
[HttpPost]
public async Task<APIResult> Post(List<IFormFile> files)
{
    // https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads

    string webDatasRoot = Path.Combine(_HostingEnvironment.WebRootPath, "Datas");

    long size = files.Sum(f => f.Length);

    // full path to file in temp location

    if (files.Count > 0)
    {
        foreach (var formFile in files)
        {
            if (formFile.Length > 0)
            {
                var filePath = Path.Combine(webDatasRoot, formFile.FileName);
                using (var stream = new FileStream(filePath, FileMode.Create))
                {
                    await formFile.CopyToAsync(stream);
                }

                fooAPIResult.Success = true;
                fooAPIResult.Message = "檔案上傳成功";
                fooAPIResult.Payload = new APIData
                {
                    Id = 3000,
                    Name = "Your Name",
                    Filename = formFile.FileName
                };
            }
        }
    }
    else
    {
        fooAPIResult.Success = false;
        fooAPIResult.Message = "沒有任何檔案上傳";
        fooAPIResult.Payload = null;
    }

    return fooAPIResult;
}

進行測試

在程式進入點函式,我們直接呼叫 UploadImageAsync 方法,並且指定要上傳圖片檔案的名出,一旦該題片上傳之後,將會使用瀏覽器將這個 Web 伺服器上的圖片開起來。
static async Task Main(string[] args)
{
    await UploadImageAsync("vulcan.png");
    Process myProcess = new Process();
    try
    {
        // true is the default, but it is important not to set it to false
        myProcess.StartInfo.UseShellExecute = true;
        myProcess.StartInfo.FileName = "https://vulcanwebapi.azurewebsites.net/Datas/Myvulcan.png";
        myProcess.Start();
    }
    catch (Exception e)
    {
        Console.WriteLine(e.Message);
    }
    Console.WriteLine($"Press any key to Exist...{Environment.NewLine}");
    Console.ReadKey();
}

執行結果

這個測試將會輸出底下內容
Press any key to Exist...

HTTP 傳送與接收原始封包

讓我們來看看,這個 Web API 的呼叫動作中,在請求 (Request) 與 反應 (Response) 這兩個階段,會在網路上傳送了那些 HTTP 資料
  • 請求 (Request)
    在這裡的第三行中,您將會看到 Content-Type 標示為 multipart/form-data,說明了這次 POST 請求的資料編碼封裝方式,而由於採用這樣的編碼方式,所以當然會看到這樣的 Http Header boundary="cedff4d2-6ac3-492e-8e5a-56ba4fc9ea27",這是因為每個要傳遞的欄位資料,都需要使用這個 boundary 值在 Http Body 內進行使用,最後,我們使用了 HttpClient 類別,所以,對於 Http Header Content-Length 並不需要做任何的計算,HttpClient 會幫我們計算出來(也就是說,若這個數值計算或者寫入不正確,將會導致這次的 POST 請求無法正常完成。
    由於是要上傳圖片檔案到遠端 Web API 伺服器上,因此,在 Http Body 的部分會有很多關於該圖片檔案的編碼內容,我們在這裡僅列出一小部分做為參考之用。
POST http://vulcanwebapi.azurewebsites.net/api/Upload HTTP/1.1
Accept: application/json
Content-Type: multipart/form-data; boundary="cedff4d2-6ac3-492e-8e5a-56ba4fc9ea27"
Host: vulcanwebapi.azurewebsites.net
Content-Length: 160584
Expect: 100-continue
Connection: Keep-Alive

--cedff4d2-6ac3-492e-8e5a-56ba4fc9ea27
Content-Type: application/octet-stream
Content-Disposition: form-data; name="files"; filename="Myvulcan.png"

 PNG
 

IHDR              u         pHYs          +      tIME         8ǫe     tEXtAuthor    H    tEXtDescription      !#   
tEXtCopyright    :    tEXtCreation time 5             tEXtSoftware ]p :   



 b   zBO     = '   e  Gz!   b   zBO     = '        " *    zBO     = ' E        zBO     =    3  \       IEND B` 
--cedff4d2-6ac3-492e-8e5a-56ba4fc9ea27--
  • 反應 (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: Mon, 23 Oct 2017 02:13:20 GMT

72
{"success":true,"message":"檔案上傳成功","payload":{"id":3000,"name":"Your Name","filename":"Myvulcan.png"}}
0

相關文章索引

C# HttpClient WebAPI 系列文章索引


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


C# HttpClient WebAPI : 16. 呼叫 Web API 之後端與用戶端例外異常處理用法,避免應用程式異常中斷

在我們提供的 HttpClient 用戶端程式碼中,有將 HttpClient 的相關存取程式碼,都加入到 try 區塊內,這樣,當用戶端程式碼發生例外異常的時候,我們就可以將其例外異常捕捉起來;不過,對於 Web API 伺服器那端,若發生了例外異常之後,我們在用戶端處理上,就會有些狀況發生。
在我們的設計模式中,希望不論後端系統是否有例外異常發生的時候,都能夠回傳 APIResult JSON 編碼內容,這樣,我們就可以憑藉著這個些屬性值,做出適當的判斷與處理。由於我們後端的 Web API 程式碼採用的是 ASP.NET Core 的方式來進行開發,在後端那哩,我們可以設計一個繼承 ExceptionFilterAttribute 類別的物件,並且套用到整個控制器或者相關動作中,這樣,在用戶端那哩,我們就可以統一接收到 APIResult JSON 編碼內容。

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


呼叫 Web API 之後端與用戶端例外異常處理用法,避免應用程式異常中斷

在這個 GetExceptionFilter 方法中,我們會接收一個字串參數,這個參數會用來產生不同的 URL,當傳入的是 GetExceptionFilter 字串,我們將會呼叫有套用客製化的 CustomExceptionFilterAttribute 屬性,而傳入的是 GetException 字串,則沒有套用任何 ExceptionFilterAttribute
當取得 HttpResponseMessage 物件之後,我們會使用 response.IsSuccessStatusCode 屬性來判斷這次的呼叫,是否有成功,若沒有成功的話,我們會先嘗試解碼回傳內容,若成功的話,就可以取得後端伺服器回覆的內容,但是解碼失敗的話,則會在用戶端自行產生一個新的 APIResult 物件,並且設定回傳錯誤資訊。
private static async Task<APIResult> GetExceptionFilter(string action)
{
    APIResult fooAPIResult;
    using (HttpClientHandler handler = new HttpClientHandler())
    {
        using (HttpClient client = new HttpClient(handler))
        {
            try
            {
                #region 呼叫遠端 Web API
                string FooUrl = $"http://vulcanwebapi.azurewebsites.net/api/values/{action}";
                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");

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

                #region 處理呼叫完成 Web API 之後的回報結果
                if (response != null)
                {
                    if (response.IsSuccessStatusCode == true)
                    {
                        // 取得呼叫完成 API 後的回報內容
                        String strResult = await response.Content.ReadAsStringAsync();
                        fooAPIResult = JsonConvert.DeserializeObject<APIResult>(strResult, new JsonSerializerSettings { MetadataPropertyHandling = MetadataPropertyHandling.Ignore });
                    }
                    else
                    {
                        // 這裡將會取得這次例外異常的錯誤資訊
                        String strResult = await response.Content.ReadAsStringAsync();
                        fooAPIResult = JsonConvert.DeserializeObject<APIResult>(strResult, new JsonSerializerSettings { MetadataPropertyHandling = MetadataPropertyHandling.Ignore });

                        if (fooAPIResult == null)
                        {
                            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 動作

當 URL 指向的是 http://vulcanwebapi.azurewebsites.net/api/values/GetExceptionFilter ,此時,將會觸發 Web API 伺服器上的 Values 控制器(Controller)的 public APIResult GetExceptionFilter() 動作(Action),其有套用我們自訂的 CustomExceptionFilter C# 屬性 (Attribute) 在這個方法上,其該動作的原始碼如下所示。
這個 Web API 動作,將會回傳一個 APIData 的 JSON 資料。
[HttpGet("GetExceptionFilter")]
[CustomExceptionFilter]
public APIResult GetExceptionFilter()
{
    APIResult foo = new APIResult();
    throw new Exception("喔喔,我發生錯誤了");
    return foo;
}
而這個 CustomExceptionFilterAttribute 類別,則是當後端 Web API 有例外異常發生的時候,就會執行 OnException 這裡覆寫方法,在這個方法內,我們取得當時後端伺服器中發生的例外異常呼叫堆疊與訊息,並且把些資料,設定到 APIResul 類別物件內,這樣,用戶端就會得到這個回傳訊息(雖然,伺服器端已經發生了錯誤)。
這裡,我們也使用了 context.HttpContext.Response.StatusCode 設定了此次 Http 呼叫的處理狀態碼為 500。
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
    public override void OnException(ExceptionContext context)
    {
        APIResult foo = new APIResult();
        // Unhandled errors
        var msg = context.Exception.GetBaseException().Message;
        string stack = context.Exception.StackTrace;

        foo.Success = false;
        foo.Message = msg;
        foo.Payload = stack;

        context.HttpContext.Response.StatusCode = 500;
        context.Result = new JsonResult(foo);

        base.OnException(context);
    }

}
當 URL 指向的是 http://vulcanwebapi.azurewebsites.net/api/values/GetException ,此時,將會觸發 Web API 伺服器上的 Values 控制器(Controller)的 public APIResult GetException() 動作(Action),這裡沒有套用任何自訂的 ExceptionFilterAttribute C# 屬性 (Attribute) 在這個方法上,其該動作的原始碼如下所示。
這個 Web API 動作,將不會回傳一個 APIData 的 JSON 資料。
[HttpGet("GetException")]
public APIResult GetException()
{
    APIResult foo = new APIResult();
    throw new Exception("喔喔,我發生錯誤了");
    return foo;
}

進行測試

在程式進入點函式,我們呼叫了兩次 GetExceptionFilter 方法,不過,由於傳送的引數字串不同,會導致執行不同 URL 的 Http GET 要求。
static async Task Main(string[] args)
{
    var foo = await GetExceptionFilter("GetExceptionFilter");
    Console.WriteLine($"使用 Get 方法呼叫,並有套用 ExceptionFilter");
    Console.WriteLine($"結果狀態 : {foo.Success}");
    Console.WriteLine($"結果訊息 : {foo.Message}");
    Console.WriteLine($"其他訊息 : {foo.Payload}");
    Console.WriteLine($"");

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

    foo = await GetExceptionFilter("GetException");
    Console.WriteLine($"使用 Get 方法呼叫,沒有套用 ExceptionFilter");
    Console.WriteLine($"結果狀態 : {foo.Success}");
    Console.WriteLine($"結果訊息 : {foo.Message}");
    Console.WriteLine($"其他訊息 : {foo.Payload}");
    Console.WriteLine($"");

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

執行結果

這個測試將會輸出底下內容
使用 Get 方法呼叫,並有套用 ExceptionFilter
結果狀態 : False
結果訊息 : 喔喔,我發生錯誤了
其他訊息 :    at VulcanWebAPI.Controllers.ValuesController.GetExceptionFilter() in D:\Vulcan\GitHub\CSharpNotes\WebAPI\VulcanWebAPI\Controllers\ValuesController.cs:line 86
   at lambda_method(Closure , Object , Object[] )
   at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeActionMethodAsync>d__12.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeNextActionFilterAsync>d__10.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeInnerFilterAsync>d__14.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.<InvokeNextExceptionFilterAsync>d__23.MoveNext()

Press any key to Exist...

使用 Get 方法呼叫,沒有套用 ExceptionFilter
結果狀態 : False
結果訊息 : Error Code:InternalServerError, Error Message:Method: GET, RequestUri: 'http://vulcanwebapi.azurewebsites.net/api/values/GetException', Version: 1.1, Content: <null>, Headers:
{
  Accept: application/json
}
其他訊息 :

Press any key to Exist...

HTTP 傳送與接收原始封包

讓我們來看看,這個 Web API 的呼叫動作中,在請求 (Request) 與 反應 (Response) 這兩個階段,會在網路上傳送了那些 HTTP 資料
  • 請求 (Request)
    這裡將會呼叫 URL 有套用 CustomExceptionFilterAttribute 的屬性之控制器動作。
GET http://vulcanwebapi.azurewebsites.net/api/values/GetExceptionFilter HTTP/1.1
Accept: application/json
Host: vulcanwebapi.azurewebsites.net
Connection: Keep-Alive
  • 反應 (Response)
    由於 ASP.NET Core 系統會自動捕捉到任何在呼叫 Web API 過程中所產生的例外異常,因此,在用戶端中,我們可以透過回傳的 JSON 字串,經過反序列化得到 APIResult 類別物件;我們可以從這個物件,得到更多關於此次伺服器上發生的錯誤資訊。
HTTP/1.1 500 Internal Server Error
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: Mon, 23 Oct 2017 02:24:12 GMT

7f0
{"success":false,"message":"喔喔,我發生錯誤了","payload":"   at VulcanWebAPI.Controllers.ValuesController.GetExceptionFilter() in D:\\Vulcan\\GitHub\\CSharpNotes\\WebAPI\\VulcanWebAPI\\Controllers\\ValuesController.cs:line 86\r\n   at lambda_method(Closure , Object , Object[] )\r\n   at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters)\r\n   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeActionMethodAsync>d__12.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()\r\n   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeNextActionFilterAsync>d__10.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)\r\n   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)\r\n   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeInnerFilterAsync>d__14.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()\r\n   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.<InvokeNextExceptionFilterAsync>d__23.MoveNext()"}
0
  • 請求 (Request)
    這裡將會呼叫 URL 將沒有套用 CustomExceptionFilterAttribute 的屬性之控制器動作。
GET http://vulcanwebapi.azurewebsites.net/api/values/GetException HTTP/1.1
Accept: application/json
Host: vulcanwebapi.azurewebsites.net
  • 反應 (Response)
    您可以看到,由於沒有套用任何 ExceptionFilterAttribute 的客製類別屬性,所以,所得到的 Http 回應封包卻沒有辦法看到任何此次異常發生了甚麼問題,只知道狀態碼是 500 Internal Server Error
HTTP/1.1 500 Internal Server Error
Content-Length: 0
Server: Kestrel
X-Powered-By: ASP.NET
Set-Cookie: ARRAffinity=9d3635139ab6649f453417d1e9047b7ed7a79b7bef031b04afeb6a2c58b33d4e;Path=/;HttpOnly;Domain=vulcanwebapi.azurewebsites.net
Date: Mon, 23 Oct 2017 02:24:16 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 課程