【问题标题】:What is the correct way to access a remote resource in my MVC controller?在我的 MVC 控制器中访问远程资源的正确方法是什么?
【发布时间】:2015-07-17 07:08:33
【问题描述】:

我在 .Net 上使用 MVC 5,我的用户流程如下所示:

  1. 用户在表单中输入地址。
  2. 使用 AJAX 将表单发布到控制器。
  3. 控制器将地址记录到数据库中。
  4. 控制器向 Bing 地图发出 WebClient 请求,以将地址地理编码为纬度和经度。
  5. 控制器将经纬度记录到数据库中。
  6. 控制器返回一个 AJAX 结果,该结果在客户端呈现到带有地址和纬度/经度的更新视图中。

我知道对 Bing 地图的调用应该在异步上下文中进行,以便我的网站的速度与 Bing 地图的速度分离。

相反,我认为我的流程应该是这样的:

  1. 用户在表单中输入地址。
  2. 使用 AJAX 将表单发布到控制器。
  3. 控制器将地址记录到数据库中。
  4. 控制器启动异步任务来进行地理编码和更新数据库
  5. 控制器向客户端返回一个显示更新地址的 AJAX 结果,并告诉它轮询客户端以完成地理编码结果。

我被困在第 4 步。这是我所拥有的:

public ActionResult GetLocation(int id)
{
    Listing li = db.Listings.Find(id);

    Task.Run(() => {
        // update geocode if necessary
        if (li.BizAddress.GeoStatus != BusinessAddress.GeocodeStatus.UpToDate &&
            DateTime.Now - li.BizAddress.LastGeoAttempt > TimeSpan.FromHours(1))
        {
            Geocoder geo = new Geocoder();
            GeocodeResult gr = geo.Geocode(li.BizAddress).Result;
            if (gr.BadResult != true)
            {
                li.BizAddress.Latitude = gr.Location.Latitude;
                li.BizAddress.Longitude = gr.Location.Longitude;
                li.BizAddress.GeoStatus = BusinessAddress.GeocodeStatus.UpToDate;

            }
            else
            {
                // failed
                li.BizAddress.Latitude = 0;
                li.BizAddress.Longitude = 0;
                li.BizAddress.GeoStatus = BusinessAddress.GeocodeStatus.BadResult;
            }

            li.BizAddress.LastGeoAttempt = DateTime.Now;
            db.SaveChanges();
        }

    });


    return PartialView("~/Views/Listing/ListingPartials/_Location.cshtml", li);
}

但是,当我到达 db.SaveChanges() 时,我得到一个异常,即 db 上下文已被释放。

我希望我的任务在闭包内运行异步,这样 db 仍然是一个有效的未处理变量。

这可能吗?我是异步编程的新手,我还不知道所有的习语。

附加信息:我在我的视图中呈现我的面板,如下所示:

@{Html.Action("GetLocation", new { id = Model.ID });}

我想保留这种行为,因为使用 AJAX 加载它可能会损害 SEO。

【问题讨论】:

  • 不确定我是否完全理解,但在 #5 中的 IINM 在你想要做的事情中(投票),那仍然是 client-side 以便“打破”你的最后一个声明(使用 AJAX 加载会损害 SEO)(?) - 除了 Bing 调用(无论如何)之外,您的“其他内容”将被“完成”(渲染)。我不是大师,所以对于异常部分,您正在从 ASP.net 请求上下文中触发一个单独的任务(它是自己的上下文) - 所以它不是“线性的” - ASP.Net 请求上下文之前是“完成的”你的Task(这就是为什么在下面的答案中使用async/await

标签: c# asp.net-mvc asynchronous


【解决方案1】:

以这种方式启动任务不是好的做法。相反,将您的 ActionResult 签名标记为 async,然后将 await 标记为您在方法体内需要的结果。这样,您就不会占用服务器资源来等待 Bing 的响应。这也使您不必担心指示客户端轮询未来的结果,因为我认为这是异步编程中的反模式。只需一个电话即可处理所有这些并在可用时发送结果要容易得多。由于您使用的是 EF6,因此您还可以利用异步方法来提高服务器性能。

public async Task<ActionResult> GetLocation(int id)
{
    var listing = await db.Listings.FindAsync(id);
    ...
    var gr = await geo.Geocode(li.BizAddress);

至少,如果您确定要走这条路,那么清理您的任务代码很重要。将 .Result 与 Web 服务器内的任务一起使用是非常糟糕的做法,因为它会锁定服务器线程。更改任务的定义,使其异步:

Task.Run(async () => {
        // update geocode if necessary
        if (li.BizAddress.GeoStatus != BusinessAddress.GeocodeStatus.UpToDate &&
            DateTime.Now - li.BizAddress.LastGeoAttempt > TimeSpan.FromHours(1))
        {
            Geocoder geo = new Geocoder();
            GeocodeResult gr = await geo.Geocode(li.BizAddress);

【讨论】:

  • 据我了解,这个问题是现在 GetLocation 在返回 PartialView 之前阻止地理编码步骤。我想马上回来。然后让异步进程稍后将其结果写入数据库。
  • @JohnShedletsky 嗯,是的,我没有意识到您是从 Razor 中的 @ActionResult 调用此 ActionResult 方法
  • 必须有一个模式来处理这个问题,我只是不知道谷歌是为了什么。
  • 您从哪里获得db 的实例?您的代码中不清楚。无需在 Task 的闭包中传递它,只需在 Task 中创建一个新实例,然后您就不必担心它会被释放。
  • 它是我的 Controller 的私有 ivar。只需按照我在入门代码中看到的内容即可。我会尝试你的建议,这很聪明。 SaveChanges 如何知道在这种情况下发生了什么变化?它必须做疯狂的巫术才能仍然工作?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-20
相关资源
最近更新 更多