How do I implement an interstitial "loading..." page in ASP.NET MVC?

intoOrbit picture intoOrbit · Oct 30, 2009 · Viewed 7.3k times · Source

I have a SearchController with an action that can execute some long running searches and return a results page. The searches can take anywhere from 1 to 60 seconds. The URL for a search is an HTTP GET request of the form: http://localhost/Search?my=query&is=fancy

The experience I'm looking for is similar to the many travel sites that are out there. I would like to show an intermediate "Loading..." page where, ideally:

  1. The user can reload the page without restarting the search
  2. Once the back-end search is finished, the user is redirected to the results
  3. The experience degrades for browsers with JavaScript disabled
  4. The back button / browser history should not include this interstitial page.
  5. In the case of a short search (1 second), it doesn't have a significant impact on either the time to get to the results OR the experience (significantly ugly page flashes, whatever)

Those are nice-to-have's. I'm open to all ideas! Thanks.

Answer

daveidmx picture daveidmx · Oct 30, 2009

In order to keep it javascript-less, you can break the search into multiple actions.

The first action (/Search/?q=whodunit) just does some validation of your parameters (so you know if you need to re-display the form) and then returns a view which uses a meta-refresh to point the browser back to the "real" search action.

You can implement this with two separate controller actions (say Search and Results):

public ActionResult Search(string q)
{
    if (Validate(q))
    {
        string resultsUrl = Url.Action("Results", new { q = q });
        return View("ResultsLoading", new ResultsLoadingModel(resultsUrl));
    }
    else
    {
        return ShowSearchForm(...);
    }
}

bool Validate(string q)
{
    // Validate
}

public ActionResult Results(string q)
{
    if (Validate(q))
    {
        // Do Search and return View
    }
    else
    {
        return ShowSearchForm(...);
    }
}

But this gives you some snags as far as refreshing goes. So you can re-merge them back into a single action which can signal itself of the two-phase process using TempData.

static string SearchLoadingPageSentKey = "Look at me, I'm a magic string!";

public ActionResult Search(string q)
{
    if (Validate(q))
    {
        if (TempData[SearchLoadingPageSentKey]==null)
        {
            TempData[SearchLoadingPageSentKey] = true;
            string resultsUrl = Url.Action("Search", new { q = q });
            return View("ResultsLoading", new ResultsLoadingModel(resultsUrl));
        }
        else
        {
            // Do actual search here
            return View("SearchResults", model);
        }
    }
    else
    {
        return ShowSearchForm(...);
    }
}

This covers points 2, 3, 4 and arguably 5.

To include support for #1 implies that you're going to store the results of the search either in session, db, etc..

In this case, just add your desired cache implementatin as part of the "Do actual search here" bit, and add a check-for-cached-result to bypass the loading page. e.g.

if (TempData[SearchLoadingPageSentKey]==null)

becomes

if (TempData[SearchLeadingPageSentKey]==null && !SearchCache.ContainsKey(q))