1. 页面布局
我们先按照第二讲的方法 创建一个页面 创建出一个空页面。
先创建一个Controller,比如MetronicController,再创建一个Action方法:UploadFile,再给UploadFile方法添加一个视图,复制空页面的内容到该视图UploadFile.cshtml
接着写上传文件的form表单
整体视图如下:
@using EDU.SIS.Web.Areas.app.Startup
@using EDU.SIS.Authorization
@{
ViewBag.CurrentPageName = appPageNames.Common.MetronicUploadFile;
}
<!-- 文件上传 -->
<div class="kt-content kt-grid__item kt-grid__item--fluid kt-grid kt-grid--hor">
<div class="kt-subheader kt-grid__item">
<div class="@(await GetContainerClass())">
<!--标题和副标题start-->
<div class="kt-subheader__main">
<h3 class="kt-subheader__title">
<span>文件上传</span>
</h3>
</div>
<!--标题和副标题end-->
</div>
</div>
<div class="@(await GetContainerClass()) kt-grid__item kt-grid__item--fluid">
<div class="kt-portlet kt-portlet--mobile">
<div class="kt-portlet__body">
<form class="kt-form kt-form--center kt-form--label-right" id="kt_form_upload">
<div class="kt-portlet__body">
<div class="form-group kt-form__group row">
<label class="col-form-label col-lg-3 col-sm-12">附件上传</label>
<div class="col-lg-4 col-md-9 col-sm-12">
<div class="input-group">
<input id="txt_uploadFileId" type="hidden" />
<input id="txt_fileName" type="text" class="form-control kt-input" name="fileName" readonly autocomplete="off" placeholder="附件名称">
<div class="input-group-append">
<span class="btn btn-primary fileinput-button">
<i class="glyphicon glyphicon-plus"></i>
<span>选择文件</span>
<input id="fileupload" type="file" name="files" accept="image/*">
</span>
</div>
</div>
<span class="kt-form__help">上传进度</span>
<div id="progress" class="progress">
<div class="progress-bar progress-bar-success"></div>
</div>
</div>
</div>
<div class="form-group kt-form__group row">
<label class="col-form-label col-lg-3 col-sm-12">获取附件</label>
<div class="col-lg-4 col-md-9 col-sm-12">
<div class="input-group">
<input id="txt_enclosureId" type="text" class="form-control kt-input" name="enclosureId" required autocomplete="off" placeholder="请输入附件ID">
<div class="input-group-append">
<button id="btn_get_enclosure" class="btn btn-primary" type="button">附件详情</button>
</div>
</div>
<span class="kt-form__help">附件详情将在控制台输出</span>
</div>
</div>
<div class="form-group kt-form__group row">
<label class="col-form-label col-lg-3 col-sm-12">删除附件</label>
<div class="col-lg-4 col-md-9 col-sm-12">
<div class="input-group">
<input id="txt_fileToDeleteId" type="text" class="form-control kt-input" name="fileToDeleteId" required autocomplete="off" placeholder="请输入附件ID">
<div class="input-group-append">
<button id="btn_delete_file" class="btn btn-primary" type="button">附件详情</button>
</div>
</div>
<span class="kt-form__help">删除附件</span>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
@section Scripts{
<script src="~/view-resources/Areas/app/Views/Metronic/Index.js"></script>
<script>
</script>
}
2. 前端js
在页面的Index.js文件种编写javascript脚本,这里使用了一个前端上传文件的jquery插件:jquery.fileupload.js,这个插件是在视图布局文件中已经绑定了压缩打包版app-layout-libs,不用再单独引用。
Index.js源码如下:
(function () {
$(function () {
//--------------------------------- 系统附件上传 ---------------------------------//
var url = abp.appPath + 'app/Metronic/UploadFilePost';
$('#fileupload').fileupload({
url: url, //后台上传服务地址
dataType: 'json',
add: function (e, data) { //选择文件后处理方法
var files = data.originalFiles;
var isCheckSuccess = true;
if (files && files.length > 0) {
$(files).each(function (i, obj) {
//文件上传大小:10MB
var _maxFileSize = 1024 * 1024 * 10;
if (obj.size > _maxFileSize) {
isCheckSuccess = false;
abp.message.error("文件大小不能超过10MB");
return;
}
//判断文件类型
var acceptFileTypes = /^gif|jpe?g|png|bmp$/i;
var name = data.originalFiles[0]["name"];
var index = name.lastIndexOf(".") + 1;
var fileType = name.substring(index, name.length);
if (!acceptFileTypes.test(fileType)) {
isCheckSuccess = false;
abp.message.error("只允许上传图片格式文件");
return;
}
});
}
//校验成功后才提交上传数据
if (isCheckSuccess) {
//上传按钮禁用状态
$('#fileupload').attr("disabled", "disabled");
$('.fileinput-button').addClass("disabled");
//提交上传数据
data.submit();
}
},
done: function (e, response) { //上传完成后结果返回处理
//解除上传按钮禁用状态
$('#fileupload').removeAttr("disabled");
$('.fileinput-button').removeClass("disabled");
var jsonResult = response.result;
//判断上传状态
if (jsonResult.success) {
var fileUrl = abp.appPath + 'app/Metronic/GetFile?id=' + jsonResult.result.id + '&contentType=' + jsonResult.result.contentType; //注意contentType首字母要小写
var uploadedFile = '<a href="' + fileUrl + '" target="_blank">' + app.localize('UploadedFile') + '</a><br/><br/>' + ' 文件名称: ' + jsonResult.result.defaultFileName;
//赋值附件名称
$('#txt_fileName').val(jsonResult.result.defaultFileName);
//赋值隐藏域上传附件ID
$('#txt_uploadFileId').val(jsonResult.result.id);
//赋值获取附件详情文本框
$('#txt_enclosureId').val(jsonResult.result.id);
//弹出成功提示框
abp.message.success(jsonResult.result.defaultFileName, app.localize('PostedData'), true);
//弹出成功通知
abp.notify.success(app.localize('SavedSuccessfully'));
} else {
abp.message.error(jsonResult.error.message);
}
},
progressall: function (e, data) { //上传进度处理
var progress = parseInt(data.loaded / data.total * 100, 10);
$('#progress .progress-bar').css(
'width',
progress + '%'
);
}
});
//获取附件上传表单对象
var _$formUpload = $("#kt_form_upload");
//启用表单验证
_$formUpload.validate();
//获取详情按钮点击事件
$('#btn_get_enclosure').on('click', function () {
//校验附件ID输入
var _validStatus = $('#txt_enclosureId').valid();
//判断校验结果
if (_validStatus) {
var url = abp.appPath + 'app/Metronic/GetFileDetail?id=' + $('#txt_enclosureId').val();
$.get(url, function (data) {
console.log(data);
abp.notify.success("获取数据详情成功,请前往控制台查看。");
});
}
});
//删除附件
$('#btn_delete_file').on('click', function () {
//校验附件ID输入
var _validStatus = $('#txt_fileToDeleteId').valid();
if (_validStatus) {
var url = abp.appPath + 'app/Metronic/DeleteFile?id=' + $('#txt_fileToDeleteId').val();
$.get(url, function (data) {
if (data.success) {
abp.notify.success("删除文件成功");
} else {
//console.log(data);
abp.notify.info("删除文件失败:"+data.error.message);
}
})
}
});
});
})();
3. 后端代码
后端与上传文件相关的代码包括领域实体层的BinaryObject,这里对其扩展,添加了文件类型、大小等相关字段:
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Abp;
using Abp.Domain.Entities;
namespace EDU.SIS.Storage
{
/// <summary>
/// 附件实体
/// </summary>
[Table("AppBinaryObjects")]
public class BinaryObject : Entity<Guid>, IMayHaveTenant
{
/// <summary>
/// 租户ID
/// </summary>
public virtual int? TenantId { get; set; }
/// <summary>
/// 文件类型【拓展字段】
/// </summary>
public virtual string ContentType { get; set; }
/// <summary>
/// 文件名称【拓展字段】
/// </summary>
public virtual string FileName { get; set; }
/// <summary>
/// 文件大小【拓展字段】
/// </summary>
public virtual long FileSize { get; set; }
/// <summary>
/// 二进制数据
/// </summary>
[Required]
public virtual byte[] Bytes { get; set; }
public BinaryObject()
{
Id = SequentialGuidGenerator.Instance.Create();
}
public BinaryObject(int? tenantId, byte[] bytes)
: this()
{
TenantId = tenantId;
Bytes = bytes;
}
}
}
还包括IBinaryObjectManager、DbBinaryObjectManager实现文件上传的领域服务,未修改。
接下来就是在Controller中编写上传文件处理、获取文件详情等方法:
/// <summary>
/// 上传文件界面
/// </summary>
/// <returns></returns>
[HttpGet]
public IActionResult UploadFile()
{
return View();
}
/// <summary>
/// 文件上传
/// </summary>
/// <returns></returns>
[HttpPost]
public async Task<JsonResult> UploadFilePost()
{
try
{
//获取上传对象
var file = Request.Form.Files.First();
//判断是否选择文件
if (file == null)
{
throw new UserFriendlyException(L("File_Empty_Error"));
}
//判断文件大小(单位:字节)
if (file.Length > 10485760) //10MB = 1024 * 1024 *10
{
throw new UserFriendlyException(L("File_SizeLimit_Error"));
}
//将文件流转为二进制数据
byte[] fileBytes;
using (var stream = file.OpenReadStream())
{
fileBytes = stream.GetAllBytes();
}
//创建附件对象
var fileObject = new BinaryObject()
{
TenantId = AbpSession.TenantId,
Bytes = fileBytes,
ContentType = file.ContentType,
FileName = file.FileName,
FileSize = file.Length
};
//上传文件存储路径
string destPath = _webHostEnvironment.WebRootPath + "\uploads\";
if (!Directory.Exists(destPath))
{
Directory.CreateDirectory(destPath);
}
//生成随机文件名
var fileExtension = Path.GetExtension(file.FileName).ToLowerInvariant();
string fileName = fileObject.Id + fileExtension;//需要查找没有扩展名的文件??
string filePath = Path.Combine(destPath, fileName);
//存放文件到本地
using (FileStream fs = System.IO.File.Create(filePath))
{
file.CopyTo(fs);
fs.Flush();
}
//附件对象保存到数据库
await _binaryObjectManager.SaveAsync(fileObject);
//返回给前端上传结果
return Json(new AjaxResponse(new
{
id = fileObject.Id,
contentType = file.ContentType,
defaultFileName = file.FileName
}));
}
catch (UserFriendlyException ex)
{
return Json(new AjaxResponse(new ErrorInfo(ex.Message)));
}
catch(Exception ex)
{
return Json(new AjaxResponse(new ErrorInfo(ex.Message)));
}
}
/// <summary>
/// 删除文件
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public async Task<IActionResult> DeleteFile(string id)
{
try
{
var fileId = new Guid(id);
var fileToDelete = await _binaryObjectManager.GetOrNullAsync(fileId);
if (fileToDelete != null)
{
//string filePath = _webHostEnvironment.WebRootPath + "\uploads\"+ fileToDelete.FileName;
await _binaryObjectManager.DeleteAsync(fileId);
return Json(new AjaxResponse(true));
}
else
{
return Json(new AjaxResponse(new ErrorInfo
{
Message = "文件不存在或删除文件失败"
}));
}
}
catch (Exception)
{
return Json(new AjaxResponse(new ErrorInfo
{
Message = "文件ID无效"
}));
}
}
/// <summary>
/// 获取附件
/// </summary>
/// <param name="id">附件ID</param>
/// <param name="contentType">附件类型</param>
/// <returns></returns>
public async Task<IActionResult> GetFile(Guid id, string contentType)
{
var fileObject = await _binaryObjectManager.GetOrNullAsync(id);
if (fileObject == null)
{
return StatusCode((int)HttpStatusCode.NotFound);
}
return File(fileObject.Bytes, contentType);
}
/// <summary>
/// 获取附件详情
/// </summary>
/// <param name="id">附件ID</param>
/// <returns></returns>
[HttpGet]
public async Task<IActionResult> GetFileDetail(Guid id)
{
var fileObject = await _binaryObjectManager.GetOrNullAsync(id);
if (fileObject == null)
{
return StatusCode((int)HttpStatusCode.NotFound);
}
return Json(new AjaxResponse(new
{
id = fileObject.Id,
fileName = fileObject.FileName,
contentType = fileObject.ContentType,
fileSize = fileObject.FileSize,
fileSizeFormat = FormatFileSize(fileObject.FileSize),
bytes = fileObject.Bytes,
tenantId = fileObject.TenantId,
downloadUrl = string.Format("{0}app/Metronic/GetFile?id={1}&contentType={2}", _appConfiguration["App:WebSiteRootAddress"], fileObject.Id, fileObject.ContentType)
}));
}
这里abp官方上传的文件都是存放在数据库中,对于存放大的文件很不科学。可以修改代码存放到本地,也可以参考Magicodes.Storage这个开源库,实现本地存储或者云端OSS存储。
4. 系统文件上传大小限制
系统文件上传大小限制可以在代码中实现,也可以通过配置实现,但是最大不会超过配置内规定的大小,在MVC项目的Web.config中修改最大上传大小限制
...
<security>
<requestFiltering>
<!-- 文件上传大小限制:500M(默认值:30000000字节(28.6 MB),最大值:4GB) -->
<requestLimits maxAllowedContentLength="524288000" />
</requestFiltering>
</security>
</system.webServer>
<system.web>
<!-- 文件上传大小为:500M (默认为:4M,最大值:2TB),上传超时时间为:120秒(默认值:90秒) -->
<httpRuntime maxRequestLength="512000" executionTimeout="120" />
</system.web>