1

基本的に、ユーザーがビューのテキスト ボックスにキーワードを入力して送信ボタンをクリックするたびに、単純な検索機能を実装したいと考えています。コンソール アプリケーションで同じことを行うと、魅力的に機能しますが、ASP.NET MVC3 では機能しません。

理由がわかりませんでした

 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;
    }

これが私の見解です。今のところ、キーワードを microsoft としてハードコーディングしています。

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

更新: Index Post メソッドの try ブロック内に永久に残ります

4

1 に答える 1

9

ASP.NETアプリケーションに起こりうる最悪の事態の1つであるASP.NETワーカースレッドを危険にさらすことを避けるために、このタスクにAsyncControllerを使用することをお勧めします=>ワーカースレッドが不足します。砂漠の真ん中で燃料が切れたようなものです。あなたは間違いなく死ぬ。

それでは、従来の WebClient イベント ベースのパターンを新しいタスク ベースのパターンに変換できるようにする拡張メソッドを作成することから始めましょう。

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; }
}

次に、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>

さて、検索操作には非常に長い時間がかかる可能性があるため、Web ページが提供する他の 100 分の 1 の機能のいくつかを使用できずに、ユーザーはすぐに飽きてしまう可能性があります。

この場合Search、AJAX リクエストを使用してコントローラー アクションを呼び出し、スピナーを表示して検索が進行中であることをユーザーに通知することは非常に簡単ですが、Web ページをフリーズして他のことを実行できるようにすることはありません (明らかにページから移動する必要はありません)。 .

では、そうしましょう。

~/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);
}

あとは、検索フォームを AJAX 化する単純な JavaScript を記述するだけです。したがって、これは、レイアウトで参照する別の 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 関数を削除できます。

そして最後の部分は、サイトが実際に検索を実行していることをユーザーに視覚的に示すことです。これは、サブスクライブできるグローバル ajax イベント ハンドラーを使用して実行できます。

$(function () {
    $(document).ajaxStart(function () {
        $('#results').html('searching ...');
    });
});
于 2012-07-09T17:03:21.960 に答える