Mvc отменить запрос Jquery Ajax со стороны сервера - PullRequest
0 голосов
/ 07 января 2019

У меня есть один сценарий, в котором мне нужно выполнить операцию поиска из базы данных и показать результат на веб-странице. Пользователь имеет веб-форму для ввода и поиска продуктов питания из Sql Server базы данных. Поэтому здесь я хочу отменить предыдущую текущую операцию поиска и продолжить новый поиск.

Например, Первый пользовательский тип tea и запрос, отправленный Mvc ActionResult с использованием Ajax. Теперь запрос обрабатывается, и пользователь набирает Чай с молоком , поэтому я хочу отменить предыдущий запрос и продолжить с новым запросом.

У меня ниже Jquery код для отправки запроса на Mvc ActionResult с функциональностью abort().

var xhr = null;
function searchFood(o, q, f) {
    if(xhr && xhr.readyState != 4){
        console.log(xhr);
        xhr.abort();
    }
    xhr = $.ajax({
        type: "GET",
        url: "/food/foodsearch/",
        data: {
            o: o,
            q: q,
            filters: f
        },
        beforeSend: function () {
            showSpinner();
        },
        success: function (a) {
            try {                        
                xhr = null;
            } catch (c) {
                xhr = null;
            }
        },
        error: function (a, b, c) {
            xhr = null;
        },
    });
}

Когда пользователь заканчивает печатать, я вызываю этот метод searchfood. Взгляните на мою серверную сторону c# код

[HttpGet]
[AjaxOnly]
public async Task<PartialViewResult> foodsearch(int o, string q, string filters, CancellationToken cancellationToken)
{
    CancellationToken disconnectedToken = Response.ClientDisconnectedToken;
    var source = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, disconnectedToken);

    //some local veriable declared
    model.foods = filters.ToLower() == FoodFilterTypes.b_food || filters.ToLower() == all_categories ? await SearchFoodAsync(o, maxLimit, FoodModel.b_food, q, null, null, source.Token) : new List<Food>();
}

/// <summary>
/// search foods
/// </summary>
/// <param name="offset"></param>
/// <param name="limit"></param>
/// <param name="q"></param>
/// <param name="filters"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[NonAction]
protected async Task<List<Food>> SearchFoodAsync(int offset, int limit, string filters, string q, CancellationToken cancellationToken)
{
    DataTable dtblFood = await LSocialBL.SearchFoodAsync(offset, limit, filters, q, cancellationToken);
    //--- few more code lines
    if (dtblFood != null)
    {
        foods = dtblFood.ToList<Food>();
        //dispose object
        dtblFood.Dispose();
        dtblFood = null;

        Parallel.ForEach(foods, new ParallelOptions { CancellationToken = cancellationToken }, async (f) =>
        {
            f.images = await GetFoodImagesAsync(f.id, cancellationToken);
        });
        //for (int i = 0; i < foods.Count; i++)
        //{
        //  foods[i].images = await GetFoodImagesAsync(foods[i].id);
        //}
    }
}

Здесь мой LSocialBL.SearchFoodAsync метод выполняет операцию с базой данных, как показано ниже. Я выполняю хранимую процедуру для получения результатов.

using (IDataReader drdFood = await objSqlDatabase.ExecuteReaderAsync(objSqlCommand, cancellationToken))
{
    dtblFoods.Load(drdFood);
}
return dtblFoods;

Здесь я отправляю токен отмены для отмены существующей операции БД. Мне необходимо отменить операцию дБ, поскольку у меня есть огромные данные о продуктах питания. Я отлаживаю запрос клиента, и он показывает как показано ниже

enter image description here

Я думаю, что это действительно простой сценарий, поэтому я много искал его, но не нашел полезных ссылок или примеров. Я нашел this , но я не понимаю, как эта реализация помогает мне отменить операции БД или отменить предыдущий старый запрос. Может ли кто-нибудь помочь мне тренироваться?

Ответы [ 3 ]

0 голосов
/ 09 января 2019

Я создал следующий пример с использованием ASP.NET MVC 5, и я проверил вкладку «SQL Profiler» и «Сеть» в инструментах разработчика, и я могу подтвердить, что она работает хорошо, и сервер базы данных также получает отмену и отменяет выполнение запроса.

