文章来源
应用背景:
系统新增资料 批量上传 功能,用户再上传过程中,不知道上传进度,等待时间过长会误以为系统导入异常 而关闭页面,重新导入,可能出现资料重复的问题。为此,需要提供类似文件上传进度 将执行进度回复给前端页面的方案。
消息从服务端客户端主动推送到客户端,最直接的方案是使用WebSocke(可以双向通信就行)。感觉是有点大材小用了。
具体实现
a,页面加载时与WebSocket服务端建立连接
b,服务端接收连接请求并记录下客户端socket连接
c,执行导入
d,服务端将进度返回前端(当前记录行/总记录行数)
e,前端解析
1,前端:
<script src="~/Scripts/jquery-1.6.4.js"></script>
<script type="text/javascript">
var ws;
$(
function () {
debugger;
ws = new WebSocket("ws://" + window.location.hostname + ":" + window.location.port + "/plmimport/Home/WSChat?userid=" + $('#userid').val());
ws.onopen = function () {
//$("#messageSpan").text("Connected!");
console.log("Connected!");
};
ws.onmessage = function (result) {
//$("#messageSpan").text(result.data);
$("#process").val(result.data.split('/')[0]);
$("#process")[0].setAttribute("max", result.data.split('/')[1]);
$("#processText").text(result.data);
console.log(result.data);
};
ws.onerror = function (error) {
//$("#messageSpan").text(error.data);
console.log(error.data);
};
ws.onclose = function () {
//$("#messageSpan").text("Disconnected!");
console.log("Disconnected!");
};
$("#btnImport").click(function () {
$(this).slideUp("slow");
$("#process").show("slow");//.toggle("hidden");
$("#p1").text("正在执行,请勿关闭当前页面!");
$.ajax({
url: '/plmimport/home/index',
type: 'POST',
cache: false,
data: new FormData($('#uploadForm')[0]),
processData: false,
contentType: false
}).done(function (res) {
$("#p1").text(res);
$("#btnImport").show("slow");
}).fail(function (res) {
$("#p1").text(res);
});
});
}
);
</script>
<body>
<div style="display:none;border: 1px solid #C8DBD3; padding: 8px; line-height: 30px; height:55px; background:url(../../Images/bg_odv.png);">
</div>
<input type="text" id="userid" value="@ViewBag.UserId" hidden />
@*@using (Html.BeginForm("Index", "Home", FormMethod.Post, new { enctype = "multipart/form-data", id = "form1" }))
{*@
<form id="uploadForm" enctype="multipart/form-data">
<h2>料件导入(Excel)</h2>
<span style="color:red">@ViewBag.PartTemplateInfo</span>
<div>
<fieldset id="myfieldset">
<legend>帮助 </legend>
<p style="color: Red; text-align: left;">
@Html.ActionLink("模版下载", "GetFile", new { down = "template" })
</p>
</fieldset>
</div>
<div style="margin-top: 20px;">
<fieldset id="myfieldset2">
<legend>导入信息提示区</legend>
@*<p id="" style="color: Red; text-align:left;">@ViewBag.error</p>*@
<div>
<progress id="process" value="1" max="100" hidden></progress>
<label id="processText"></label>
</div>
<p id="p1" style="color: Red; text-align:left;">@ViewBag.error</p>
</fieldset>
</div>
<div style="margin-top: 20px;">
<fieldset id="myfieldset1">
<legend>导入操作</legend>
<p>选择文件:<input id="FileUpload" type="file" name="files" style="width: 250px; height: 28px;background: White" class="easyui-validatebox" /></p>
<p>
<input id="btnImport" type="button" value="执行导入" style="width: 100px; height: 28px;" />
</p>
</fieldset>
</div>
</form>
@* } *@
</body>
2,后段实现:
#region 使用WebSocket实现消息推送
private static Dictionary<string, System.Net.WebSockets.WebSocket> CONN_POOL = new Dictionary<string, System.Net.WebSockets.WebSocket>();
public void WSChat()
{
if (HttpContext.IsWebSocketRequest)
{
HttpContext.AcceptWebSocketRequest(ProcessWSChat);
}
}
async System.Threading.Tasks.Task ProcessWSChat(System.Web.WebSockets.AspNetWebSocketContext context)
{
string user = context.QueryString["userid"]?.ToString();
if (string.IsNullOrEmpty(user))
{
return;
}
System.Net.WebSockets.WebSocket ws = context.WebSocket;
if (CONN_POOL.Keys.Contains(user))
{
if (CONN_POOL[user] != ws)
{
CONN_POOL[user] = ws;
}
}
else
{
CONN_POOL.Add(user, ws);
}
while (true)
{
if (ws.State == System.Net.WebSockets.WebSocketState.Open)
{
ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[2048]);
System.Net.WebSockets.WebSocketReceiveResult result = await ws.ReceiveAsync(buffer, System.Threading.CancellationToken.None);
try
{
#region 连接关闭,删除连接池
if (ws.State != System.Net.WebSockets.WebSocketState.Open)
{
if (CONN_POOL.Keys.Contains(user))
{
CONN_POOL.Remove(user);
}
break;
}
#endregion
}
catch (Exception ex)
{
if (CONN_POOL.Keys.Contains(user))
{
CONN_POOL.Remove(user);
}
}
}
else
{
//发送消息时已离线。退出
break;
}
}
}
async private void SendMessageToClient(string message)
{
///消息推送异常不抛错误。
try
{
if (Session["plmUserid"] == null || !CONN_POOL.Keys.Contains(Session["plmUserid"].ToString()))
{
return;
}
if (CONN_POOL.Keys.Contains(Session["plmUserid"].ToString()))
{
if (CONN_POOL[Session["plmUserid"].ToString()].State == System.Net.WebSockets.WebSocketState.Open)
{
await CONN_POOL[Session["plmUserid"].ToString()].SendAsync(GetArrSegFromStr(message), System.Net.WebSockets.WebSocketMessageType.Text, true, System.Threading.CancellationToken.None);
}
}
}
catch (Exception ex)
{
}
}
/// <summary>
/// 字符串转ArraySegment<byte>,用于WebSocket发送消息
/// </summary>
/// <param name="text">要转换的文本</param>
/// <returns></returns>
ArraySegment<byte> GetArrSegFromStr(string text)
{
return new ArraySegment<byte>(System.Text.Encoding.UTF8.GetBytes(text));
}
/// <summary>
/// ArraySegment<byte>转字符串,用于消息队列存储
/// </summary>
/// <param name="text">要转换的ArraySegment对象</param>
/// <returns></returns>
string GetStrFromArrSeg(ArraySegment<byte> arrSeg)
{
return System.Text.Encoding.UTF8.GetString(arrSeg.Array);
}
#endregion
[HttpPost]
public async System.Threading.Tasks.Task<string> Index(HttpPostedFileBase filebase)
{
SendMessageToClient("10/50");
System.Threading.Thread.Sleep(1000);
SendMessageToClient("20/50");
System.Threading.Thread.Sleep(1000);
SendMessageToClient("30/50");
System.Threading.Thread.Sleep(1000);
System.Threading.Thread.Sleep(1000);
SendMessageToClient("40/50");
System.Threading.Thread.Sleep(1000);
SendMessageToClient("50/50");
System.Threading.Thread.Sleep(1000);
return null;
}
遇到的问题:
在使用Form 对button是submit类型,提交到Action时,页面点击 [执行导入]时,Firefox存在一个问题:载入页面时与 ws: 的连接中断。官方也一直没有解决此问题。Chrome就可以正常执行。
解决办法就是使用Ajax,如此页面URL就不会是等待加载 的状态了,数据也能提交执行,然后通过WebSocket将进度传回前端。