1 重构Framework.Infrastructure.Middleware. CorsExceptionHandlerMiddleware. InvokeAsync
///<param name="context">HTTP上下文实例。</param>
/// <summary>
/// 【异步调用】
/// <remarks>
/// 摘要:
/// 通过该方法向.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.”。
/// </remarks>
/// </summary>
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";
//在使用“elmentUI”前端时“context.Request.ContentType”的实例值会为:null,所以必须包含:“context.Request.ContentType == null”。
if (context.Request.ContentType == null || !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);
}
2 重构WebApi.Controllers. CustomerController. PostAvatarStream
/// <param name="customerId">1个指定的长整型值。</param>
/// <param name="formFile">1个指定的上传文件的实例。</param>
/// <summary>
/// 【上传单个文件--无需权限】
/// </summary>
/// <remarks>
/// 摘要:
/// 把1个指定的上传文件从客户端上传到服务器端的指定目录中。
/// 说明(elmentUI Upload组件):
/// 1、如果使用头属性字典传递customerId参数实例,则不用使用“[FromForm]”标记,
/// URL为:this.actionRequestUrl = "https://localhost:7239/Customer/PostAvatarStream?customerId=" + this.formUser.id;
/// 2、如果使用“:data”传递customerId参数实例,则必须使用“[FromForm]”标记,否则customerId参数实例会一直为:0,
/// URL为:this.actionRequestUrl = "https://localhost:7239/Customer/PostAvatarStream"。
/// </remarks>
/// <returns>
/// 返回:
/// 1个指定的上传文件上传操作后的状态信息。
/// </returns>
[HttpPost]
public async Task<IActionResult> PostAvatarStream([FromForm] long customerId, /*IFormCollection collection [FromForm]*/ IFormFile formFile)
{
Customer _customer = await _customerService.GetCustomerByIdAsync(customerId);
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 = Guid.NewGuid().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();
}
3 \src\components\Users\ EditUser.vue
<template>
<el-dialog width="30%">
<template #header>
<div class="my-header">
<h1 style="margin: 0px; padding: 0px; ">
<el-icon style="margin-right:5px; font-size: 25px; color:#337ecc; vertical-align: middle">
<Edit />
</el-icon>
编辑用户
</h1>
</div>
</template>
<el-form :model="formUser" label-width="100px" class="demo-ruleForm" label-position="left" status-icon>
<!--
elmentUI Upload组件注意事项说明:
v-model:file-list:文件上传--需要或已经被上传的文件。
:action:必须被实例化的属性,文件上传--上传文件所需要调用的指定的后端控制器行为方法。
name:如果指定的后端控制器行为方法中使用IFormFile参数实例,则该属性是必须被实例化的属性,且属性实例化值与IFormFile参数名必须相同。
:limit="1"://同1次上传操作中,最大能够上传多文件的个数值。
:headers(注意):很多网上的示例把headers属性实例化为“mutipart/form-data,在此我重申一下,没有必要。elementUI已经封装加工过了,
同时如果设定headers属性实例,还会造成异常:"Failed to read the request form. Missing content-type boundary"
或异常:"body length limit 16384 exceeded"。
:before-upload:文件上传前验证文件是否符合所规则的上传规则。
-->
<el-upload v-model:file-list="fileList" class="upload-demo" list-type="picture" :action="actionRequestUrl"
name="formFile" :before-upload="beforeUpload" :on-change="onUploadChange" :data="paramData">
<el-button type="primary">点击上传</el-button>
</el-upload>
<el-form-item label="编号:" prop="id">
<el-input v-model="formUser.id" disabled />
</el-form-item>
<el-form-item label="名称:" prop="name">
<el-input v-model="formUser.name" />
</el-form-item>
<el-form-item label="邮箱:" prop="email">
<el-input v-model="formUser.email" />
</el-form-item>
<el-form-item label="手机号:" prop="phone">
<el-input v-model="formUser.phone" />
</el-form-item>
<el-form-item label="头像:" prop="avatar">
<el-input v-model="formUser.avatar" />
</el-form-item>
<!-- 注意:必须使用 :value="true",不能使用 value=true,否则会出现:
“ [Vue warn]: Invalid prop: type check failed for prop "modelValue". Expected String | Number | Object, got Boolean with value true. ”
警告信息 -->
<el-form-item label="系统帐户:" prop="isSystemAccount">
<el-select v-model="formUser.isSystemAccount" style="width: 100%;">
<el-option label="系统帐户" :value="true" />
<el-option label="其它" :value="false" />
</el-select>
</el-form-item>
<el-form-item label="激活:" prop="isActive">
<el-select v-model="formUser.isActive" style="width: 100%;">
<el-option label="激活" :value="true" />
<el-option label="禁用" :value="false" />
</el-select>
</el-form-item>
<el-form-item label="可用:" prop="deleted">
<el-select v-model="formUser.deleted" style="width: 100%;">
<el-option label="已被删除" :value="true" />
<el-option label="可用" :value="false" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="this.formUser.centerDialogVisible = false">取 消</el-button>
<el-button type="primary">提 交</el-button>
</span>
</template>
</el-dialog>
</template>
<script>
export default {
name: 'EditUser',
props: {
propParent: {},
},
data() {
return {
//用户编辑表单实例初始化。
formUser: {
id: 0,
name: '',
email: '',
phone: '',
avatar: '',
isSystemAccount: false,
isActive: true,
deleted: false,
//该实例用于指示,当前页面角色编辑子页面是否在父窗口中显示出来。
centerDialogVisible: false,
},
//文件上传--需要或已经被上传的文件。
fileList: [{
url: '',
}],
//文件上传--上传文件所需要调用的指定的后端控制器行为方法。
actionRequestUrl: '',
//文件上传--允许被上传文件的类型规则
fileType: ["bmp", "gif", "jpeg", "jpg", "jpe", "jfif", "pjpeg", "webp", "png", "svg", "tiff", "tif"],
//文件上传--允许被上传文件的最大大小值规则,单位:MB
fileSize: 10,
paramData: {
customerId: 0,
},
};
},
//监视父窗口传递的参数实例,使当前子页面中的表单始终显示父窗口最新传递的参数实例。
watch: {
propParent(val) {
//console.log(val);
this.formUser = val;
this.fileList[0].url = this.formUser.avatar;
//console.log(this.fileList[0].url);
},
},
methods: {
//上传文件之前,验证指定上传文件是否符合上传规则。
beforeUpload(file) {
if (file.type != "" || file.type != null || file.type != undefined) {
//清空已上传的文件列表
//this.$refs.upload.clearFiles();
//console.log(file);
//截取文件的后缀,判断文件类型
let fileExt = file.name.replace(/.+\./, "").toLowerCase();
//计算指定上传文件大小是否大于10MB。
let isMaxFileSize = file.size / 1024 / 1024 <= 10;
//如果大于i大于10MB
if (!isMaxFileSize) {
this.$message.error("上传文件大小不能超过 10MB!");
return false;
}
//如果文件类型不在允许上传的范围内
if (this.fileType.includes(fileExt)) {
return true;
} else {
this.$message.error("上传文件格式不正确!");
return false;
}
//this.fileList[0].url = file.url;
}
},
//头像图片文件发生变更时,实现头像图片文件的上传操作。
//注意:该方法必须包含“file”参数。
async onUploadChange(file,fileList) {
/* console.log("需要或已经被上传的文件列表:", fileList);
console.log("1个指定的需要被上传的文件:", file);
console.log("1个指定的需要被上传文件的大小:", file.size);
console.log("1个指定的需要被上传文件的文件名:", file.name);
console.log("1个指定的需要被上传文件的完整信息(作为传递参数的实例值):", file.raw);
console.log("1个指定的需要被上传文件的URL:", file.url);
console.log("上传操作后所返回的成功/失败的数据信息:", file.response);
console.log("1个指定用户的长整型编号值:", this.formUser.id); */
this.paramData.customerId = this.formUser.id;
this.actionRequestUrl = "";
//this.actionRequestUrl = "https://localhost:7239/Customer/PostAvatarStream?customerId=" + this.formUser.id;
this.actionRequestUrl = " https://localhost:7239/Customer/PostAvatarStream";
//需要或已经被上传的文件列表中只渲染显示最新的1个上传文件。
this.fileList = fileList.slice(-1);
//console.log("渲染显示的文件列表:", this.fileList);
},
},
async mounted() {
},
};
</script>
<style scoped lang="scss">
.my-header {
display: flex;
flex-direction: row;
justify-content: space-between;
}
</style>
对以上功能更为具体实现和注释见:230406_015shopvue(elmentUI Upload组件通过IFormFile参数上传注意事项)。