Обратите внимание, что те же решения будут работать и с EF, но, поскольку в посте использовался DataTable, я также написал пример, используя DataTable.

ASP.NET MVC 5 - Пример отмены

В следующем примере я создал простую страницу индекса, содержащую текстовое поле. Когда вы набираете TextBox, он ждет 500 миллисекунд, чтобы определить, прекратили ли вы печатать. Затем, после обнаружения прекращения набора текста, он отправляет ajax-запрос для поиска.

Если вы снова начнете печатать (или у вас более 500 мс) между нажатиями клавиш, запрос будет отменен, а выполнение запроса также будет отменено на уровне базы данных. Вы можете увидеть это, используя профилировщик.

Table1Business.cs

Полагаю, у вас есть база данных, содержащая Table1, в которой есть Id и Name столбцы. Поэтому я создаю класс бизнес-логики для поиска между записями Table1, сделав небольшую задержку в запросе.

Примечание. Задержка указана, например, для имитации длительного выполнения запроса.

В следующем коде соединение было открыто асинхронно, считыватель выполнил асинхронно, а также как считыватель считан асинхронно:

using System.Data;
using System.Data.SqlClient;
using System.Threading;
using System.Threading.Tasks;
public class Table1Business
{
    public async Task<DataTable> Search(string name,
        CancellationToken cancellationToken = default(CancellationToken))
    {
        using (var connection = new SqlConnection(@"Your connection string"))
        using (var command = new SqlCommand("WAITFOR DELAY '00:00:10'; " +
            "SELECT TOP 10 [Id], [Name] " +
            "FROM [Table1] WHERE [Name] LIKE '%' + @Name + '%'", connection))
        {
            var table = new DataTable();
            table.Columns.Add("Id", typeof(int));
            table.Columns.Add("Name", typeof(string));
            command.Parameters.Add("@Name", SqlDbType.NVarChar, 50).Value = name;
            await connection.OpenAsync(cancellationToken);
            var reader = await command.ExecuteReaderAsync(cancellationToken);
            while (await reader.ReadAsync(cancellationToken))
            {
                object[] values = new object[2];
                reader.GetValues(values);
                table.Rows.Add(values);
            }
            return table;
        }
    }
}

HomeController.cs

Следующий контроллер имеет простое действие Index для возврата к просмотру Index и действие Search, которое отвечает за выполнение поиска и получение импульса отмены. В случае отмены выполнения будет отменено даже в базе данных. В этом сообщении вы можете найти больше информации об отмене.

using CancellationExample.Models;
using System.Data;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Mvc;
public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }
    public async Task<ActionResult> Search(string name, CancellationToken cancellationToken)
    {
        CancellationToken disconnectedToken = Response.ClientDisconnectedToken;
        var source = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, 
            disconnectedToken);
        DataTable dt = null;
        var business = new Table1Business();
        dt = await business.Search(name, source.Token);
        return PartialView(dt.AsEnumerable().Select(x => x.Field<string>("Name")));
    }
}

Index.cshtml

Способ обнаружения конца набора текста заимствован из этой записи . Это просто, например, вы можете использовать любое другое решение в зависимости от ваших предпочтений. Лично я предпочитаю полагаться на ввод, а не пытаться обнаружить изменение ввода.

В любом случае, в следующем коде мы проверяем, не является ли xhr нулевым, это означает, что выполняется другой запрос ajax, и затем мы прерываем его:

@{
    Layout = null;
}
<div>
    <form action="/home/search" method="get" id="form">
        <input type="text" name="name" id="name" />
    </form>
    <div id="result"></div>
</div>

<script src="~/Scripts/jquery-3.3.1.js"></script>
<script>
    $(function () {
        var xhr = null;
        var timeout = null;
        $("#name").keyup(function () {
            clearTimeout(timeout);
            if (xhr != null)
                xhr.abort();
            timeout = setTimeout(function () {
                xhr = $.get("/home/search?name=" + $("#name").val(), function (data) {
                    $("#result").html(data);
                });
            }, 500);
        });
    });
</script>

