Хочу таймер и счётчик загруженных строк на Web-форме

в 6:52, , рубрики: .net, ASP, ASP.NET, browser, C#, counter, httphandler, ie, javascript, jquery, memory leak, onreadystatechange, setTimeout, timer, web, счетчик строк, таймер, метки: ,

… отчет формировался долго. Песочные часы и синяя полоска клонили в сон. Вначале он хотел обезьянку, которая лезет по лестнице, потом градусник, но в итоге остался таймер и счетчик. Глупая улыбка и добрый, сочувствующий, взгляд делают чудеса.

В процессе разработки форм для отчетности, пользователь захотел видеть процесс загрузки данных из базы. Он хотел, чтобы после нажатия кнопки включался секундомер, а по мере получения строк, их количество отображалось на форме.Реализовать это надо было в рамках существующего проекта на 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.

Автор: alex_29

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js