在我的MVC控制器中访问远程资源的正确方法是什么?

时间:2015-07-16 22:32:03

标签: c# asp.net-mvc asynchronous

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

  1. 用户在表单中输入地址。
  2. 使用AJAX将表单发布到控制器。
  3. 控制器将地址记录到数据库中。
  4. Controller向Bing Maps发出WebClient请求,将地址编码为纬度和经度。
  5. 控制器将纬度和经度记录到数据库中。
  6. Controller将客户端呈现的AJAX结果返回到具有地址和纬度/经度的更新视图中。
  7. 我知道对Bing地图的调用应该在异步上下文中进行,这样我的网站速度就会与Bing地图的速度脱钩。

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

    1. 用户在表单中输入地址。
    2. 使用AJAX将表单发布到控制器。
    3. 控制器将地址记录到数据库中。
    4. Controller启动异步任务以执行地理编码并更新数据库
    5. Controller将AJAX结果返回给显示更新地址的客户端,并告诉客户端轮询完成地理编码结果。
    6. 我被困在第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上下文已被处理掉。

      我希望我的Task在闭包内部运行async,这样db仍然是一个有效的未被置换的变量。

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

      其他信息:我在我的视图中渲染我的面板:

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

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

1 个答案:

答案 0 :(得分:0)

以这种方式解雇任务并不是一种好习惯。相反,将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);