相信已经有很多文章来介绍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: ///
2: /// File streaming API
3: ///
4: [RoutePrefix("filestreaming")]
5: [RequestModelValidator]
6: public class StreamFilesController : ApiController
7: {
8: ///
9: /// Get File meta data
10: ///
11: /// FileName value
12: ///FileMeta data response.
13: [Route("getfilemetadata")]
14: public HttpResponseMessage GetFileMetaData(string fileName)
15: {
16: // .........................................
17: // Full code available in the source control
18: // .........................................
19:
20: }
21:
22: ///
23: /// Search file and return its meta data in all download directories
24: ///
25: /// FileName value
26: ///List of file meta datas response
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: ///
37: /// Asynchronous Download file
38: ///
39: /// FileName value
40: ///Tasked File stream response
41: [Route("downloadasync")]
42: [HttpGet]
43: public async TaskDownloadFileAsync(string fileName)
44: {
45: // .........................................
46: // Full code available in the source control
47: // .........................................
48: }
49:
50: ///
51: /// Download file
52: ///
53: /// FileName value
54: ///File stream response
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: ///
65: /// Upload file(s)
66: ///
67: /// An indicator to overwrite a file if it exist in the server
68: ///Message response
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: ///
79: /// Asynchronous Upload file
80: ///
81: /// An indicator to overwrite a file if it exist in the server
82: ///Tasked Message response
83: [Route("uploadasync")]
84: [HttpPost]
85: public async TaskUploadFileAsync(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: ///
2: /// Download file
3: ///
4: /// FileName value
5: ///File stream response
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("p_w_upload");
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: ///
2: /// Upload file(s)
3: ///
4: /// An indicator to overwrite a file if it exist in the server.
5: ///Message response
6: [Route("upload")]
7: [HttpPost]
8: public HttpResponseMessage UploadFile(bool overWrite)
9: {
10: HttpResponseMessage response = Request.CreateResponse();
11: ListfileResponseMessages = new List ();
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: ///
41: /// Asynchronous Upload file
42: ///
43: /// An indicator to overwrite a file if it exist in the server.
44: ///Tasked Message response
45: [Route("uploadasync")]
46: [HttpPost]
47: public async TaskUploadFileAsync(bool overWrite)
48: {
49: return await new TaskFactory().StartNew(
50: () =>
51: {
52: return UploadFile(overWrite);
53: });
54: }
55:
56: ///
57: /// Process upload request in the server
58: ///
59: /// An indicator to overwrite a file if it exist in the server.
60: /// List of message object
61: private HttpResponseMessage ProcessUploadRequest(bool overWrite)
62: {
63: // .........................................
64: // Full code available in the source control
65: // .........................................
66: }
调用download 及 upload 文件方法是控制台应用,App 假定文件流服务通过HttpClient和相关类。基本下载文件代码,创建下载HTTP 请求对象。
1: ///
2: /// Download file
3: ///
4: ///Awaitable Task object
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: ///
52: /// Process download response object
53: ///
54: /// Local download file path
55: /// An indicator to overwrite a file if it exist in the client.
56: /// Awaitable HttpResponseMessage task value
57: private static void ProcessDownloadResponse(string localDownloadFilePath, bool overWrite,
58: Taskresponse)
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: ///
2: /// Upload file
3: ///
4: ///Awaitable task object
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: ///
68: /// Process download response object
69: ///
70: /// Awaitable HttpResponseMessage task value
71: private static void ProcessUploadResponse(Taskresponse)
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>().TryResult(), Formatting.Indented)));
81: }
82: else
83: {
84: ProcessFailResponse(response);
85: }
86: }
数据流项目由可扩展类和方法组成,本文就不再详述。下篇文章中将介绍“使用HTTPS 开发项目”
下载源代码
原文链接:http://www.codeproject.com/Articles/838274/Web-API-Thoughts-of-Data-Streaming#Hist