… отчет формировался долго. Песочные часы и синяя полоска клонили в сон. Вначале он хотел обезьянку, которая лезет по лестнице, потом градусник, но в итоге остался таймер и счетчик. Глупая улыбка и добрый, сочувствующий, взгляд делают чудеса.
В процессе разработки форм для отчетности, пользователь захотел видеть процесс загрузки данных из базы. Он хотел, чтобы после нажатия кнопки включался секундомер, а по мере получения строк, их количество отображалось на форме.Реализовать это надо было в рамках существующего проекта на ASP.NET.
Решая эту задачу, я столкнулся с проблемой выбора способа обмена данными с сервером, так как ему надо было передать данные и получить какой-то ответ, не перегружая страницу. Помимо кардинального метода – переписать всё на MVC, есть и другие способы. Большинство из них было описано в статье Как выполнить callback со стороны клиента на сервер в ASP.NET. Так же можно пользоваться Callback контролами от DevExpress или ему подобных коммерческих продуктах.
В описываемом случае я воспользовался методом, о котором узнал, читая Дино Эспозито. Речь идёт об HTTPHandler. Его преимущество – это минимум кода, который выполняет сервер по сравнению с другими способами, отсюда высокое быстродействие, чего очень не хватает ASP.NET приложениям. На стороне клиента я использовал JQuery и функцию setTimeout.
Код на стороне сервера выглядит так:
Файл TestForm.aspx.cs:
using System;
namespace HTTPHandler_Test
{
public partial class TestForm : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
}
}
HTTPHandler для теста — HTTPHandlerTest.ashx.cs :
using System.Web;
using System.Web.SessionState;
namespace HTTPHandler_Test.HTTPHandler
{
/// <summary>
/// Сводное описание для HTTPHandlerTest
/// </summary>
public class HTTPHandlerTest : IHttpHandler, IRequiresSessionState
{
public void ProcessRequest(HttpContext context)
{
var counter = context.Request.QueryString["counter"];
int iCount;
if (int.TryParse(counter, out iCount))
{
counter = (++iCount).ToString();
}
context.Response.Write(counter);
}
public bool IsReusable
{
get
{
return true;
}
}
}
}
На стороне клинета так же надо добавить пару строк:
Файл TestForm.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="TestForm.aspx.cs" Inherits="HTTPHandler_Test.TestForm" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title></title>
<script src="Script/jquery-1.10.2.min.js"></script>
<script src="Script/askHTTPHandler.js"></script>
</head>
<body>
<form id="form1" runat="server">
<div>
<label id="lblStatus" style="display:block">Таймер: </label>
<label id="lblCounter" style="display:block">Счетчик: </label>
<input type="button" id="btnStartStop" value="Старт" onclick="AskHttpHandler()"/>
</div>
</form>
</body>
</html>
Файл askHTTPHandler.js:
var start = false;
function AskHttpHandler() {
var dateStart = new Date();
var counter = 0;
start = !start;
if (start) {
$('#btnStartStop').val("Стоп");
Ask();
}
else $('#btnStartStop').val("Старт");
function Ask() {
var difInSeconds = Math.floor(((new Date()).getTime() - dateStart.getTime()) / 1000);
var hours = Math.floor(difInSeconds / 3600);
var minutes = Math.floor((difInSeconds - (hours * 3600)) / 60);
var seconds = difInSeconds - (hours * 3600) - (minutes * 60);
if (hours < 10) hours = "0" + hours;
if (minutes < 10) minutes = "0" + minutes;
if (seconds < 10) seconds = "0" + seconds;
$('#lblStatus').text("Таймер: " + hours + ":" + minutes + ":" + seconds);
var $ajaxQ = $.ajax({
type: "GET",
async: false,
url: "/HTTPHandler/HTTPHandlerTest.ashx",
data: "counter=" + counter,
success: onSuccessAsk,
error: onErrorAsk
});
var noop = function () { };
if ($ajaxQ != null) {
$ajaxQ.onreadystatechange = $ajaxQ.abort = noop;
$ajaxQ = null;
}
function onSuccessAsk(result) {
counter = parseInt(result);
$('#lblCounter').text("Счетчик: " + result);
if (start) setTimeout(Ask, 1000);
}
function onErrorAsk(result) {
alert("error " + result.responseText);
}
};
}
В процессе разработки обнаружилась досадная утечка памяти на стороне клиента. Решил её с помощью кода ниже:
var noop = function () { };
if ($ajaxQ != null) {
$ajaxQ.onreadystatechange = $ajaxQ.abort = noop;
$ajaxQ = null;
}
Предварительно полазив по интернету и наткнувшись на это ссылку: Memory Growing to Heaven
Заключение
В процессе работы с ASP.NET все больше и больше приходиться смешаться в сторону толстого клиента и использовать сервер как источник данных. HTTPHandler неплохо справляется с этой задачей, позволяя сохранить специфический для ASP.NET функционал и обойти его недостатки.
P.S.
Данное решение, помимо информирования пользователя, так же помогло отловить баги связанные с провайдером к БД Oracle. Время от времени, при получении данных от сервера, скорость чтения строк начинала очень быстро падать. Исправить это получилось установкой Min Pool Size и Max Pool Size в значение 23.