2012-07-09 23 views
1

基本上我想实现简单的搜索功能,每当用户在视图上的文本框中输入一些关键字并点击提交按钮我想使用ASYNC调用预定义的网站URL TPL异步机制。当我对控制台应用程序进行相同操作时,它的作用就像一个魅力,但不适用于ASP.NET MVC3。从点击提交按钮进行TPL的异步调用点击提交按钮

我找不到原因

public ActionResult Index() 
    { 
     ViewBag.Message = "Welcome to ASP.NET MVC!"; 

     return View(); 
    } 

    public ActionResult About() 
    { 
     return View(); 
    } 

    [HttpPost] 
    public ActionResult Index(string text) 
    { 
     string[] url = { "http://www.msnbc.com", "http://www.yahoo.com", 
          "http://www.nytimes.com", "http://www.washingtonpost.com", 
          "http://www.latimes.com", "http://www.newsday.com" }; 
     Task<string[]> webTask = this.GetWordCounts(url, text); 
     string[] results = null; 
     try 
     { 
      results = webTask.Result; 
     } 
     catch (AggregateException e) 
     { 

     } 

     return View("Index", results); 

    } 

    //Taken from MSDN 
    Task<string[]> GetWordCounts(string[] urls, string name) 
    { 
     TaskCompletionSource<string[]> tcs = new TaskCompletionSource<string[]>(); 
     WebClient[] webClients = new WebClient[urls.Length]; 

     object m_lock = new object(); 
     int count = 0; 
     List<string> results = new List<string>(); 
     for (int i = 0; i < urls.Length; i++) 
     { 
      webClients[i] = new WebClient(); 

      #region callback 
      // Specify the callback for the DownloadStringCompleted 
      // event that will be raised by this WebClient instance. 
      webClients[i].DownloadStringCompleted += (obj, args) => 
      { 
       if (args.Cancelled == true) 
       { 
        tcs.TrySetCanceled(); 
        return; 
       } 
       else if (args.Error != null) 
       { 
        // Pass through to the underlying Task 
        // any exceptions thrown by the WebClient 
        // during the asynchronous operation. 
        tcs.TrySetException(args.Error); 
        return; 
       } 
       else 
       { 
        // Split the string into an array of words, 
        // then count the number of elements that match 
        // the search term. 
        string[] words = null; 
        words = args.Result.Split(' '); 
        string NAME = name.ToUpper(); 
        int nameCount = (from word in words.AsParallel() 
                where word.ToUpper().Contains(NAME) 
                select word) 
                .Count(); 

        // Associate the results with the url, and add new string to the array that 
        // the underlying Task object will return in its Result property. 
        results.Add(String.Format("{0} has {1} instances of {2}", args.UserState, nameCount, name)); 
       } 

       // If this is the last async operation to complete, 
       // then set the Result property on the underlying Task. 
       lock (m_lock) 
       { 
        count++; 
        if (count == urls.Length) 
        { 
         tcs.TrySetResult(results.ToArray()); 
        } 
       } 
      }; 
      #endregion 

      // Call DownloadStringAsync for each URL. 
      Uri address = null; 
      try 
      { 
       address = new Uri(urls[i]); 
       // Pass the address, and also use it for the userToken 
       // to identify the page when the delegate is invoked. 
       webClients[i].DownloadStringAsync(address, address); 
      } 

      catch (UriFormatException ex) 
      { 
       // Abandon the entire operation if one url is malformed. 
       // Other actions are possible here. 
       tcs.TrySetException(ex); 
       return tcs.Task; 
      } 
     } 

     // Return the underlying Task. The client code 
     // waits on the Result property, and handles exceptions 
     // in the try-catch block there. 
     return tcs.Task; 
    } 

这是我的看法 - 现在我已经硬编码的关键字作为微软

@using (Html.BeginForm("Index", "Home", new { text = "Microsoft" })) 
{ 
<input type="submit" /> 
} 

更新:它永远保持和try块内索引发布方法

+0

什么是不工作? – 2012-07-09 14:46:43

+0

对不起,不提这个问题。基本上,当我尝试在此行收获结果 results = webTask.Result;在索引后的方法它永远呆在那里 – 2012-07-09 14:52:00

+0

我认为这是我需要使用AsyncController。我对吗? – 2012-07-09 15:57:57

回答

8

我建议你使用AsyncController这个任务来避免危害ASP.NET工作线程这是可能发生的最糟糕的事情之一到ASP.NET应用程序=>耗尽工作线程。就像在沙漠中间燃尽了燃料。你绝对会死。

因此,让我们通过编写扩展方法,允许我们将传统Web客户端基于事件的模式到新的基于任务的模式开始:

public static class TaskExtensions 
{ 
    public static Task<string> DownloadStringAsTask(this string url) 
    { 
     var tcs = new TaskCompletionSource<string>(url); 
     var client = new WebClient(); 
     client.DownloadStringCompleted += (sender, args) => 
     { 
      if (args.Error != null) 
      { 
       tcs.SetException(args.Error); 
      } 
      else 
      { 
       tcs.SetResult(args.Result); 
      } 
     }; 
     client.DownloadStringAsync(new Uri(url)); 
     return tcs.Task; 
    } 
} 

有了这些扩展方法在手,我们现在可以定义视图模型将基本反映了我们的观点要求:

public class DownloadResultViewModel 
{ 
    public string Url { get; set; } 
    public int WordCount { get; set; } 
    public string Error { get; set; } 
} 