Search.cshtml

@model IEnumerable<string>
<ul>
    @foreach (var item in Model)
    {
        <li>@item</li>
    }
</ul>
0 голосов
/ 09 января 2019

Вот код

   [HttpGet]
    [AjaxOnly]
    public async Task<PartialViewResult> foodsearch(int o, string q, string filters)
    {
        //some local veriable declared
        model.foods = new List<Food>();


        if (filters.ToLower() == FoodFilterTypes.b_food
        || filters.ToLower() == all_categories)
        {
            var gotResult = false;
            var tokenSource1 = new CancellationTokenSource();
            var tokenSource2 = new CancellationTokenSource();
            CancellationToken ct1 = tokenSource.Token;
            CancellationToken ct2 = tokenSource.Token;
            Task.Factory.StartNew(() =>
            {
                await SearchFoodAsync(o, maxLimit, FoodModel.b_food, q, null, null, ct2);
                gotResult = true;
            }, ct1);

            while (!gotResult)
            {
                // When you call abort Response.IsClientConnected will = false
                if (!Response.IsClientConnected)
                {
                    tokenSource1.Cancel();
                    tokenSource2.Cancel();
                    break;
                }
                Thread.Sleep(1000 * 10);
            }

        };
        return PartialView();
    }
    /// <summary>
    /// search foods
    /// </summary>
    /// <param name="offset"></param>
    /// <param name="limit"></param>
    /// <param name="q"></param>
    /// <param name="filters"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    [NonAction]
    protected async Task<List<Food>> SearchFoodAsync(int offset, int limit, string filters, string q, CancellationToken ct2)
    {
        var dtblFood = await LSocialBL.SearchFoodAsync(offset, limit, filters, q, ct2);
        //--- few more code lines
    }
0 голосов
/ 09 января 2019

Шаг 1: Получить идентификатор сеанса

Select @@SPID ;

Шаг 2: убить идентификатор сессии

    Kill 50 ;

Шаг 3: Доказательство того, что SQL-запрос убит

если вы запустите код ниже, вы получите

Невозможно продолжить выполнение, потому что сеанс находится в уничтожении состояние.

Произошла серьезная ошибка в текущей команде. результаты, если таковые имеются, должны быть отброшены.

enter image description here

Шаг 4: Полный код

создать новое консольное приложение и изменить "Сервер =; База данных = Тест; Trusted_Connection = True;"

using System;
using System.Data.SqlClient;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static   void Main(string[] args)
        {
            SqlConnection connection = new SqlConnection("Server=.;Database=Test;Trusted_Connection=True;;Connection Timeout=0");
            connection.Open();
            var SPID = GetSPID(connection);
            var task1 = new Task(() => doLongSql(connection),
               TaskCreationOptions.LongRunning);
            task1.Start();



            Thread.Sleep(1000 * 10);//wait 10 seconds

            if (!task1.IsCompleted)
            {

               KillSPID( SPID);

            }

            Task.WaitAll(task1);

        }
        static int GetSPID(SqlConnection connection)
        {
            SqlCommand command = new SqlCommand("Select @@SPID ",connection);
            int SPID = Convert.ToInt32( command.ExecuteScalar()) ;
            return SPID;
        }

        static void KillSPID( int SPID)
        {
            SqlConnection connection = new SqlConnection("Server=.;Database=Test;Trusted_Connection=True;");
            connection.Open();
            SqlCommand command = new SqlCommand($"KILL {SPID}", connection);
            command.ExecuteNonQuery();
            connection.Close();
        }

        static void doLongSql(SqlConnection connection)
        {

            using (connection)
            {
                SqlCommand command = new SqlCommand(
                  "WAITFOR DELAY '01:00'", //wait 1 minute
                  connection);
                 command.ExecuteNonQuery();
              }
        }


    }
}

Ref:

Extra

SELECT conn.session_id, host_name, program_name,
    nt_domain, login_name, connect_time, last_request_end_time 
FROM sys.dm_exec_sessions AS sess
JOIN sys.dm_exec_connections AS conn
   ON sess.session_id = conn.session_id;

enter image description here

Select @@SPID

 KILL 56;  

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...