1 以数据流形式渲染显示头像图片
1.1 Core. MimeTypes
namespace Core
{
///
/// 【Mime类型--类】
///
/// 摘要:
/// 媒体类型(通常称为 Multipurpose Internet Mail Extensions 或 MIME 类型)是一种标准,用来表示文档、文件或字节流的性质和格式。
/// 它在IETF RFC 6838中进行了定义和标准化。
/// 警告/说明:
/// 1、浏览器通常使用 MIME 类型(而不是文件扩展名)来确定如何处理 URL,因此 Web 服务器在响应头中添加正确的 MIME 类型非常重要。
/// 如果配置不正确,浏览器可能会曲解文件内容,网站将无法正常工作,并且下载的文件也会被错误处理。
/// 2、 MIME类型主要是为浏览器通常以静态文件的形式渲染显示指定类型的持久化文件提供数据支撑,
/// 如果先把指定类型的持久化文件转换为数据流再在浏览器渲染显示时,则不用,但是在持久化文件转换为数据流操作时必须为其提供相应的MIME 类型作为数据支撑。
///
///
public static class MimeTypes
{
#region 应用
///
/// 【应用程序强制下载】
///
/// 摘要:
/// 客户端浏览器头字典实例中设定指定静态文件为下载文件。
///
///
public static string ApplicationForceDownload => "application/force-download";
///
/// 【应用程序Json】
///
/// 摘要:
/// 客户端浏览器头字典实例中设定服务器端发送的指定文件为以Json编码格式进行持久化存储的静态文件,同时客户端浏览器也会以Json编码格式渲染显示该静态文件。
///
///
public static string ApplicationJson => "application/json";
///
/// 【应用程序清单】
///
/// 摘要:
/// 客户端浏览器头字典实例中设定服务器端发送的指定文件为以Json/XML编码格式进行持久化存储的静态文件,同时客户端浏览器也会以Json/XML编码格式渲染显示该静态文件。
///
public static string ApplicationManifestJson => "application/manifest+json";
///
/// 【应用程序八位字节流】
///
/// 摘要:
/// 客户端浏览器向服务器端上传持久化存储静态文件时,只能以二进制数据流(或者字节数组)进行上传,且以此标准一次只能向服务器端上传一个静态文件,服务器端的接收参数也只能有一个。
///
///
public static string ApplicationOctetStream => "application/octet-stream";
///
/// 【应用程序PDF】
///
/// 摘要:
/// 客户端浏览器头字典实例中设定服务器端发送的指定文件为以PDF编码格式进行持久化存储的静态文件,同时客户端浏览器也会以PDF编码格式渲染显示该静态文件。
///
///
public static string ApplicationPdf => "application/pdf";
///
/// 【应用程序RSSXML】
///
/// 摘要:
/// 1、据说搜索引擎也会根据这个标签自动去爬行博客的RSS;
/// 2、让 Firefox、IE7 或其它 Feed 机器人自动发现网站的RSS视图;
///
///
public static string ApplicationRssXml => "application/rss+xml";
///
/// 【应用程序XML】
///
/// 摘要:
/// 客户端浏览器头字典实例中设定服务器端发送的指定文件为以XML编码格式进行持久化存储的静态文件,同时客户端浏览器也会以XML编码格式渲染显示该静态文件。
///
public static string ApplicationXml => "application/xml";
///
/// 【窗体应用程序】
///
/// 摘要:
/// 客户端浏览器以键/值对的形式把地址栏中的参数值发送到服务器端的指定的控制器行为方法。
/// 说明:
/// Postman以此标准把键/值对发送到服务器端的指定的控制器行为方法。
///
///
public static string ApplicationXWwwFormUrlencoded => "application/x-www-form-urlencoded";
///
/// 【应用程序Zip】
///
/// 摘要:
/// 客户端浏览器头字典实例中设定服务器端发送的指定文件为静态压缩文件,同时客户端浏览器也会以此标准打开静态压缩文件。
///
///
public static string ApplicationZip => "application/zip";
///
/// 【应用程序Zip扩展】
///
/// 摘要:
/// 客户端浏览器头字典实例中设定服务器端发送的指定文件为静态压缩文件,同时客户端浏览器也会以此标准打开静态压缩文件。
///
///
public static string ApplicationXZipCo => "application/x-zip-co";
#endregion
#region 图片
///
/// 【Bmp图片】
///
/// 摘要:
/// 客户端浏览器头字典实例中设定服务器端发送的指定文件以“bmp”为扩展名的静态图片文件,同时客户端浏览器也会以此标准打开该静态图片文件。
///
///
public static string ImageBmp => "image/bmp";
///
/// 【Gif图片】
///
/// 摘要:
/// 客户端浏览器头字典实例中设定服务器端发送的指定文件以“Gif”为扩展名的静态图片文件,同时客户端浏览器也会以此标准打开该静态图片文件。
///
///
public static string ImageGif => "image/gif";
///
/// 【Jpeg图片】
///
/// 摘要:
/// 客户端浏览器头字典实例中设定服务器端发送的指定文件以“jpe/jpeg/jpg/jfif/pjpeg/pjp”为扩展名的静态图片文件,同时客户端浏览器也会以此标准打开该静态图片文件。
///
///
public static string ImageJpeg => "image/jpeg";
///
/// 【pjpeg图片】
///
/// 摘要:
/// 客户端浏览器头字典实例中设定服务器端发送的指定文件以“jpe/jpeg/jpg/jfif/pjpeg/pjp”为扩展名的静态图片文件,同时客户端浏览器也会以此标准打开该静态图片文件。
///
///
public static string ImagePJpeg => "image/pjpeg";
///
/// 【Png图片】
///
/// 摘要:
/// 客户端浏览器头字典实例中设定服务器端发送的指定文件以“Png”为扩展名的静态图片文件,同时客户端浏览器也会以此标准打开该静态图片文件。
///
///
public static string ImagePng => "image/png";
///
/// 【Tiff图片】
///
/// 摘要:
/// 客户端浏览器头字典实例中设定服务器端发送的指定文件以“tiff”为扩展名的静态图片文件,同时客户端浏览器也会以此标准打开该静态图片文件。
///
///
public static string ImageTiff => "image/tiff";
///
/// 【Webp图片】
///
/// 摘要:
/// 客户端浏览器头字典实例中设定服务器端发送的指定文件以“webp”为扩展名的静态图片文件,同时客户端浏览器也会以此标准打开该静态图片文件。
///
///
public static string ImageWebp => "image/webp";
///
/// 【Svg图片】
///
/// 摘要:
/// 客户端浏览器头字典实例中设定服务器端发送的指定文件以“svg”为扩展名的静态图片文件,同时客户端浏览器也会以此标准打开该静态图片文件。
///
///
public static string ImageSvg => "image/svg+xml";
#endregion
#region 文本
///
/// 【CSS文本】
///
/// 摘要:
/// 客户端浏览器头字典实例中设定服务器端发送的指定文件为以CSS编码格式进行持久化存储的静态文本文件,同时客户端浏览器也会以CSS编码格式渲染显示该静态文本文件。
///
///
public static string TextCss => "text/css";
///
/// 【】
///
/// 摘要:
/// 客户端浏览器头字典实例中设定服务器端发送的指定文件为以Csv码格式进行持久化存储的静态文本文件,同时客户端浏览器也会以Csv编码格式渲染显示该静态Csv文本文件。
///
///
public static string TextCsv => "text/csv";
///
/// 【Javascript文本】
///
/// 摘要:
/// 客户端浏览器头字典实例中设定服务器端发送的指定文件为以Javascript编码格式进行持久化存储的静态文本文件,同时客户端浏览器也会以Javascript编码格式渲染显示该静态Javascript文本文件。
///
///
public static string TextJavascript => "text/javascript";
///
/// 【纯文本(text)】
///
/// 摘要:
/// 客户端浏览器头字典实例中设定服务器端发送的指定文件为以纯文本(text)编码格式进行持久化存储的静态文本文件,同时客户端浏览器也会以纯文本(text)编码格式渲染显示该静态纯文本(text)文件。
///
///
public static string TextPlain => "text/plain";
///
/// 【Excel文本】
///
/// 摘要:
/// 客户端浏览器头字典实例中设定服务器端发送的指定文件为以Excel编码格式进行持久化存储的静态文本文件,同时客户端浏览器也会以Excel编码格式渲染显示该静态Excel文本文件。
///
///
public static string TextXlsx => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
#endregion
}
}
1.2 WebApi.Controllers.CustomerController. GetPictureContentTypeByFileExtension
/// name="fileExtension">1个指定图片的扩展名。
///
/// 【通过扩展名获取图片类型】
///
///
/// 摘要:
/// 通过1个指定图片的扩展名获取1个指定图片的类型。
///
///
/// 返回:
/// 1个指定图片的类型。
///
private string GetPictureContentTypeByFileExtension(string fileExtension)
{
string contentType = null;
switch (fileExtension.ToLower())
{
case ".bmp":
contentType = MimeTypes.ImageBmp;
break;
case ".gif":
contentType = MimeTypes.ImageGif;
break;
case ".jpeg":
case ".jpg":
case ".jpe":
case ".jfif":
case ".pjpeg":
case ".pjp":
contentType = MimeTypes.ImageJpeg;
break;
case ".webp":
contentType = MimeTypes.ImageWebp;
break;
case ".png":
contentType = MimeTypes.ImagePng;
break;
case ".svg":
contentType = MimeTypes.ImageSvg;
break;
case ".tiff":
case ".tif":
contentType = MimeTypes.ImageTiff;
break;
default:
break;
}
return contentType;
}
1.3 WebApi.Controllers.CustomerController. GetAvatarStream
/// name="customerId">1个指定的长整型值。
///
/// 【以数据流形式渲染显示头像图片--无需权限】
///
///
/// 摘要:
/// 获取1个指定用户头像图片的数据流,为前端头像图片的渲染显示提供数据支撑。
///
///
/// 返回:
/// 1个指定用户头像图片的数据流,为前端头像图片的渲染显示提供数据支撑。
///
[HttpGet]
public async Task<IActionResult> GetAvatarStream(long customerId)
{
Customer _customer = await _customerService.GetCustomerByIdAsync(customerId);
string _avatarPath = string.Empty;
if (_customer != null)
{
if (string.IsNullOrEmpty(_customer.Avatar))
{
_avatarPath = _nopFileProvider.Combine(_nopFileProvider.WebRootPath, @"\images\Avatar\Default.jpg");
}
else
{
//去除网络格式路径字符中的第一个字符“~/”。
_customer.Avatar = _customer.Avatar.Replace("~/", string.Empty).TrimStart('/');
//去除网络格式路径字符中的最后一个字符“/”。
var pathEnd = _customer.Avatar.EndsWith('/') ? Path.DirectorySeparatorChar.ToString() : string.Empty;
//通过拼接操作,拼接出与之相对应的1个本地格式的路径字符串。
_avatarPath = _nopFileProvider.Combine(_nopFileProvider.WebRootPath ?? string.Empty, _customer.Avatar) + pathEnd;
}
}
string _avatarType = GetPictureContentTypeByFileExtension(_nopFileProvider.GetFileExtension(_avatarPath));
var imageFileStream = System.IO.File.OpenRead(_avatarPath);
return File(imageFileStream, _avatarType);
}
2 Swagger通过IFormFile或IFormCollection参数实例实现文件上传
2.1 415异常:
Swagger/index.html在默认情况下是可以通过IFormFile或IFormCollection参数实例向服务器端上传控制器行为方法发送参数实例的实现文件上传功能的,但是由于Framework.Infrastructure.Middleware.CorsExceptionHandlerMiddleware中间件定义会出现415异常,如下图上所示:
2.2 解决方案:重构Framework.Infrastructure.Middleware.CorsExceptionHandlerMiddleware
using Microsoft.AspNetCore.Cors.Infrastructure;
using Microsoft.AspNetCore.Http;
namespace Framework.Infrastructure.Middleware
{
///
/// 【跨域异常处理中间件--类】
///
/// 摘要:
/// 该管道中间件类主要为了集中解决在由vue/uni-app前端项目跨域(Cors)访问当前后端项目时,浏览器或App中出现的异常:
/// 1、“has been blocked by CORS policy: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.”。
/// 2、“has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.”。
/// 3、“has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.”。
/// 说明:
/// vue/uni-app前端项目跨域(Cors)访问当前后端项目时,浏览器或App中出现的异常,可以直接在 vue/uni-app前端项目中解决;但本人更习惯通过后端定义来集中解决这些异常。
///
///
public class CorsExceptionHandlerMiddleware
{
#region 拷贝构造方法与变量
///
/// 【下1个】
///
/// 摘要:
/// .Net(Core)框架内置管道中的下1个管道中间件实例。
///
///
private readonly RequestDelegate _next;
/// name="next">.Net(Core)框架内置管道中的下1个管道中间件实例。
///
/// 【拷贝构造方法】
///
/// 摘要:
/// 通过该构造方法中的参数实例,实例化.Net(Core)框架内置管道中的下1个管道中间件实例。
///
///
public CorsExceptionHandlerMiddleware(RequestDelegate next)
{
_next = next;
}
#endregion
#region 方法
/// name="context">HTTP上下文实例。
///
/// 【异步调用】
///
/// 摘要:
/// 通过该方法向.Net(Core)框架内置管道中集成当前管道中间件,集中解决在由vue/uni-app前端项目跨域(Cors)访问当前后端项目时,浏览器或App中出现的异常:
/// 1、“has been blocked by CORS policy: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.”。
/// 2、“has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.”。
/// 3、“has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.”。
///
///
public async Task InvokeAsync(HttpContext context)
{
//解决在由Hbuilder创建的前端Xuni-app项目(Cors)访问当前后端项目时,浏览器或App中会出现异常:
//“has been blocked by CORS policy: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.”。
if (!context.Response.Headers.ContainsKey("Access-Control-Allow-Headers"))
{
context.Response.Headers.Add("Access-Control-Allow-Headers", "DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization");
}
if (!context.Response.Headers.ContainsKey("Access-Control-Allow-Methods"))
{
context.Response.Headers.Add("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,PATCH,OPTIONS");
}
//解决在由Hbuilder创建的前端Xuni-app项目(Cors)访问当前后端项目时,浏览器或App中会出现异常:
//“has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.”。
if (!context.Response.Headers.ContainsKey("Access-Control-Allow-Origin"))
{
context.Response.Headers.Add("Access-Control-Allow-Origin", "*");
}
if (context.Request.Headers.ContainsKey(CorsConstants.Origin))
{
//解决在前端通过“axios.post”方式调用后端POST-API有,如果前端“axios.post”方法没有加载“headers”参数实例,下1行语句中的配置,否则“axios.post”方法,访问后端的POST-API,否则会出现:"HTTP:415"错误。
//context.Request.ContentType = "application/json";
if (!context.Request.ContentType.Contains("multipart/form-data"))
context.Request.ContentType = "application/json";
//解决在由Hbuilder创建的前端Xuni-app项目(Cors)访问当前后端项目时,浏览器或App中会出现异常:
//“' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.”。
if (context.Request.Method.Equals("OPTIONS"))
{
context.Response.StatusCode = StatusCodes.Status200OK;
return;
}
}
await _next(context);
}
#endregion
}
}
2.3 WebApi.Controllers. CustomerController. PostAvatarStream
/// name="customerId">1个指定的长整型值。
/// name="formFile">1个指定的上传文件的实例。
///
/// 【上传单个文件--无需权限】
///
///
/// 摘要:
/// 把1个指定的上传文件从客户端上传到服务器端的指定目录中。
///
///
/// 返回:
/// 1个指定的上传文件上传操作后的状态信息。
///
[HttpPost]
public async Task<IActionResult> PostAvatarStream(long customerId, /*IFormCollection collection [FromForm]*/ IFormFile formFile)
{
Customer _customer = await _customerService.GetCustomerByIdAsync(customerId);
//string _avatarPath = string.Empty;
if (_customer != null && formFile != null)
{
if(!string.IsNullOrEmpty(_customer.Avatar)&&!_nopFileProvider.GetFileName(_customer.Avatar).Equals("Default.jpg"))
{
//去除网络格式路径字符中的第一个字符“~/”。
_customer.Avatar = _customer.Avatar.Replace("~/", string.Empty).TrimStart('/');
//去除网络格式路径字符中的最后一个字符“/”。
var pathEnd = _customer.Avatar.EndsWith('/') ? Path.DirectorySeparatorChar.ToString() : string.Empty;
//通过拼接操作,拼接出与之相对应的1个本地格式的路径字符串。
string _avatarPath = _nopFileProvider.Combine(_nopFileProvider.WebRootPath ?? string.Empty, _customer.Avatar) + pathEnd;
_nopFileProvider.DeleteFile(_avatarPath);
}
string _filename = customerId.ToString() + _nopFileProvider.GetFileExtension(formFile.FileName);
string _path = _nopFileProvider.Combine(_nopFileProvider.WebRootPath, @"\images\Avatar\", _filename);
using FileStream fileStream = new FileStream(_path, FileMode.Create);
await formFile.CopyToAsync(fileStream);
_customer.Avatar = "/images/Avatar/" + _filename;
await _customerService.UpdateCustomerAsync(_customer);
return Created(WebUtility.UrlEncode(_customer.Avatar), _customer);
}
return BadRequest();
}
对以上功能更为具体实现和注释见:230322_045shopDemo(Swagger通过IFormFile或IFormCollection参数实例实现文件上传)。