然后我们移动到一个asyncrhonous控制器将包含2个动作:标准同步Index的行动,将使搜索表单和异步Search动作,将执行实际工作:

public class HomeController : AsyncController 
{ 
    public ActionResult Index() 
    { 
     return View(); 
    } 

    [AsyncTimeout(600000)] 
    [HttpPost] 
    public void SearchAsync(string searchText) 
    { 
     AsyncManager.Parameters["searchText"] = searchText; 
     string[] urls = 
     { 
      "http://www.msnbc.com", 
      "http://www.yahoo.com", 
      "http://www.nytimes.com", 
      "http://www.washingtonpost.com", 
      "http://www.latimes.com", 
      "http://www.unexistentdomainthatwillcrash.com", 
      "http://www.newsday.com" 
     }; 

     var tasks = urls.Select(url => url.DownloadStringAsTask()); 
     AsyncManager.OutstandingOperations.Increment(urls.Length); 
     Task.Factory.ContinueWhenAll(tasks.ToArray(), allTasks => 
     { 
      var results = 
       from task in allTasks 
       let error = task.IsFaulted ? task.Exception.Message : null 
       let result = !task.IsFaulted ? task.Result : string.Empty 
       select new DownloadResultViewModel 
       { 
        Url = (string)task.AsyncState, 
        Error = error, 
        WordCount = result.Split(' ') 
         .Where(x => string.Equals(x, searchText, StringComparison.OrdinalIgnoreCase)) 
         .Count() 
       }; 
      AsyncManager.Parameters["results"] = results; 
      AsyncManager.OutstandingOperations.Decrement(urls.Length); 
     }); 
    } 

    public ActionResult SearchCompleted(IEnumerable<DownloadResultViewModel> results) 
    { 
     return View("index", results); 
    } 
} 

现在我们定义一个~/Views/Home/Index.cshtml视图将包含搜索逻辑以及其结果:

@model IEnumerable<DownloadResultViewModel> 

@using (Html.BeginForm("search", null, new { searchText = "politics" })) 
{ 
    <button type="submit">Search</button> 
} 

@if (Model != null) 
{ 
    <h3>Search results</h3> 
    <table> 
     <thead> 
      <tr> 
       <th>Url</th> 
       <th>Word count</th> 
      </tr> 
     </thead> 
     <tbody> 
      @Html.DisplayForModel() 
     </tbody> 
    </table> 
} 

当然相应的显示模板会自动为我们的模型中的每个元素被渲染(~/Views/Shared/DisplayTemplates/DownloadResultViewModel.cshtml):

@model DownloadResultViewModel 
<tr> 
    <td>@Html.DisplayFor(x => x.Url)</td> 
    <td> 
     @if (Model.Error != null) 
     { 
      @Html.DisplayFor(x => x.Error) 
     } 
     else 
     { 
      @Html.DisplayFor(x => x.WordCount) 
     } 
    </td> 
</tr> 

现在,由于搜索操作可能需要相当长的时间,因此您的用户可能会很快感到无聊,无法使用您的网页提供的其他百分之百功能。

在这种情况下,它绝对是微不足道的调用使用AJAX请求,并呈现出微调,告知用户其搜索过程中又没有冻的网页,让他们做其他事情(无须离开浏览的Search控制器动作从页面显然)。

那么,让我们来这样做吧?

我们没有在显示模板接触开始由外化的结果到部分(~/Views/Home/_Results.cshtml):

@model IEnumerable<DownloadResultViewModel> 
@if (Model != null) 
{ 
    <h3>Search results</h3> 
    <table> 
     <thead> 
      <tr> 
       <th>Url</th> 
       <th>Word count</th> 
      </tr> 
     </thead> 
     <tbody> 
      @Html.DisplayForModel() 
     </tbody> 
    </table> 
} 

,我们调整我们的~/Views/Home/Index.cshtml视图中使用此部分:

@model IEnumerable<DownloadResultViewModel> 

@using (Html.BeginForm("search", null, new { searchText = "politics" })) 
{ 
    <button type="submit">Search</button> 
} 

<div id="results"> 
    @Html.Partial("_Results") 
</div> 

和当然SearchCompleted控制器的行动,现在必须只返回部分结果:

public ActionResult SearchCompleted(IEnumerable<DownloadResultViewModel> results) 
{ 
    return PartialView("_Results", results); 
} 

现在剩下的就是编写一个简单的JavaScript来AJAX化我们的搜索表单。所以,这可能发生到一个单独的JS,将在我们的布局引用:

$(function() { 
    $('form').submit(function() { 
     $.ajax({ 
      url: this.action, 
      type: this.method, 
      success: function (results) { 
       $('#results').html(results); 
      } 
     }); 
     return false; 
    }); 
}); 

根据您是否在<head>部分或身体的最后,你可能不需要它包装在一个document.ready引用这个脚本。如果脚本最后可以从我的示例中删除包装document.ready函数。

最后一部分是给用户一些视觉指示,表明该网站实际上正在进行搜索。这可以使用我们可能订阅的global ajax event handler完成:

$(function() { 
    $(document).ajaxStart(function() { 
     $('#results').html('searching ...'); 
    }); 
}); 
+2

+1写作广泛。谢谢 – Shyju 2012-07-09 17:44:48

+0

感谢@Darin花时间给我示例代码,我非常感谢。我会从这里扩展它。谢谢 – 2012-07-09 17:45:45