ASP.NET Web API 应用教程(一) ——数据流使用

ASP.NET  Web API 应用教程(一) ——数据流使用

相信已经有很多文章来介绍ASP.Net Web API 技术,本系列文章主要介绍如何使用数据流,HTTPS,以及可扩展的Web API 方面的技术,系列文章主要有三篇内容。

主要内容如下:

I  数据流

II 使用HTTPS

III 可扩展的Web API 文档

项目环境要求

  • VS 2012(SP4)及以上,
  • .Net 框架4.5.1
  • Nuget包,可在packages.config 文件中查寻

本文涉及的知识点

  1. ActionFilter
  2. AuthorizationFilter
  3. DelegateHandler
  4. Different Web API routing 属性
  5. MediaTypeFormatter
  6. OWIN
  7. Self Hosting
  8. Web API 文档及可扩展功能

.Net 框架

  1. Async/Await
  2. .NET reflection
  3. Serialization
  4. ASP.NET Web API/MVC Error handling
  5. IIS ,HTTPS 及Certificate
  6. 设计准则及技术

前言

ASP.NET  Web API 应用教程(一) ——数据流使用

自从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 方法匹配。下图是常用匹配表。

ASP.NET  Web API 应用教程(一) ——数据流使用

但是此方法在很多情况下,并不实用,假如你想在单个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 方法属性。如图所示:

ASP.NET  Web API 应用教程(一) ——数据流使用

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

ASP.NET  Web API 应用教程(一) ——数据流使用

b. 保证 maxAllowedContentLength 在requestFiltering部分配置下正确设置,默认值为30MB,最大值4GB

ASP.NET  Web API 应用教程(一) ——数据流使用

一旦完成预先配置,那么创建数据流服务就非常简单了,首先 需要定义文件流“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

上一篇:2016 alictf Timer writeup


下一篇:终于解决了用JAVA写窗口程序在不同的windows界面下的显示保持一致。