相信已經有很多文章來介紹ASP.Net Web API 技術,本系列文章主要介紹如何使用資料流,HTTPS,以及可擴充套件的Web API 方面的技術,系列文章主要有三篇內容。
主要內容如下:
I 資料流
II 使用HTTPS
III 可擴充套件的Web API 文件
專案環境要求
- VS 2012(SP4)及以上,
- .Net 框架4.5.1
- Nuget包,可在packages.config 檔案中查尋
本文涉及的知識點
- ActionFilter
- AuthorizationFilter
- DelegateHandler
- Different Web API routing 屬性
- MediaTypeFormatter
- OWIN
- Self Hosting
- Web API 文件及可擴充套件功能
.Net 框架
- Async/Await
- .NET reflection
- Serialization
- ASP.NET Web API/MVC Error handling
- IIS ,HTTPS 及Certificate
- 設計準則及技術
前言
自從ASP.NET MVC 4之後.Net 框架開始支援ASP.NET Web API ,ASP.NET Web API 基於HTTP 協議建立的,是構建 RESTful 服務和處理資料的理想平臺,旨在使用HTTP 技術實現對多平臺的支援。
ASP.NET Web API 以request-response 的訊息轉換模式為主,客戶端向伺服器傳送請求,伺服器端響應客戶端請求。響應可同步或非同步。
個人認為使用Web API建立應用需要注意的三個關鍵點:
- 採用服務及方法滿足的目標
- 每個方法的輸入,如請求
- 每個方法的輸出,如響應
通常情況下,Asp.Net Web API 定義method語法與HTTP方法一一對應的,如自定義方法名 GetPysicians(),則與HTTP中Get 方法匹配。下圖是常用匹配表。
但是此方法在很多情況下,並不實用,假如你想在單個API controller 類中定義多個Get 或Post 方法,在這種情況下,需要定義包含action 的路徑,將Action 作為URI 的一部分。以下是配置程式碼:
1: public static void Register(HttpConfiguration config)
2: {
3: // Web API configuration and services
4: // Web API routes
5: config.MapHttpAttributeRoutes();
6:
7: config.Routes.MapHttpRoute(name: "PhysicianApi",
8: routeTemplate: "{controller}/{action}/{id}",
9: defaults: new { id = RouteParameter.Optional });
10: }
但是此方法不足以應對所有情況,如果想實現從中央倉庫刪除檔案,並且想呼叫同一個方法來獲取檔案,這種情況下,Web API 框架需要偽裝Get 及Delete對應的HTTP 方法屬性。如圖所示:
RemoveFile 方法可被Delete(
HttpDelete
) 或 Get(HttpGet
)方法同時呼叫,從某種程度來說,HTTP 方法使開發人員命名 API“方法”變得簡單而標準。
Web API框架也提供了一些其他功能來處理路徑方面的問題,與MVC 的路徑處理方法相似。因此可定義不同型別的Action方法。
資料流
網路App 最常見的執行操作就是獲取資料流。ASP.NET Web API 能夠處理客戶端與伺服器端傳輸的重量級的資料流,資料流可來源於目錄檔案,也可是資料庫中的二進位制檔案。本文主要介紹兩種方法“Download”和“Upload”實現資料流相關的功能,Download是從伺服器下載資料操作,而Upload則是上傳資料到伺服器。
相關專案
WebAPIDataStreaming
WebAPIClient
POCOLibrary
在對程式碼解釋之前,首先來了解如何配置IIS(7.5)和Web API 服務Web.Config 檔案。
1. 保證Downloads/Uploads 涉及的檔案具有讀寫許可權。
2. 保證有足夠容量的內容或因公安空間處理大檔案。
3. 如果檔案較大
a. 配置Web.Config 檔案時,保證
maxRequestLength 時響應時間
executionTimeout 合理。具體的值主要依賴於資料大小,允許一次性上傳的最大資料為2 GB
b. 保證
maxAllowedContentLength 在
requestFiltering部分配置下正確設定,預設值為30MB,最大值4GB
一旦完成預先配置,那麼建立資料流服務就非常簡單了,首先 需要定義檔案流“ApiController
”,如下:
1: /// <summary>
2: /// File streaming API
3: /// </summary>
4: [RoutePrefix("filestreaming")]
5: [RequestModelValidator]
6: public class StreamFilesController : ApiController
7: {
8: /// <summary>
9: /// Get File meta data
10: /// </summary>
11: /// <param name="fileName">FileName value</param>
12: /// <returns>FileMeta data response.</returns>
13: [Route("getfilemetadata")]
14: public HttpResponseMessage GetFileMetaData(string fileName)
15: {
16: // .........................................
17: // Full code available in the source control
18: // .........................................
19:
20: }
21:
22: /// <summary>
23: /// Search file and return its meta data in all download directories
24: /// </summary>
25: /// <param name="fileName">FileName value</param>
26: /// <returns>List of file meta datas response</returns>
27: [HttpGet]
28: [Route("searchfileindownloaddirectory")]
29: public HttpResponseMessage SearchFileInDownloadDirectory(string fileName)
30: {
31: // .........................................
32: // Full code available in the source control
33: // .........................................
34: }
35:
36: /// <summary>
37: /// Asynchronous Download file
38: /// </summary>
39: /// <param name="fileName">FileName value</param>
40: /// <returns>Tasked File stream response</returns>
41: [Route("downloadasync")]
42: [HttpGet]
43: public async Task<HttpResponseMessage> DownloadFileAsync(string fileName)
44: {
45: // .........................................
46: // Full code available in the source control
47: // .........................................
48: }
49:
50: /// <summary>
51: /// Download file
52: /// </summary>
53: /// <param name="fileName">FileName value</param>
54: /// <returns>File stream response</returns>
55: [Route("download")]
56: [HttpGet]
57: public HttpResponseMessage DownloadFile(string fileName)
58: {
59: // .........................................
60: // Full code available in the source control
61: // .........................................
62: }
63:
64: /// <summary>
65: /// Upload file(s)
66: /// </summary>
67: /// <param name="overWrite">An indicator to overwrite a file if it exist in the server</param>
68: /// <returns>Message response</returns>
69: [Route("upload")]
70: [HttpPost]
71: public HttpResponseMessage UploadFile(bool overWrite)
72: {
73: // .........................................
74: // Full code available in the source control
75: // .........................................
76: }
77:
78: /// <summary>
79: /// Asynchronous Upload file
80: /// </summary>
81: /// <param name="overWrite">An indicator to overwrite a file if it exist in the server</param>
82: /// <returns>Tasked Message response</returns>
83: [Route("uploadasync")]
84: [HttpPost]
85: public async Task<HttpResponseMessage> UploadFileAsync(bool overWrite)
86: {
87: // .........................................
88: // Full code available in the source control
89: // .........................................
90: }
91: }
Download 服務方法首先需要確認請求的檔案是否存在,如果未找到,則返回錯誤提示“file is not found”,如果找到此檔案,內容則轉換為位元組附加到響應物件,為“application/octet-stream” MIMI 內容型別。
1: /// <summary>
2: /// Download file
3: /// </summary>
4: /// <param name="fileName">FileName value<param>
5: /// <returns>File stream response<returns>
6: [Route("download")]
7: [HttpGet]
8: public HttpResponseMessage DownloadFile(string fileName)
9: {
10: HttpResponseMessage response = Request.CreateResponse();
11: FileMetaData metaData = new FileMetaData();
12: try
13: {
14: string filePath = Path.Combine(this.GetDownloadPath(), @"\", fileName);
15: FileInfo fileInfo = new FileInfo(filePath);
16:
17: if (!fileInfo.Exists)
18: {
19: metaData.FileResponseMessage.IsExists = false;
20: metaData.FileResponseMessage.Content = string.Format("{0} file is not found !", fileName);
21: response = Request.CreateResponse(HttpStatusCode.NotFound, metaData, new MediaTypeHeaderValue("text/json"));
22: }
23: else
24: {
25: response.Headers.AcceptRanges.Add("bytes");
26: response.StatusCode = HttpStatusCode.OK;
27: response.Content = new StreamContent(fileInfo.ReadStream());
28: response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
29: response.Content.Headers.ContentDisposition.FileName = fileName;
30: response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
31: response.Content.Headers.ContentLength = fileInfo.Length;
32: }
33: }
34: catch (Exception exception)
35: {
36: // Log exception and return gracefully
37: metaData = new FileMetaData();
38: metaData.FileResponseMessage.Content = ProcessException(exception);
39: response = Request.CreateResponse(HttpStatusCode.InternalServerError, metaData, new MediaTypeHeaderValue("text/json"));
40: }
41: return response;
42: }
Upload服務方法則會在
multipart/form-data MIMI 內容型別執行,首先會檢測HTTP 請求的內容型別是否是多主體,如果是,則對比內容長度是否超過最大尺寸,如果沒有超過,則開始上傳內容,當操作完成之後,則提示相應的資訊。
程式碼片段如下:
1: /// <summary>
2: /// Upload file(s)
3: /// </summary>
4: /// <param name="overWrite">An indicator to overwrite a file if it exist in the server.</param>
5: /// <returns>Message response</returns>
6: [Route("upload")]
7: [HttpPost]
8: public HttpResponseMessage UploadFile(bool overWrite)
9: {
10: HttpResponseMessage response = Request.CreateResponse();
11: List<FileResponseMessage> fileResponseMessages = new List<FileResponseMessage>();
12: FileResponseMessage fileResponseMessage = new FileResponseMessage { IsExists = false };
13:
14: try
15: {
16: if (!Request.Content.IsMimeMultipartContent())
17: {
18: fileResponseMessage.Content = "Upload data request is not valid !";
19: fileResponseMessages.Add(fileResponseMessage);
20: response = Request.CreateResponse(HttpStatusCode.UnsupportedMediaType, fileResponseMessages, new MediaTypeHeaderValue("text/json"));
21: }
22:
23: else
24: {
25: response = ProcessUploadRequest(overWrite);
26: }
27: }
28: catch (Exception exception)
29: {
30: // Log exception and return gracefully
31: fileResponseMessage = new FileResponseMessage { IsExists = false };
32: fileResponseMessage.Content = ProcessException(exception);
33: fileResponseMessages.Add(fileResponseMessage);
34: response = Request.CreateResponse(HttpStatusCode.InternalServerError, fileResponseMessages, new MediaTypeHeaderValue("text/json"));
35:
36: }
37: return response;
38: }
39:
40: /// <summary>
41: /// Asynchronous Upload file
42: /// </summary>
43: /// <param name="overWrite">An indicator to overwrite a file if it exist in the server.<param>
44: /// <returns>Tasked Message response</returns>
45: [Route("uploadasync")]
46: [HttpPost]
47: public async Task<HttpResponseMessage> UploadFileAsync(bool overWrite)
48: {
49: return await new TaskFactory().StartNew(
50: () =>
51: {
52: return UploadFile(overWrite);
53: });
54: }
55:
56: /// <summary>
57: /// Process upload request in the server
58: /// </summary>
59: /// <param name="overWrite">An indicator to overwrite a file if it exist in the server.</param>
60: /// </returns>List of message object</returns>
61: private HttpResponseMessage ProcessUploadRequest(bool overWrite)
62: {
63: // .........................................
64: // Full code available in the source control
65: // .........................................
66: }
呼叫download 及 upload 檔案方法是控制檯應用,App 假定檔案流服務通過HttpClient和相關類。基本下載檔案程式碼,建立下載HTTP 請求物件。
1: /// <summary>
2: /// Download file
3: /// </summary>
4: /// <returns>Awaitable Task object</returns>
5: private static async Task DownloadFile()
6: {
7: Console.ForegroundColor = ConsoleColor.Green;
8: Console.WriteLine("Please specify file name with extension and Press Enter :- ");
9: string fileName = Console.ReadLine();
10: string localDownloadPath = string.Concat(@"c:\", fileName); // the path can be configurable
11: bool overWrite = true;
12: string actionURL = string.Concat("downloadasync?fileName=", fileName);
13:
14: try
15: {
16: Console.WriteLine(string.Format("Start downloading @ {0}, {1} time ",
17: DateTime.Now.ToLongDateString(),
18: DateTime.Now.ToLongTimeString()));
19:
20:
21: using (HttpClient httpClient = new HttpClient())
22: {
23: httpClient.BaseAddress = baseStreamingURL;
24: HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, actionURL);
25:
26: await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).
27: ContinueWith((response)
28: =>
29: {
30: Console.WriteLine();
31: try
32: {
33: ProcessDownloadResponse(localDownloadPath, overWrite, response);
34: }
35: catch (AggregateException aggregateException)
36: {
37: Console.ForegroundColor = ConsoleColor.Red;
38: Console.WriteLine(string.Format("Exception : ", aggregateException));
39: }
40: });
41: }
42: }
43: catch (Exception ex)
44: {
45: Console.ForegroundColor = ConsoleColor.Red;
46: Console.WriteLine(ex.Message);
47: }
48: }
49:
50:
51: /// <summary>
52: /// Process download response object
53: /// </summary>
54: /// <param name="localDownloadFilePath">Local download file path</param>
55: /// <param name="overWrite">An indicator to overwrite a file if it exist in the client.</param>
56: /// <param name="response">Awaitable HttpResponseMessage task value</param>
57: private static void ProcessDownloadResponse(string localDownloadFilePath, bool overWrite,
58: Task<HttpResponseMessage> response)
59: {
60: if (response.Result.IsSuccessStatusCode)
61: {
62: response.Result.Content.DownloadFile(localDownloadFilePath, overWrite).
63: ContinueWith((downloadmessage)
64: =>
65: {
66: Console.ForegroundColor = ConsoleColor.Green;
67: Console.WriteLine(downloadmessage.TryResult());
68: });
69: }
70: else
71: {
72: ProcessFailResponse(response);
73: }
74: }
注意上述程式碼中HttpClient 物件傳送請求,並等待響應傳送Header內容(HttpCompletionOption.ResponseHeadersRead )。而不是傳送全部的響應內容檔案。一旦Response header 被讀,則執行驗證,一旦驗證成功,則執行下載方法。
以下程式碼呼叫upload 檔案流,與下載方法類似,建立多主體表單資料,併傳送給伺服器端。
1: /// <summary>
2: /// Upload file
3: /// </summary>
4: /// <returns>Awaitable task object</returns>
5: private static async Task UploadFile()
6: {
7: try
8: {
9: string uploadRequestURI = "uploadasync?overWrite=true";
10:
11: MultipartFormDataContent formDataContent = new MultipartFormDataContent();
12:
13: // Validate the file and add to MultipartFormDataContent object
14: formDataContent.AddUploadFile(@"c:\nophoto.png");
15: formDataContent.AddUploadFile(@"c:\ReadMe.txt");
16:
17: if (!formDataContent.HasContent()) // No files found to be uploaded
18: {
19: Console.ForegroundColor = ConsoleColor.Red;
20: Console.Write(formDataContent.GetUploadFileErrorMesage());
21: return;
22: }
23: else
24: {
25: string uploadErrorMessage = formDataContent.GetUploadFileErrorMesage();
26: if (!string.IsNullOrWhiteSpace(uploadErrorMessage)) // Some files couldn't be found
27: {
28: Console.ForegroundColor = ConsoleColor.Red;
29: Console.Write(uploadErrorMessage);
30: }
31:
32: HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, uploadRequestURI);
33: request.Content = formDataContent;
34:
35: using (HttpClient httpClient = new HttpClient())
36: {
37: Console.ForegroundColor = ConsoleColor.Green;
38: Console.WriteLine(string.Format("Start uploading @ {0}, {1} time ",
39: DateTime.Now.ToLongDateString(),
40: DateTime.Now.ToLongTimeString()));
41:
42: httpClient.BaseAddress = baseStreamingURL;
43: await httpClient.SendAsync(request).
44: ContinueWith((response)
45: =>
46: {
47: try
48: {
49: ProcessUploadResponse(response);
50: }
51: catch (AggregateException aggregateException)
52: {
53: Console.ForegroundColor = ConsoleColor.Red;
54: Console.WriteLine(string.Format("Exception : ", aggregateException));
55: }
56: });
57: }
58: }
59: }
60: catch (Exception ex)
61: {
62: Console.ForegroundColor = ConsoleColor.Red;
63: Console.WriteLine(ex.Message);
64: }
65: }
66:
67: /// <summary>
68: /// Process download response object
69: /// </summary>
70: /// <param name="response">Awaitable HttpResponseMessage task value</param>
71: private static void ProcessUploadResponse(Task<HttpResponseMessage> response)
72: {
73: if (response.Result.IsSuccessStatusCode)
74: {
75: string uploadMessage = string.Format("\nUpload completed @ {0}, {1} time ",
76: DateTime.Now.ToLongDateString(),
77: DateTime.Now.ToLongTimeString());
78: Console.ForegroundColor = ConsoleColor.Green;
79: Console.WriteLine(string.Format("{0}\nUpload Message : \n{1}", uploadMessage,
80: JsonConvert.SerializeObject(response.Result.Content.ReadAsAsync<List<FileResponseMessage>>().TryResult(), Formatting.Indented)));
81: }
82: else
83: {
84: ProcessFailResponse(response);
85: }
86: }
資料流專案由可擴充套件類和方法組成,本文就不再詳述。下篇文章中將介紹“使用HTTPS 開發專案”
資料流是資料傳輸中的重要部分,學習了本節內容有助於大家更好地進行ASP.NET的開發。當然,還可以藉助一些開發工具來助力開發過程。ComponentOne Studio for ASP.NET 提供了一整套完備的開發工具包,用於在各種瀏覽器中建立和設計具有現代風格的Web應用程式。
原文連結:http://www.codeproject.com/Articles/838274/Web-API-Thoughts-of-Data-Streaming#Hist