views:

312

answers:

3

If you don't want any context or an example of why I need this, then skip to The question(s) at the bottom!

In a bid to keep things tidy I initially built my application without JavaScript. I am now attempting to add a layer of unobtrusive JavaScript on the top of it.

In the spirit of MVC I took advantage of the easy routing and re-routing you can do with things like RedirectToAction()

Suppose I have the following URL to kick off the sign up process:

http://www.mysite.com/signup

And suppose the sign up process is two steps long:

http://www.mysite.com/signup/1
http://www.mysite.com/signup/2

And suppose I want, if JavaScript is enabled, the sign up form to appear in a dialog box like ThickBox.

If the user leaves the sign up process at step 2, but later clicks the "sign up" button, I want this URL:

http://www.mysite.com/signup

To perform some business logic, checking the session. If they left a previous sign up effort half way through then I want to prompt them to resume that or start over.

I might end up with the following methods:

public ActionResult SignUp(int? step)
{
    if(!step.HasValue)
    {
        if((bool)Session["SignUpInProgress"] == true)
        {
            return RedirectToAction("WouldYouLikeToResume");
        }
        else
        {
            step = 1;
        }
    }
    ...
}

public ActionResult WouldYouLikeToResume()
{
    if(Request.IsAjaxRequest())
    {
        return View("WouldYouLikeToResumeControl");
    }
    return View();
}

The logic in WouldYouLikeToResume being:

  • If it's an AJAX request, only return the user control, or "partial", so that the modal popup box does not contain the master page.
  • Otherwise return the normal view

This fails, however, because once I redirect out of SignUp, IsAjaxRequest() becomes false.

Obviously there are very easy ways to fix this particular redirect, but I'd like to maintain the knowledge of the Ajax request globally to resolve this issue across my site.

The question(s):

ASP.NET MVC is very, very extensible.

Is it possible to intercept calls to RedirectToAction and inject something link "isAjaxRequest" in the parameters?

OR

Is there some other way I can detect, safely, that the originating call was an AJAX one?

OR

Am I going about this the completely wrong way?

+1  A: 

Perhaps you can add a AjaxRedirected key in the TempData property before doing the redirection?

Gregoire
But how can I do this *globally*? Rather than hand code it for this specific example?
joshcomley
yes, tempdata is the way to do this. be aware though that tempdata uses session state. If you have a web farm, you need to deal with that state accordingly
Joel Martinez
@josh: you can create an extension method . This extension method will check if IsAjaxRequest() is true or TempData[IsAjaxRequest] is true
Gregoire
A: 

One way to transfer state is to add an extra route parameter i.e.

public ActionResult WouldYouLikeToResume(bool isAjax)
{
    if(isAjax || Request.IsAjaxRequest())
    {
        return PartialView("WouldYouLikeToResumeControl");
    }
    return View();
}

and then in the Signup method:

return RedirectToAction("WouldYouLikeToResume", new { isAjax = Request.IsAjaxRequest() });

// Don't forget to also set the "ajax" parameter to false in your RouteTable
// So normal views is not considered Ajax

Then in your RouteTable, default the "ajax" parameter to false.

Or another way to go would be override extension points in your BaseController (you do have one, right?) to always pass along the IsAjaxRequest state.

..

The TempData approaches are valid too, but I'm a little allergic of states when doing anything that looks RESTful :-)

Havn't tested/prettify the route though but you should get the idea.

chakrit
Like I said in my question, there are easy ways to solve it *for this instance* by hand, but what about globally? In fact, my exact question is asking if it is possible to automate your answer!
joshcomley
@joshcomley one moment... coming up with an *automated* answer :-)
chakrit
I agree with what you said about states on something that's RESTful, which is why I am hesitant to go the TempData route. Yep, I do have a "BaseController". "Overriding extension points" sounds like the animal I was after... could you elaborate? :)
joshcomley
+1  A: 

As requested by @joshcomley, an automated answer using the TempData approach:

This assumes that you have a BaseController and your controllers are inheriting from it.

public class AjaxianController : /*Base?*/Controller
{
    private const string AjaxTempKey = "__isAjax";


    public bool IsAjax
    {
        get { return Request.IsAjaxRequest() || (TempData.ContainsKey(AjaxTempKey)); }
    }


    protected override RedirectResult Redirect(string url)
    {
        ensureAjaxFlag();
        return base.Redirect(url);
    }

    protected override RedirectToRouteResult RedirectToAction(string actionName, string controllerName, System.Web.Routing.RouteValueDictionary routeValues)
    {
        ensureAjaxFlag();
        return base.RedirectToAction(actionName, controllerName, routeValues);
    }

    protected override RedirectToRouteResult RedirectToRoute(string routeName, System.Web.Routing.RouteValueDictionary routeValues)
    {
        ensureAjaxFlag();
        return base.RedirectToRoute(routeName, routeValues);
    }


    private void ensureAjaxFlag()
    {
        if (IsAjax)
            TempData[AjaxTempKey] = true;

        else if (TempData.ContainsKey(AjaxTempKey))
            TempData.Remove(AjaxTempKey);
    }
}

To use this, make your controller inherit from AjaxianController and use the "IsAjax" property instead of the IsAjaxRequest extension method, then all redirects on the controller will automatically maintain the ajax-or-not flag.

...

Havn't tested it though, so be wary of bugs :-)

...

Another generic approach that doesn't require using state that I can think of may requires you to modify your routes.

Specifically, you need to be able to add a generic word into your route, i.e.

{controller}/{action}/{format}.{ajax}.html

And then instead of checking for TempData, you'd check for RouteData["ajax"] instead.

And on the extension points, instead of setting the TempData key, you add "ajax" to your RouteData instead.

See this question on multiple format route for more info.

chakrit
@chakrit - thanks, actually a solution like this had occurred to me whereby I inject the new isAjax parameter in on the overridden RedirectToAction method. But then I (initially) scrapped it due to the fact that when you do something like RedirectToAction("Home", new { id = 1 }), MVC uses reflection to figure out what should be in the route values and I didn't fancy injecting properties into an object. But of course that just boils down to the RedirectToAction() that takes a RouteValueCollection, at which point it should be easy to inject a new value. I was just being a complete moron! Thanks!
joshcomley
@joshcomley In that case... may I have an upvote please? :-)
chakrit
@chakrit (and anyone else) - I've gone down pretty much this route, but without using TempData, which I think is cleaner. I'll probably blog about the final solution in the coming weeks and put a link to the blog on here :) but for all intents and purposes this was the answer I was looking for!
joshcomley
@joshcomley When you're done don't forget to share it with the rest of us :-)
chakrit