首页 > 解决方案 > Mvc 从服务器端取消 Jquery Ajax 请求

问题描述

我有一种情况,我需要从数据库执行搜索操作并在网页上显示结果。用户有网络表单可以从Sql Server数据库中输入和搜索食物。所以在这里我想取消之前正在进行的搜索操作并继续新的搜索。

例如,第一个用户类型tea和请求发送到 MvcActionResult使用Ajax. 所以现在请求正在进行中,用户类型Tea with milk所以当时我想取消以前的请求并继续新的请求。

我有下面Jquery的代码向 Mvc 发送请求ActionResultabort()功能。

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;

在这里,我发送取消令牌以取消现有的数据库操作。由于我有大量的食物数据,我有必要取消数据库操作。我调试客户端请求,它显示如下

在此处输入图像描述

我认为这是非常简单的场景,所以我为此搜索了很多,但没有找到任何有用的链接或示例。我发现了这个,但我不知道这个实现如何帮助我取消数据库操作或取消以前的旧请求。有人可以帮我锻炼吗?

标签: c#jqueryasp.net-mvcazure-sql-database

解决方案


我使用 ASP.NET MVC 5 创建了以下示例,并检查了开发人员工具中的 SQL Profiler 和 Network 选项卡,我可以确认它运行良好,并且数据库服务器也收到取消并取消查询执行。

请考虑,相同的解决方案也适用于 EF,但由于在帖子DataTable中已使用,我还使用DataTable.

ASP.NET MVC 5 - 取消示例

在下面的示例中,我创建了一个简单的索引页面,其中包含一个文本框。当您输入 时TextBox,它会等待 500 毫秒来检测您是否停止输入。然后,如果检测到您停止输入,它会发送一个 ajax 搜索请求。

如果您在 keytorkes 之间再次开始输入(或者您有超过 500 毫秒的延迟),它会取消请求并且查询执行也将在数据库级别取消。您可以使用分析器查看它。

Table1Business.cs

我想您有一个数据库,其中包含Table1哪些具有IdName列。所以我创建了一个业务逻辑类,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;
        }
    }
}

家庭控制器.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")));
    }
}

索引.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>

搜索.cshtml

@model IEnumerable<string>
<ul>
    @foreach (var item in Model)
    {
        <li>@item</li>
    }
</ul>

推荐阅读