主頁 > .NET開發 > ASP.NET CORE使用WebUploader對大檔案分片上傳,并通過ASP.NET CORE SignalR實時反饋后臺處理進度給前端展示

ASP.NET CORE使用WebUploader對大檔案分片上傳,并通過ASP.NET CORE SignalR實時反饋后臺處理進度給前端展示

2021-04-26 06:05:43 .NET開發

本次,我們來實作一個單個大檔案上傳,并且把后臺對上傳檔案的處理進度通過ASP.NET CORE SignalR反饋給前端展示,比如上傳一個大的zip壓縮包檔案,后臺進行解壓縮,并且對壓縮包中的檔案進行md5校驗,同時要求前臺可以實時(實際情況看網路情況)展示后臺對壓縮包的處理進度(解壓、校驗檔案),

在前端上傳檔案的組件選擇上,采用了WebUploader(http://fex.baidu.com/webuploader/)這個優秀的前端組件,下面是來自它的官網介紹:

WebUploader是由Baidu WebFE(FEX)團隊開發的一個簡單的以HTML5為主,FLASH為輔的現代檔案上傳組件,在現代的瀏覽器里面能充分發揮HTML5的優勢,同時又不摒棄主流IE瀏覽器,沿用原來的FLASH運行時,兼容IE6+,iOS 6+, android 4+,兩套運行時,同樣的呼叫方式,可供用戶任意選用,

采用大檔案分片并發上傳,極大的提高了檔案上傳效率,

WebUploader的功能很多,本次只使用它的上傳前檔案MD5校驗并發分片上傳分片MD5校驗三個主要功能,分別來實作類似網盤中的檔案【秒傳】,瀏覽器多執行緒上傳檔案和檔案的斷點續傳

閱讀參考此文章前,請先看一下https://www.cnblogs.com/wdw984/p/14645614.html

此文章是上一篇的功能擴展,一些基本的程式模塊邏輯都已經在上一篇文章中做了介紹,這里就不再重復,

在正式使用WebUploader進行上傳檔案之前,先對它的執行流程和觸發的事件做個大致的介紹(如有不對的地方請指正),我們可以通過它觸發的事件來做相應的流程或業務上的預處理,比如檔案秒傳,重復檔案檢測等,

當WebUploader正確加載完成后,會觸發它的ready事件;

當點擊檔案選擇框的時候(其它方式傳入檔案所觸發的事件請參考官方檔案),會觸發它的dialogOpen事件;

當選擇檔案完成后,觸發事件的流程為:beforeFileQueued ==> fileQueued ==> filesQueued;

當點擊(開始)上傳的時候,觸發事件的流程為:

1、正常檔案上傳流程

startUpload(如秒傳(后臺通過檔案的md5判斷回傳)秒傳則觸發UploadSkip) ==> uploadStart ==> uploadBeforeSend ==> uploadProgress ==> uploadAccept(接收服務器處理分塊傳輸后的回傳資訊) ==> uploadSuccess ==> uploadComplete ==> uploadFinished

2、檔案秒傳或續傳流程

startUpload ==> uploadStart(觸發秒傳或檔案續傳) ==> uploadSkip ==> uploadSuccess ==> uploadComplete ==> uploadFinished

現在,我們在上一次專案的基礎上做一些改造升級,最終實作我們本次的功能,

先看效果(GIF錄制時間略長,請耐心等待一下)

首先,我們參考大名鼎鼎的WebUploader組件庫,在專案上右鍵==>添加==>客戶端庫 的界面中選擇unpkg然后輸入webuploader 

為了實作壓縮檔案的解壓縮操作,我們在Nuget中參考SharpZipLib組件

 然后我們在appsettings.json中增加一個配置用來保存上傳檔案,

 1 {
 2   "Logging": {
 3     "LogLevel": {
 4       "Default": "Information",
 5       "Microsoft": "Warning",
 6       "Microsoft.Hosting.Lifetime": "Information"
 7     }
 8   },
 9   "FileUpload": {
10     "TempPath": "temp",//臨時檔案保存目錄
11     "FileDir": "upload",//上傳完成后的保存目錄
12     "FileExt": "zip,rar"//允許上傳的檔案型別
13   },
14   "AllowedHosts": "*"
15 }

在專案中新建一個Model目錄,用來實作上傳檔案的相關配置,建立相應的多個類檔案 

FileUploadConfig.cs 服務器用來接受和保存檔案的配置

 1 using System;
 2 
 3 namespace signalr.Model
 4 {
 5     /// <summary>
 6     /// 上傳檔案配置類
 7     /// </summary>
 8     [Serializable]
 9     public class FileUploadConfig
10     {
11         /// <summary>
12         /// 臨時檔案夾目錄名
13         /// </summary>
14         public string TempPath { get; set; }
15         /// <summary>
16         /// 上傳檔案保存目錄名
17         /// </summary>
18         public string FileDir { get; set; }
19         /// <summary>
20         /// 允許上傳的檔案擴展名
21         /// </summary>
22         public string FileExt { get; set; }
23     }
24 }

UploadFileWholeModel.cs 前臺開始傳輸前會對檔案進行一次MD5演算法,這里可以通過檔案MD5值傳遞給后臺來通過比對已上傳的檔案MD5值串列來實作秒傳功能

 1 namespace signalr.Model
 2 {
 3     /// <summary>
 4     /// 檔案秒傳檢測前臺傳遞引數
 5     /// </summary>
 6     public class UploadFileWholeModel
 7     {
 8         /// <summary>
 9         /// 請求型別,這里固定為:whole
10         /// </summary>
11         public string CheckType { get; set; }
12         /// <summary>
13         /// 檔案的MD5
14         /// </summary>
15         public string FileMd5 { get; set; }
16         /// <summary>
17         /// 前臺檔案的唯一標識
18         /// </summary>
19         public string FileGuid { get; set; }
20         /// <summary>
21         /// 前臺上傳檔案名
22         /// </summary>
23         public string FileName { get; set; }
24         /// <summary>
25         /// 檔案大小
26         /// </summary>
27         public int? FileSize { get; set; }
28     }
29 }

UploadFileChunkModel.cs 前臺檔案分塊傳輸的時候會對分塊傳輸內容進行MD5計算,并且分塊傳輸的時候會傳遞當前分塊的一些資訊,這里對應的后臺接收物體類,

我們可以通過分塊傳輸的MD5值來實作檔案續傳功能(如檔案的某塊MD5已存在則回傳給前臺跳過當前塊)

 1 namespace signalr.Model
 2 {
 3     /// <summary>
 4     /// 檔案分塊(續傳)傳遞引數
 5     /// </summary>
 6     public class UploadFileChunkModel
 7     {
 8         /// <summary>
 9         /// 檔案分塊傳輸檢測型別,這里固定為chunk
10         /// </summary>
11         public string CheckType { get; set; }
12         /// <summary>
13         /// 檔案的總大小
14         /// </summary>
15         public long? FileSize { get; set; }
16         /// <summary>
17         /// 當前塊所屬檔案編號
18         /// </summary>
19         public string FileId { get; set; }
20         /// <summary>
21         /// 當前塊基于檔案的開始偏移量
22         /// </summary>
23         public long? ChunkStart { get; set; }
24         /// <summary>
25         /// 當前塊基于檔案的結束偏移量
26         /// </summary>
27         public long? ChunkEnd { get; set; }
28         /// <summary>
29         /// 當前塊的大小
30         /// </summary>
31         public long? ChunkSize { get; set; }
32         /// <summary>
33         /// 當前塊編號
34         /// </summary>
35         public string ChunkIndex { get; set; }
36         /// <summary>
37         /// 當前檔案分塊總數
38         /// </summary>
39         public string ChunkCount { get; set; }
40         /// <summary>
41         /// 當前塊的編號
42         /// </summary>
43         public string ChunkId { get; set; }
44         /// <summary>
45         /// 當前塊的md5
46         /// </summary>
47         public string Md5 { get; set; }
48     }
49 }

FormData.cs 這是分塊傳輸時傳遞的當前塊的資訊配置

 1 using System;
 2 
 3 namespace signalr.Model
 4 {
 5     /// <summary>
 6     /// 上傳檔案時的附加資訊
 7     /// </summary>
 8     [Serializable]
 9     public class FormData
10     {
11         /// <summary>
12         /// 當前請求型別 分片傳輸是:chunk
13         /// </summary>
14         public string Checktype { get; set; }
15         /// <summary>
16         /// 檔案總位元組數
17         /// </summary>
18         public int? Filesize { get; set; }
19         /// <summary>
20         /// 檔案唯一編號
21         /// </summary>
22         public string Fileid { get; set; }
23         /// <summary>
24         /// 分片資料大小
25         /// </summary>
26         public int? Chunksize { get; set; }
27         /// <summary>
28         /// 當前分片編號
29         /// </summary>
30         public int? Chunkindex { get; set; }
31         /// <summary>
32         /// 分片起始編譯量
33         /// </summary>
34         public int? Chunkstart { get; set; }
35         /// <summary>
36         /// 分片結束編譯量
37         /// </summary>
38         public int? Chunkend { get; set; }
39         /// <summary>
40         /// 分片總數量
41         /// </summary>
42         public int? Chunkcount { get; set; }
43         /// <summary>
44         /// 當前分片唯一編號
45         /// </summary>
46         public string Chunkid { get; set; }
47         /// <summary>
48         /// 當前塊MD5值
49         /// </summary>
50         public string Md5 { get; set; }
51     }
52 }

UploadFileModel.cs 每次上傳檔案的時候,前臺都會傳遞這些引數給服務器,服務器可以根據引數做相應的處理

 1 using System;
 2 using Microsoft.AspNetCore.Mvc;
 3 
 4 namespace signalr.Model
 5 {
 6     /// <summary>
 7     /// WebUploader上傳檔案物體類
 8     /// </summary>
 9     [Serializable]
10     public class UploadFileModel
11     {
12         /// <summary>
13         /// 前臺WebUploader的ID
14         /// </summary>
15         public string Id { get; set; }
16         /// <summary>
17         /// 當前檔案(塊)的前端計算的md5
18         /// </summary>
19         public string FileMd5 { get; set; }
20         /// <summary>
21         /// 當前檔案塊號
22         /// </summary>
23         public string Chunk { get; set; }
24         /// <summary>
25         /// 原始檔案名
26         /// </summary>
27         public string Name { get; set; }
28         /// <summary>
29         /// 檔案型別(如:image/png)
30         /// </summary>
31         [FromForm(Name = "type")]
32         public string FileType { get; set; }
33         /// <summary>
34         /// 當前檔案(塊)的大小
35         /// </summary>
36         public long? Size { get; set; }
37         /// <summary>
38         /// 前臺給此檔案分配的唯一編號
39         /// </summary>
40         public string Guid { get; set; }
41         /// <summary>
42         /// 附件資訊
43         /// </summary>
44         public FormData FromData { get; set; }
45         /// <summary>
46         /// Post過來的資料容器
47         /// </summary>
48         public byte[] FileData { get; set; }
49     }
50 }

UploadFileMergeModel.cs 當所有塊傳輸完成后,傳遞給后臺一個合并檔案的請求,后臺通過引數中的資訊把分塊保存的檔案合并成一個完整的檔案

 1 namespace signalr.Model
 2 {
 3     /// <summary>
 4     /// 檔案合并請求引數類
 5     /// </summary>
 6     public class UploadFileMergeModel
 7     {
 8         /// <summary>
 9         /// 請求型別
10         /// </summary>
11         public string CheckType { get; set; }
12         /// <summary>
13         /// 前臺檢測到的檔案大小
14         /// </summary>
15         public long? FileSize { get; set; }
16         /// <summary>
17         /// 前臺回傳檔案總塊數
18         /// </summary>
19         public int? ChunkNumber { get; set; }
20         /// <summary>
21         /// 前臺回傳檔案的md5值
22         /// </summary>
23         public string FileMd5 { get; set; }
24         /// <summary>
25         /// 前臺回傳上傳檔案唯一標識
26         /// </summary>
27         public string FileName { get; set; }
28         /// <summary>
29         /// 檔案擴展名,不包含.
30         /// </summary>
31         public string FileExt { get; set; }
32     }
33 }

為了實作【秒傳】和分塊傳輸時的【斷點續傳】功能,我們在Class目錄中定義一個UploadFileList.cs類,用來模擬持久化保存服務器所接收到的檔案MD5校驗串列和已接收的分塊MD5值資訊,這里我們使用了并發執行緒安全的ConcurrentDictionary和ConcurrentBag

 1 using System;
 2 using System.Collections.Concurrent;
 3 
 4 namespace signalr.Class
 5 {
 6     public class UploadFileList
 7     {
 8         private static readonly Lazy<ConcurrentDictionary<string, string>> _serverUploadFileList = new Lazy<ConcurrentDictionary<string, string>>();
 9         private static readonly Lazy<ConcurrentDictionary<string, ConcurrentBag<string>>> _uploadChunkFileList =
10             new Lazy<ConcurrentDictionary<string, ConcurrentBag<string>>>();
11         public UploadFileList()
12         {
13             ServerUploadFileList = _serverUploadFileList;
14             UploadChunkFileList = _uploadChunkFileList;
15         }
16 
17         /// <summary>
18         /// 服務器上已經存在的檔案,key為檔案的Md5,value為檔案路徑
19         /// </summary>
20         public readonly Lazy<ConcurrentDictionary<string, string>> ServerUploadFileList;
21         /// <summary>
22         /// 客戶端分配上傳檔案時的記錄資訊,key為上傳檔案的唯一id,value為檔案分片后的當前段的md5
23         /// </summary>
24         public readonly Lazy<ConcurrentDictionary<string, ConcurrentBag<string>>> UploadChunkFileList;
25     }
26 }

擴展一下HubInterface/IChatClient.cs 用來推送給前臺展示后臺處理的資訊

public interface IChatClient
    {
        /// <summary>
        /// 客戶端接收資料觸發函式名
        /// </summary>
        /// <param name="clientMessageModel">訊息物體類</param>
        /// <returns></returns>
        Task ReceiveMessage(ClientMessageModel clientMessageModel);
        /// <summary>
        /// Echart接收資料觸發函式名
        /// </summary>
        /// <param name="data">JSON格式的可以被Echarts識別的data資料</param>
        /// <returns></returns>
        Task EchartsMessage(Array data);
        /// <summary>
        /// 客戶端獲取自己登錄后的UID
        /// </summary>
        /// <param name="clientMessageModel">訊息物體類</param>
        /// <returns></returns>
        Task GetMyId(ClientMessageModel clientMessageModel);
        /// <summary>
        /// 上傳成功后服務器處理資料時通知前臺的資訊內容
        /// </summary>
        /// <param name="clientMessageModel">訊息物體類</param>
        /// <returns></returns>
        Task UploadInfoMessage(ClientMessageModel clientMessageModel);
    }

擴展一下Class/ClientMessageModel.cs

    /// <summary>
    /// 服務端發送給客戶端的資訊
    /// </summary>
    [Serializable]
    public class ClientMessageModel
    {
        /// <summary>
        /// 接收用戶編號
        /// </summary>
        public string UserId { get; set; }
        /// <summary>
        /// 組編號
        /// </summary>
        public string GroupName { get; set; }
        /// <summary>
        /// 發送的內容
        /// </summary>
        public string Context { get; set; }
        /// <summary>
        /// 自定義的回應編碼
        /// </summary>
        public string Code { get; set; }
    }

我們在Startup.cs中注入上傳檔案的配置,同時把前文的XSRF防護去掉,我們在前臺請求的時候帶上防護認證資訊,

public void ConfigureServices(IServiceCollection services)
        {
            services.AddSignalR();
            services.AddRazorPages()
            services.AddSingleton<UploadFileList>();//服務器上傳的檔案資訊保存在記憶體中
            services.AddOptions()
                .Configure<FileUploadConfig>(Configuration.GetSection("FileUpload"));//服務器上傳檔案配置
        }

在專案的wwwroot/js下新建一個uploader.js

 

"use strict";
var connection = new signalR.HubConnectionBuilder()
    .withUrl("/chatHub")
    .withAutomaticReconnect()
    .configureLogging(signalR.LogLevel.Debug)
    .build();
var user = "";

connection.on("GetMyId", function (data) {
    user = data.userId;
});
connection.on("ReceiveMessage", function (data) {
    console.log(data.userId + data.context);
});

connection.on("UploadInfoMessage", function (data) {
    switch (data.code) {
    case "200":
        $('.modal-body').append($("<p>" + data.context + "</p>"));//當后臺回傳處理完成或出錯時,前臺顯示內容,同時顯示關閉按鈕
        $(".modal-content").append($("<div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-secondary\" data-dismiss=\"modal\">Close</button></div>"));
        break;
    case "300":
    case "500":
        $('.modal-body').append($("<p>" + data.context + "</p>"));//展示后臺回傳資訊
        break;
    case "400":
        if ($("#process").length == 0) {//展示后臺推送的檔案處理進度
            $('.modal-body').append($("<p id='process'>" + data.context + "</p>"));
        }
        $('#process').text(data.context);
        break;
    }
});

connection.start().then(function () {
    console.log("服務器已連接");
}).catch(function (err) {
    return console.error(err.toString());
});

在專案的Pages/Shared中新建一個Razor布局頁_LayoutUpload.cshtml

<!DOCTYPE html>

<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width" />
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="~/lib/webuploader/dist/webuploader.css" />
    <script type="text/javascript" src="~/lib/jquery/dist/jquery.min.js"></script>
    <script type="text/javascript" src="~/lib/webuploader/dist/webuploader.js"></script>
    <script type="text/javascript" src="~/lib/bootstrap/dist/js/bootstrap.min.js"></script>
    <title>@ViewBag.Title</title>
    @await RenderSectionAsync("Scripts", required: false)
</head>
<body>
@RenderBody()
</body>
</html>

在Pages目錄下新建一個upload目錄,然后在它下面新建一個index.cshtml,這個檔案中實作了Webuploader中我們所要使用的事件監測、檔案上傳功能,

  1 @page "{handler?}"
  2 @model MediatRStudy.Pages.upload.IndexModel
  3 @{
  4     ViewBag.Title = "WebUploader";
  5     Layout = "_LayoutUpload";
  6 }
  7 @section Scripts
  8 {
  9 <script src="~/js/signalr/dist/browser/signalr.js"></script>
 10 <script src="~/js/uploader.js"></script>
 11 
 12 <script>
 13     // 每次分片檔案大小限制為5M
 14     var chunkSize = 5 * 1024 * 1024;
 15     // 全部檔案限制10G大小
 16     var fileTotalSize = 10 * 1024 * 1024 * 1024;
 17     // 單檔案限制5G大小
 18     var fileSingleSize = 5 * 1024 * 1024 * 1024;
 19     jQuery(function() {
 20         var $ = jQuery,
 21             $list = $('#thelist'),
 22             $btn = $('#ctlBtn'),
 23             state = 'pending',
 24             md5s = {},//分塊傳輸時的各個塊的md5值
 25             dataState,//當前狀態
 26             Token,//可以做用戶驗證
 27             uploader;//webUploader的實體
 28         var fileExt = ["zip", "rar"];//允許上傳的型別
 29         Token = '@ViewData["Token"]';
 30         if (Token == '' || Token == 'undefined') {
 31             $("#uploader").hide();
 32             alert("登錄超時,請重新登錄,");
 33         }
 34  35  36  37  38         //注冊Webuploader要監聽的上傳檔案時的三個事件
 39         //before-send-file 在執行檔案上傳前先執行這個;before-send在開始往服務器發送檔案前執行;after-send-file所有檔案上傳完畢后執行
 40 
 41         window.WebUploader.Uploader.register({
 42                 "before-send-file": "beforeSendFile",
 43                 "before-send": "beforeSend",
 44                 "after-send-file": "afterSendFile"
 45             },
 46             {
 47                 //第一步,開始上傳前校驗檔案,并傳遞給服務器當前檔案的MD5,服務器可根據MD5來實作類似秒傳效果
 48                 beforeSendFile: function(file) {
 49                     var owner = this.owner;
 50                     md5s.length = 0;
 51                     var deferred = window.WebUploader.Deferred();
 52                     owner.md5File(file, 0, file.size)
 53                         .progress(function(percentage) {
 54                             console.log("檔案MD5計算進度:", percentage);
 55                         })
 56                         .fail(function() {
 57                             deferred.reject();
 58                             console.log("檔案MD5獲取失敗");
 59                         })
 60                         .then(function(md5) {
 61                             console.log("檔案MD5:", md5);
 62                             file.md5 = md5;
 63                             var params = {
 64                                 "checktype": "whole",
 65                                 "filesize": file.size,
 66                                 "filemd5": md5
 67                                 ,"filename":file.name
 68                                 ,"fileguid":file.guid
 69                             };
 70                             $.ajax({
 71                                 url: '/upload/FileWhole', //通過md5校驗實作檔案秒傳
 72                                 type: 'POST',
 73                                 headers: {//請求的時候傳遞進去防CSRF攻擊的認證資訊
 74                                     RequestVerificationToken:
 75                                         $('input:hidden[name="__RequestVerificationToken"]').val()
 76                                 },
 77                                 data: params,
 78                                 contentType: 'application/x-www-form-urlencoded',
 79                                 async: true, // 開啟異步請求
 80                                 dataType: 'JSON',
 81                                 success: function(data) {
 82                                     data = https://www.cnblogs.com/wdw984/p/(typeof data) =='string' ? JSON.parse(data) : data;
 83                                     if (data.code != '200') {
 84                                         dataState = data;
 85                                         //服務器回傳錯誤資訊
 86                                         alert('錯誤:' + data.msg);
 87                                         deferred.reject();//取消后續上傳
 88                                     }
 89                                     if (data.isExist) {
 90                                         // 跳過當前檔案并標記檔案狀態為上傳完成
 91                                         dataState = data;
 92                                         owner.skipFile(file, window.WebUploader.File.Status.COMPLETE);
 93                                         deferred.resolve();
 94                                         $('#' + file.id).find('p.state').text('上傳成功【秒傳】');
 95 
 96                                     } else {
 97                                         deferred.resolve();
 98                                     }
 99                                 },
100                                 error: function(xhr, status) {
101                                     $('#' + file.id).find('p.state').text('上傳失敗:'+status);
102                                     console.log("上傳失敗:", status);
103                                 }
104                             });
105                         });
106 
107                     return deferred.promise();
108                 },
109                 //上傳事件第二步:分塊上傳時,每個分塊觸發上傳前執行
110                 beforeSend: function(block) {
111                     var deferred = window.WebUploader.Deferred();
112                     var owner = this.owner;
113                     owner.md5File(block.file, block.start, block.end)
114                         .progress(function(percentage) {
115                             console.log("當前分塊內容的MD5計算進度:", percentage);
116                         })
117                         .fail(function() {
118                             deferred.reject();
119                         })
120                         .then(function(md5) {
121                             //計算當前塊的MD5值并寫入陣列
122                             md5s[block.blob.uid] = md5;
123                             deferred.resolve();
124                         });
125                     return deferred.promise();
126                 },
127                 //時間點3:所有分塊上傳成功后呼叫此函式
128                 afterSendFile: function(file) {
129                     var deferred = $.Deferred();
130                     $('#' + file.id).find('p.state').text('執行最后一步');
131                     console.log(file);
132                     if (file.skipped) {
133                         deferred.resolve();
134                         console.log("執行服務器合并分塊檔案操作");
135                         return deferred.promise();
136                     }
137                     var chunkNumber = Math.ceil(file.size / chunkSize);//總塊數
138                     var params = {
139                         "checktype": "merge",
140                         "filesize": file.size,
141                         "chunknumber": chunkNumber,
142                         "filemd5": file.md5,
143                         "filename": file.guid,
144                         "fileext": file.ext//擴展名
145                     };
146                     $.ajax({
147                         type: "POST",
148                         url: "/upload/FileMerge",
149                         headers: {
150                             RequestVerificationToken:
151                                 $('input:hidden[name="__RequestVerificationToken"]').val(),
152                             userid:user //傳遞SignalR分配的編號
153                         },
154                         data: params,
155                         async: true,
156                         success: function(response) {
157                             if (response.code == 200) {
158                                 //服務器合并完成分塊傳輸的檔案后執行
159                                 dataState = response;
160                                 $("#myModal").modal('show');
161                             } else {
162                                 alert(response.msg);
163                             }
164                             deferred.resolve();
165                         },
166                         error: function() {
167                             dataState = undefined;
168                             deferred.reject();
169                         }
170                     });
171                     return deferred.promise();
172                 }
173             });
174         uploader = window.WebUploader.create({
175             resize: false,
176             fileNumLimit: 1,
177             swf: '/lib/webuploader/dist/Uploader.swf',
178             server: '/upload/FileSave',
179             pick: { id: '#picker', multiple: false },
180             chunked: true,
181             chunkSize: chunkSize,
182             chunkRetry: 3,
183             fileSizeLimit: fileTotalSize,
184             fileSingleSizeLimit: fileSingleSize,
185             formData: {
186             }
187         });
188         uploader.on('beforeFileQueued',
189             function(file) {
190                 var isAdd = false;
191                 for (var i = 0; i < fileExt.length; i++) {
192                     if (file.ext == fileExt[i]) {
193                         file.guid = window.WebUploader.Base.guid();
194                         isAdd = true;
195                         break;
196                     }
197                 }
198                 return isAdd;
199             });
200         //每次上傳前,如果分塊傳輸,則帶上分塊資訊引數
201         uploader.on('uploadBeforeSend',
202             function(block, data, headers) {
203                 var params = {
204                     "checktype": "chunk",
205                     "filesize": block.file.size,
206                     "fileid": block.blob.ruid,
207                     "chunksize": block.blob.size,
208                     "chunkindex": block.chunk,
209                     "chunkstart": block.start,
210                     "chunkend": block.end,
211                     "chunkcount": block.chunks,
212                     "chunkid": block.blob.uid,
213                     "md5": md5s[block.blob.uid]
214                 };
215                 data.formData = JSON.stringify(params);
216 
217                 headers.Authorization = Token;
218                 headers.RequestVerificationToken = $('input:hidden[name="__RequestVerificationToken"]').val();
219                 data.guid = block.file.guid;
220             });
221         // 當有檔案添加進來的時候
222         uploader.on('fileQueued',
223             function(file) {
224                 $list.append('<div id="' +
225                     file.id +
226                     '" class="item">' +
227                     '<h4 class="info">' +
228                     file.name +
229                     '</h4>' +
230                     '<input type="hidden" id="h_' +
231                     file.id +
232                     '" value="' +
233                     file.guid +
234                     '" />' +
235                     '<p class="state">等待上傳...</p>' +
236                     '</div>');
237             });
238 
239         // 檔案上傳程序中創建進度條實時顯示,
240         uploader.on('uploadProgress',
241             function(file, percentage) {
242                 var $li = $('#' + file.id),
243                     $percent = $li.find('.progress .progress-bar');
244                 // 避免重復創建
245                 if (!$percent.length) {
246                     $percent = $('<div class="progress progress-striped active">' +
247                         '<div class="progress-bar" role="progressbar" style="width: 0%">' +
248                         '</div>' +
249                         '</div>').appendTo($li).find('.progress-bar');
250                 }
251                 $li.find('p.state').text('上傳中');
252 
253                 $percent.css('width', percentage * 100 + '%');
254             });
255 
256         uploader.on('uploadSuccess',
257             function(file) {
258                 if (dataState == undefined) {
259                     $('#' + file.id).find('p.state').text('上傳失敗');
260                     $('#' + file.id).find('button').remove();
261                     $('#' + file.id).find('p.state').before('<button id="retry" type="button" class="btn btn-primary fright retry pbtn">重新上傳</button>');
262                     file.setStatus('error');
263                     return;
264                 }
265                 if (dataState.success == true) {
266                     if (dataState.miaochuan == true) {
267                         $('#' + file.id).find('p.state').text('上傳成功[秒傳]');
268                     } else {
269                         $('#' + file.id).find('p.state').text('上傳成功');
270                     }
271                     $('#' + file.id).find('button').remove();
272                     return;
273 
274                 } else {
275                     $('#' + file.id).find('p.state').text('服務器未能成功接收,狀態:' + dataState.success);
276                     return;
277                 }
278             });
279 
280         uploader.on('uploadError',
281             function(file) {
282                 $('#' + file.id).find('p.state').text('上傳出錯');
283             });
284         //分塊傳輸后,可以在這個事件中獲取到服務器回傳的資訊,同時這里可以實作檔案續傳(塊檔案的MD5存在時,后臺可以跳過保存步驟)
285         uploader.on('uploadAccept',
286             function(file, response, reject) {
287                 if (response.code !== 200) {
288                     alert("上傳出錯:" + response.msg);
289                     return false;
290                 }
291                 return true;
292             });
293         uploader.on('uploadComplete',
294             function(file) {
295                 $('#' + file.id).find('.progress').fadeOut();
296             });
297 
298         uploader.on('all',
299             function(type) {
300                 if (type === 'startUpload') {
301                     state = 'uploading';
302                 } else if (type === 'stopUpload') {
303                     state = 'paused';
304                 } else if (type === 'uploadFinished') {
305                     state = 'done';
306                 }
307                 if (state === 'done') {
308                     $btn.text('繼續上傳');
309                 } else if (state === 'uploading') {
310                     $btn.text('暫停上傳');
311                 } else {
312                     $btn.text('開始上傳');
313                 }
314             });
315         $btn.on('click',
316             function() {
317                 if (state === 'uploading') {
318                     uploader.stop();
319                 } else if (state == 'done') {
320                     window.location.reload();
321                 } else {
322                     uploader.upload();
323                 }
324             });
325     });
326 </script>
327 }
328 <div class="container">
329     <div class="row">
330         <div id="uploader" class="wu-example">
331             <span style="color: red">請上傳壓縮包</span>
332             <div class="form-group" id="thelist">
333             </div>
334             <div class="form-group">
335                 <form method="post">
336                     <div id="picker" class="webuploader-container">
337                         <div class="webuploader-pick">選擇檔案</div>
338                         <div style="position: absolute; top: 0; left: 0; width: 88px; height: 34px; overflow: hidden; bottom: auto; right: auto;">
339                             <input type="file" name="file" class="webuploader-element-invisible" />
340                             <label style="-ms-opacity: 0; opacity: 0; width: 100%; height: 100%; display: block; cursor: pointer; background: rgb(255, 255, 255);"></label>
341                         </div>
342                     </div>
343                     <button id="ctlBtn" class="btn btn-success" type="button">開始上傳</button>
344                 </form>
345             </div>
346         </div>
347     </div>
348 </div>
349 
350 <div class="modal fade" id="myModal" tabindex="-1" aria-labelledby="exampleModalScrollableTitle" style="display: none;" data-backdrop="static" aria-hidden="true">
351     <div class="modal-dialog modal-dialog-scrollable">
352         <div class="modal-content">
353             <div class="modal-header">
354                 <h5 class="modal-title" id="exampleModalScrollableTitle">正在處理,,,</h5>
355                 <button type="button" class="close" data-dismiss="modal" aria-label="Close">
356         
357                 </button>
358             </div>
359             <div class="modal-body">
360                 <p>服務器正在處理資料,請不要關閉和重繪此頁面,</p>
361             </div>
362         </div>
363     </div>
364 </div>

index.cshtml的代碼檔案如下

本示例只能解壓縮zip檔案,并且密碼是123456,友情提示,不要用QQ瀏覽器除錯,否則會遇到選擇檔案后DEBUG停止運行,

本示例只能解壓縮zip檔案,并且密碼是123456,友情提示,不要用QQ瀏覽器除錯,否則會遇到選擇檔案后DEBUG停止運行,

本示例只能解壓縮zip檔案,并且密碼是123456,友情提示,不要用QQ瀏覽器除錯,否則會遇到選擇檔案后DEBUG停止運行, 

  1 using ICSharpCode.SharpZipLib.Zip;
  2 using Microsoft.AspNetCore.Http;
  3 using Microsoft.AspNetCore.Mvc;
  4 using Microsoft.AspNetCore.Mvc.RazorPages;
  5 using Microsoft.AspNetCore.SignalR;
  6 using Microsoft.Extensions.Options;
  7 using signalr.Class;
  8 using signalr.HubInterface;
  9 using signalr.Hubs;
 10 using signalr.Model;
 11 using System;
 12 using System.Collections.Concurrent;
 13 using System.Diagnostics;
 14 using System.IO;
 15 using System.Linq;
 16 using System.Text.Json;
 17 using System.Threading.Tasks;
 18 
 19 namespace signalr.Pages.upload
 20 {
 21     public class IndexModel : PageModel
 22     {
 23         private readonly IOptionsSnapshot<FileUploadConfig> _fileUploadConfig;
 24         private readonly IOptionsSnapshot<UploadFileList> _fileList;
 25         private readonly string[] _fileExt;
 26         private readonly IHubContext<ChatHub, IChatClient> _hubContext;
 27         public IndexModel(IOptionsSnapshot<FileUploadConfig> fileUploadConfig, IOptionsSnapshot<UploadFileList> fileList, IHubContext<ChatHub, IChatClient> hubContext)
 28         {
 29             _fileUploadConfig = fileUploadConfig;
 30             _fileList = fileList;
 31             _fileExt = _fileUploadConfig.Value.FileExt.Split(',').ToArray();
 32             _hubContext = hubContext;
 33         }
 34         public IActionResult OnGet()
 35         {
 36             ViewData["Token"] = "666";
 37             return Page();
 38         }
 39 
 40         #region 上傳檔案
 41 
 42         /// <summary>
 43         /// 上傳檔案
 44         /// </summary>
 45         /// <returns></returns>
 46         public async Task<JsonResult> OnPostFileSaveAsync(IFormFile file, UploadFileModel model)
 47         {
 48             if (_fileUploadConfig.Value =https://www.cnblogs.com/wdw984/p/= null)
 49             {
 50                 return new JsonResult(new { code = 400, msg = "服務器配置不正確" });
 51             }
 52 
 53             if (file == null || file.Length < 1)
 54             {
 55                 return new JsonResult(new { code = 404, msg = "沒有接收到要保存的檔案" });
 56             }
 57             Request.EnableBuffering();
 58             var formData = https://www.cnblogs.com/wdw984/p/Request.Form["formData"];
 59             if (model == null || string.IsNullOrWhiteSpace(formData))
 60             {
 61                 return new JsonResult(new { code = 401, msg = "沒有接收到必要的引數" });
 62             }
 63 
 64             var request = model;
 65             long.TryParse(Request.Form["size"], out var fileSize);
 66             request.Size = fileSize;
 67             try
 68             {
 69                 request.FromData = https://www.cnblogs.com/wdw984/p/JsonSerializer.Deserialize(formData, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
 70             }
 71             catch (Exception e)
 72             {
 73                 Debug.WriteLine(e);
 74             }
 75 
 76             if (request.FromData =https://www.cnblogs.com/wdw984/p/= null)
 77             {
 78                 return new JsonResult(new { code = 402, msg = "引數錯誤" });
 79             }
 80 
 81 #if DEBUG
 82             Debug.WriteLine($"原檔案名:{request.Name},檔案編號:{request.Guid},檔案塊編號:{request.Chunk},檔案Md5:{request.FileMd5},當前塊UID:{request.FromData?.Chunkid},當前塊MD5:{request.FromData?.Md5}");
 83 #endif
 84             var fileExt = request.Name.Substring(request.Name.LastIndexOf('.') + 1).ToLowerInvariant();
 85             if (!_fileExt.Contains(fileExt))
 86             {
 87                 return new JsonResult(new { code = 403, msg = "檔案型別不在允許范圍內" });
 88             }
 89             if (_fileList.Value.UploadChunkFileList.Value.ContainsKey(request.Guid))
 90             {
 91                 if (!_fileList.Value.UploadChunkFileList.Value[request.Guid].Any(x => string.Equals(x, request.FromData.Md5, StringComparison.OrdinalIgnoreCase)))
 92                 {
 93                     _fileList.Value.UploadChunkFileList.Value[request.Guid].Add(request.FromData.Md5);
 94                 }
 95 #if DEBUG
 96                 else
 97                 {
 98                     Debug.WriteLine($"ContainsKey{request.FromData.Chunkindex}存在校驗值{request.FromData.Md5}");
 99                     return new JsonResult(new { code = 200, msg = "成功接收", miaochuan = true });
100                 }
101 #endif
102             }
103             else
104             {
105                 return new JsonResult(new { code = 405, msg = "接收失敗,因為服務器沒有找到此檔案的容器,請重新上傳" });
106             }
107 
108             var dirPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _fileUploadConfig.Value.TempPath, request.Guid);
109             if (!Directory.Exists(dirPath))
110             {
111                 Directory.CreateDirectory(dirPath);
112             }
113 
114             var tempFile = string.Concat(dirPath, "\\", request.FromData.Chunkindex.ToString().PadLeft(4, '0'), ".", fileExt);
115             try
116             {
117 
118                 await using var fs = System.IO.File.OpenWrite(tempFile);
119                 request.FileData = https://www.cnblogs.com/wdw984/p/new byte[Convert.ToInt32(request.FromData.Chunksize ?? 0)];
120 
121                 await using var memStream = new MemoryStream();
122                 await file.CopyToAsync(memStream);
123 
124                 request.FileData =https://www.cnblogs.com/wdw984/p/ memStream.ToArray();
125 
126                 await fs.WriteAsync(request.FileData, 0, request.FileData.Length);
127                 await fs.FlushAsync();
128             }
129             catch (Exception e)
130             {
131 #if DEBUG
132                 Debug.WriteLine($"White Error:{e}");
133 #endif
134                 _fileList.Value.UploadChunkFileList.Value.TryRemove(request.Guid, out _);
135             }
136             return new JsonResult(new { code = 200, msg = "成功接收", miaochuan = false });
137         }
138 
139         #endregion
140 
141         #region 合并上傳檔案
142 
143         /// <summary>
144         /// 合并分片上傳的檔案
145         /// </summary>
146         /// <param name="mergeModel">前臺傳遞的請求合并的引數</param>
147         /// <returns></returns>
148         public async Task<JsonResult> OnPostFileMergeAsync(UploadFileMergeModel mergeModel)
149         {
150             return await Task.Run(async () =>
151             {
152                 if (mergeModel == null || string.IsNullOrWhiteSpace(mergeModel.FileName) ||
153                     string.IsNullOrWhiteSpace(mergeModel.FileMd5))
154                 {
155                     return new JsonResult(new { code = 300, success = false, count = 0, size = 0, msg = "合并失敗,引數不正確," });
156                 }
157                 if (!_fileExt.Contains(mergeModel.FileExt.ToLowerInvariant()))
158                 {
159                     return new JsonResult(new { code = 403, success = false, msg = "檔案型別不在允許范圍內" });
160                 }
161 
162                 var fileSavePath = "";
163                 if (!_fileList.Value.ServerUploadFileList.Value.ContainsKey(mergeModel.FileMd5))
164                 {
165                     //合并塊檔案、洗掉臨時檔案
166                     var chunks = Directory.GetFiles(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _fileUploadConfig.Value.TempPath, mergeModel.FileName), "*.*");
167                     if (!chunks.Any())
168                     {
169                         return new JsonResult(new { code = 302, success = false, count = 0, size = 0, msg = "未找到檔案塊資訊,請重試," });
170                     }
171                     var dirPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _fileUploadConfig.Value.FileDir);
172                     if (!Directory.Exists(dirPath))
173                     {
174                         Directory.CreateDirectory(dirPath);
175                     }
176                     fileSavePath = Path.Combine(_fileUploadConfig.Value.FileDir,
177                         string.Concat(mergeModel.FileName, ".", mergeModel.FileExt));
178                     await using var fs =
179                         new FileStream(Path.Combine(dirPath, string.Concat(mergeModel.FileName, ".", mergeModel.FileExt)), FileMode.Create);
180                     foreach (var file in chunks.OrderBy(x => x))
181                     {
182                         //Debug.WriteLine($"File==>{file}");
183                         var bytes = await System.IO.File.ReadAllBytesAsync(file);
184                         await fs.WriteAsync(bytes.AsMemory(0, bytes.Length));
185                     }
186                     //Directory.Delete(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _fileUploadConfig.Value.TempPath, mergeModel.FileName), true);
187 
188 
189                     if (!_fileList.Value.ServerUploadFileList.Value.TryAdd(mergeModel.FileMd5, fileSavePath))
190                     {
191                         return new JsonResult(new { code = 301, success = false, count = 0, size = 0, msg = "服務器保存檔案失敗,請重試," });
192                     }
193                 }
194                 var user = Request.Headers["userid"];
195                 //呼叫解壓檔案
196                 if (string.Equals(mergeModel.FileExt.ToLowerInvariant(), "zip"))
197                 {
198                     DoUnZip(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileSavePath), user.ToString());
199                 }
200                 else
201                 {
202                     await SentMessage(user.ToString(), "服務器只能解壓縮zip格式檔案,", "200");
203                 }
204                 return new JsonResult(new { code = 200, success = true, count = 0, size = 0, msg = "上傳成功", url = fileSavePath });
205             });
206 
207         }
208 
209         #endregion
210 
211         #region 檔案秒傳檢測、檔案型別允許范圍檢測
212         public JsonResult OnPostFileWholeAsync(UploadFileWholeModel model)
213         {
214             if (model == null || string.IsNullOrWhiteSpace(model.FileMd5))
215             {
216                 return new JsonResult(new { Code = 300, IsExist = false, success = false, FileUrl = "", Msg = "引數不正確" });
217             }
218             var fileExt = model.FileName.Substring(model.FileName.LastIndexOf('.') + 1).ToLowerInvariant();
219             if (!_fileExt.Contains(fileExt))
220             {
221                 return new JsonResult(new { code = 403, success = false, msg = "檔案型別不在允許范圍內" });
222             }
223             if (_fileList.Value.ServerUploadFileList.Value.ContainsKey(model.FileMd5))
224             {
225                 return new JsonResult(new { Code = 200, IsExist = true, success = true, FileUrl = _fileList.Value.ServerUploadFileList.Value[model.FileMd5], miaochuan = true });
226             }
227             //檢測的時候創建待上傳檔案的分塊MD5容器
228             _fileList.Value.UploadChunkFileList.Value.TryAdd(model.FileGuid, new ConcurrentBag<string>());
229 
230             return new JsonResult(new { Code = 200, IsExist = false, FileUrl = "" });
231         }
232         #endregion
233 
234         #region 檔案塊秒傳檢測
235         public JsonResult OnPostFileChunkAsync(UploadFileChunkModel model)
236         {
237             if (model == null || string.IsNullOrWhiteSpace(model.Md5) || string.IsNullOrWhiteSpace(model.FileId))
238             {
239                 return new JsonResult(new { Code = 300, IsExist = false, success = false, FileUrl = "", Msg = "引數不正確" });
240             }
241 
242             if (!_fileList.Value.UploadChunkFileList.Value.ContainsKey(model.FileId))
243             {
244                 return new JsonResult(new { Code = 200, IsExist = false, FileUrl = "" });
245             }
246 
247             if (!_fileList.Value.UploadChunkFileList.Value[model.FileId].Contains(model.Md5))
248             {
249                 return new JsonResult(new { Code = 200, IsExist = false, FileUrl = "" });
250             }
251             return new JsonResult(new { Code = 200, IsExist = true, success = true, miaochuan = true });
252         }
253         #endregion
254 
255         #region 解壓、校驗檔案
256 
257         private void DoUnZip(string zipFile, string user)
258         {
259             Task.Factory.StartNew(async () =>
260             {
261                 if (!System.IO.File.Exists(zipFile))
262                 {
263                     //發送一條檔案不存在的訊息
264                     await SentMessage(user, "訪問上傳的壓縮包失敗");
265                     return;
266                 }
267                 var fastZip = new FastZip
268                 {
269                     Password = "123456",
270                     CreateEmptyDirectories = true
271                 };
272                 try
273                 {
274                     var zipExtDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ZipEx", "601018");
275                     //洗掉現有檔案夾
276                     if (Directory.Exists(zipExtDir))
277                         Directory.Delete(zipExtDir, true);
278                     //發送開始解壓縮資訊
279                     await SentMessage(user, "開始解壓縮檔案,,,");
280 #if DEBUG
281                     Debug.WriteLine("開始解壓縮檔案,,,");
282 #endif
283                     fastZip.ExtractZip(zipFile, zipExtDir, "");
284 #if DEBUG
285                     Debug.WriteLine("解壓縮檔案成功,,,");
286 #endif
287                     await SentMessage(user, "解壓縮檔案成功,開始校驗,,,");
288                     //發送解壓成功并開始校驗檔案資訊
289                     var zipFiles = Directory.GetFiles(zipExtDir, "*.jpg", SearchOption.AllDirectories);
290                     for (var i = 0; i < zipFiles.Length; i++)
291                     {
292                         var file = zipFiles[i];
293                         var i1 = i + 1;
294                         await Task.Delay(100);//模擬檔案處理需要100毫秒
295                         //發送進度 i/length
296                         await SentMessage(user, $"校驗進度==>{i1}/{zipFiles.Length}", "400");
297 #if DEBUG
298                         Debug.WriteLine($"當前進度:{i1},總數:{zipFiles.Length}");
299 #endif
300                     }
301                     await SentMessage(user, "校驗完成", "200");
302                 }
303                 catch (Exception exception)
304                 {
305                     //發送解壓縮失敗資訊
306                     await SentMessage(user, $"解壓縮檔案失敗:{exception}", "500");
307 #if DEBUG
308                     Debug.WriteLine($"解壓縮檔案失敗:{exception}");
309 #endif
310                 }
311             }, TaskCreationOptions.LongRunning);
312         }
313 
314         #endregion
315 
316         #region 訊息推送前臺
317 
318         private async Task SentMessage(string user, string content, string code = "300")
319         {
320 
321             await _hubContext.Clients.Client(user).UploadInfoMessage(new ClientMessageModel
322             {
323                 UserId = user,
324                 GroupName = "upload",
325                 Context = content,
326                 Code = code
327             });
328         }
329 
330         #endregion
331     }
332 }
View Code

未能完善的地方:

1、上傳幾百兆或更大的檔案,webuploader計算md5時間太長;

2、后臺處理錯誤的時候,前臺接收訊息后沒能出現關閉按鈕;

3、分塊傳輸時檔案斷點續傳沒有具體實作(理論上是沒問題的)

參考文章:

https://www.cnblogs.com/wdw984/p/11725118.html

http://fex.baidu.com/webuploader/

 

如此文章對你有幫助,請點個推薦吧,謝謝!

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/280138.html

標籤:.NET Core

上一篇:C#中的JSON序列化方法

下一篇:記一次 .NET醫院公眾號系統 執行緒CPU雙高分析

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more