You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

269 lines
8.2 KiB

4 years ago
  1. using System;
  2. using System.IO;
  3. namespace Apewer.Web
  4. {
  5. /// <summary>Response 模型。</summary>
  6. public abstract class ApiModel
  7. {
  8. #region
  9. private int _expires = 0;
  10. internal ApiRequest _request;
  11. internal ApiResponse _response;
  12. internal ApiOptions _options;
  13. internal ApiProvider _provider;
  14. static int SafeExpires(int seconds)
  15. {
  16. var s = seconds;
  17. if (s < 0) s = 0;
  18. if (s > 2592000) s = 2592000;
  19. return s;
  20. }
  21. #endregion
  22. #region 内部属性和方法。
  23. /// <summary>处理当前模型的 API 请求。</summary>
  24. protected ApiRequest Request { get => _request; }
  25. /// <summary>处理当前模型的 API 响应。</summary>
  26. protected ApiResponse Response { get => Response; }
  27. /// <summary>处理当前模型的 API 选项。</summary>
  28. protected ApiOptions Options { get => _options; }
  29. /// <summary>处理当前模型的服务程序实例。</summary>
  30. protected ApiProvider Provider { get => _provider; }
  31. /// <summary>在 Response 头中添加用于设置文件名的属性。</summary>
  32. protected void SetAttachment()
  33. {
  34. if (Provider == null) return;
  35. var name = Attachment;
  36. if (string.IsNullOrEmpty(name)) return;
  37. var encoded = TextUtility.EncodeUrl(name);
  38. Provider.SetHeader("Content-Disposition", $"attachment; filename={encoded}");
  39. }
  40. /// <summary>以指定参数输出。</summary>
  41. protected void Output(byte[] bytes)
  42. {
  43. if (Provider == null) return;
  44. try
  45. {
  46. SetAttachment();
  47. Provider.SetCache(Expires);
  48. Provider.SetContentType(ContentType);
  49. var length = bytes == null ? 0 : bytes.Length;
  50. Provider.SetContentLength(length);
  51. if (length > 0) Provider.ResponseBody().Write(bytes);
  52. }
  53. catch { }
  54. }
  55. /// <summary>以指定参数输出。</summary>
  56. protected void Output(Stream stream, bool dispose)
  57. {
  58. if (Provider == null) return;
  59. try
  60. {
  61. SetAttachment();
  62. Provider.SetCache(Expires);
  63. Provider.SetContentType(ContentType);
  64. Provider.SetContentLength(stream.Length - stream.Position);
  65. Provider.ResponseBody().Write(stream);
  66. }
  67. catch { }
  68. if (dispose) RuntimeUtility.Dispose(stream);
  69. }
  70. #endregion
  71. /// <summary>内容类型。</summary>
  72. public virtual string ContentType { get; set; }
  73. /// <summary>响应缓存的过期时间,以秒为单位。</summary>
  74. public virtual int Expires { get => _expires; set => _expires = SafeExpires(value); }
  75. /// <summary>设置文件名,告知客户端此附件处理此响应。</summary>
  76. public virtual string Attachment { get; set; }
  77. /// <summary>执行输出。</summary>
  78. /// <remarks>此方法由 API 调用器发起调用,用户程序不应主动调用。</remarks>
  79. /// <exception cref="InvalidOperationException"></exception>
  80. public abstract void Output();
  81. /// <summary>创建对象实例,并设置默认属性。</summary>
  82. public ApiModel()
  83. {
  84. ContentType = "application/octet-stream";
  85. Expires = 0;
  86. Attachment = null;
  87. }
  88. }
  89. /// <summary>输出二进制的 Response 模型。</summary>
  90. public class ApiBytesModel : ApiModel
  91. {
  92. /// <summary>向 Body 写入的字节数组。</summary>
  93. public byte[] Bytes { get; set; }
  94. /// <summary>输出字节数组。</summary>
  95. public override void Output() => Output(Bytes);
  96. }
  97. /// <summary>输出二进制的 Response 模型。</summary>
  98. public class ApiStreamModel : ApiModel, IDisposable
  99. {
  100. /// <summary>将要读取的流,用于向 Body 写入。</summary>
  101. public Stream Stream { get; set; }
  102. /// <summary>执行输出后释放流。</summary>
  103. /// <remarks>默认值:TRUE。</remarks>
  104. public bool AutoDispose { get; set; }
  105. /// <summary>输出流。</summary>
  106. public override void Output() => Output(Stream, AutoDispose);
  107. /// <summary>当指定 AutoDispose 属性时释放流。</summary>
  108. public void Dispose()
  109. {
  110. if (AutoDispose) RuntimeUtility.Dispose(Stream);
  111. }
  112. /// <summary>创建对象实例,并设置默认属性。</summary>
  113. public ApiStreamModel() => AutoDispose = true;
  114. }
  115. /// <summary>输出二进制的 Response 模型。</summary>
  116. public class ApiFileModel : ApiModel
  117. {
  118. /// <summary>将要读取的文件所在路径,用于向 Body 写入。</summary>
  119. public string Path { get; set; }
  120. /// <summary>输出指定路径的文件。</summary>
  121. public override void Output()
  122. {
  123. try
  124. {
  125. if (!File.Exists(Path)) return;
  126. var info = new FileInfo(Path);
  127. if (string.IsNullOrEmpty(Attachment)) Attachment = info.Name;
  128. var stream = new FileStream(Path, FileMode.Open, FileAccess.Read, FileShare.Read);
  129. Output(stream, true);
  130. }
  131. catch { }
  132. }
  133. }
  134. /// <summary>输出文本的 Response 模型。</summary>
  135. public class ApiTextModel : ApiModel
  136. {
  137. /// <summary>自定义文本。</summary>
  138. public string Text { get; set; }
  139. /// <summary>输出文本。</summary>
  140. public override void Output() => Output(TextUtility.Bytes(Text));
  141. /// <summary>创建对象实例,并设置默认属性。</summary>
  142. public ApiTextModel() => ContentType = "text/plain; charset=utf-8";
  143. }
  144. /// <summary>输出文本的 Response 模型。</summary>
  145. public class ApiJsonModel : ApiModel
  146. {
  147. /// <summary>Json 对象。</summary>
  148. public Json Json { get; set; }
  149. /// <summary>缩进排版。</summary>
  150. /// <remarks>默认值:TRUE</remarks>
  151. public bool Indented { get; set; }
  152. /// <summary>转为属性名为驼峰形式。</summary>
  153. /// <remarks>默认值:FALSE</remarks>
  154. public bool Camel { get; set; }
  155. /// <summary>输出文本。</summary>
  156. public override void Output()
  157. {
  158. var json = (Json != null && Json.Available) ? Json : Json.NewObject();
  159. if (Camel) Json.Camel(json);
  160. Output(TextUtility.Bytes(json.ToString(Indented)));
  161. }
  162. /// <summary>创建对象实例,并设置默认属性。</summary>
  163. public ApiJsonModel()
  164. {
  165. ContentType = "text/plain; charset=utf-8";
  166. Indented = true;
  167. }
  168. }
  169. /// <summary>输出重定向的 Response 模型。</summary>
  170. public class ApiRedirectModel : ApiModel
  171. {
  172. /// <summary>将要重定向的位置。</summary>
  173. public string Location { get; set; }
  174. /// <summary>执行重定向。</summary>
  175. public override void Output()
  176. {
  177. var location = Location;
  178. if (string.IsNullOrEmpty(location)) return;
  179. if (Provider == null) return;
  180. Provider.SetRedirect(Location);
  181. }
  182. }
  183. /// <summary>输出带有指定 Status 的 Response 模型。</summary>
  184. public class ApiStatusModel : ApiModel
  185. {
  186. /// <summary>状态。</summary>
  187. /// <remarks>默认值:200。</remarks>
  188. public int Status { get; set; } = 200;
  189. /// <summary>向 Body 写入的字节数组。</summary>
  190. public byte[] Bytes { get; set; }
  191. /// <summary>执行重定向。</summary>
  192. public override void Output()
  193. {
  194. var status = Status > 0 ? Status : 200;
  195. Provider.SetStatus(status.ToString());
  196. Output(Bytes);
  197. }
  198. /// <summary></summary>
  199. public ApiStatusModel() { }
  200. /// <summary></summary>
  201. public ApiStatusModel(int status)
  202. {
  203. Status = status;
  204. }
  205. }
  206. }