asp.net zero 8.2 学习-12- abp 文件上传、获取、删除

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>

5. 测试

image
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350