相信已经有很多文章来介绍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