A smart way to handle Return URLs in an MVC environment
Asked Answered
S

10

17

A problem I come up against again and again is handling redirection to the previous page after a user runs some action such as clicking a 'Back To ...' link or saving the record they are editing.

Previously whenever I have needed to know what page to return to, I would provide a returnURL parameter to my current view.

http://blah.com/account/edit/1?returnURL="account/index"

This isn't a very clean way of handling this situation, as sometimes the return URL contains parameters such as search strings, etc, which have to be included in the URL.

http://blah.com/account/edit/1?returnURL="account/index?search="searchTerm""

Also, this creates an issue when a user can go another page forward before coming back to the page with the returnURL because you have to pass the returnURL through all visited pages.

Simply calling the browser's Back functionality isn't really sufficient either, because you might want the page to refresh, e.g. to show the edits you just saved in the previous page.

So my question is, has anyone found a smart way to handle this kind of situation, specifically in an MVC environment?

Note: I am using ASP .NET MVC so if possible I'd like answers to pertain to that, however any ideas are welcome.

Springtail answered 12/9, 2011 at 1:57 Comment(0)
D
12

What's wrong with setting a cookie, or using a session variable? The only reason you wouldn't is if you don't control the page that calls into you, in which case your only options are query strings, post values, or referrer.

Detoxify answered 15/9, 2011 at 1:35 Comment(4)
I'd add that I'd do a check for the session, then destroy it at every start of the page load process. Then set if necessary at the end of the page load process. This way a session would only be set if needed and could be checked universally on any page... HTHOctroi
Cookie does not work the same as passing a parameter in the URL. Try using cookies with the same website open in multiple tabs.Frager
Session makes a lot of sense, but what about the situation where the user is using multiple browser tabs within the same session? Won't that cause unexpected behavior when switching tabs then clicking "Back to List" sometime later?Juggler
@Juggler - there are many ways to handle this given any of the methods mentioned.. For instance, you can generate a unique value which signifies the "tab" you're going to use, and index this in your cookie variable, or session variable, or whatever so it only returns the value for the unique tab you're working in. I can think of a dozen different ways to make this work effectively.Detoxify
S
3

I thought I might add my answer to the question to see if others think it's a good idea.

I'm simply passing it through to the controller using TempViewData:

@{
   TempData["returnURL"] = Request.Url.AbsoluteUri;
}

and then accessing it in a similar way to this (in my real version I check that the key is in TempData and that the returnURL is a real URL):

return Redirect(TempData["returnURL"].ToString());

If it needs to continue on past the first page change (i.e. Search page -> Edit page -> Edit Section page) I'm adding it again

TempData["returnURL"] = TempData["returnURL"];
Springtail answered 15/9, 2011 at 2:6 Comment(0)
P
2

Check my blog post on it: Using cookies to control return page after login on asp.net mvc 3

Just like @Mystere Man mentioned, you can just use a cookie or session for it. I went for cookies back when I had a similar situation a while ago.

Prestige answered 18/9, 2011 at 22:54 Comment(0)
S
1

Try register a new route of which the url is /{controller}/{action}/{id}/returnurl/{*url} and then use a RedirectToAction in the action that accepts url as a parameter

Sulphate answered 12/9, 2011 at 3:38 Comment(1)
That won't work very well (passing an url in a route). Besides is not much different than a query string, which was already mentioned by the OP.Prestige
A
1
Request.UrlReferrer.AbsoluteUri

though i'd still argue that you shouldn't be creating your own "back" button.

According answered 12/9, 2011 at 5:48 Comment(8)
I don't see it as creating my own "back" button. I am trying to find a clean way to redirect to a page based on a user completing what they came to the edit page to do, i.e. save changes. Using the URL Referrer won't suit this situation as the user could have moved to another page from this page and back again.Springtail
shouldn't going back to any previous page recreate the page (as it makes a fresh call to Action in the Controller which returns the new view) ?Danyelledanyette
@anvarbek - yes, that is exactly my point. Going to the previous page by using a fresh call to the action will recreate the page (which is what I'm after). Pressing the 'Back' button in your browser won't make a fresh call but use the browser's cached version of the page.Springtail
Also, once reason not to use Request.UrlReferrer is that you are relying on the browser to pass along the referrer in the header, which in the case of a browser like IE8, it doesn't. Also people can get plugins to stop browsers like Firefox and Chrome from passing the referrer in the header.Springtail
@link664 - IE8 returns the referrer, not sure what you're talking about. If it didn't, a whole boat load of stuff on the web would break. You're right about plugins though, but the same could be said about javascript and just about anything else. If someone wants to break your app, they can.Detoxify
@Mystere Man, tried it in my app. Worked fine with Firefox. Returned null in my controller with IE8.Springtail
@link664 - then you're doing something wrong. Lots of code out there depends on referrer, and would break if IE didn't use it.Detoxify
@Mystere Man your answer on this question is right, but your comments above are wrong / you can't rely on referrer being passed to the application. Sure there might be something else for the referrer not working for the OP's IE8, but you just can't depend on it being sent.Prestige
I
1

Use an interceptor or an aspect:

  1. Intercept each request in some fashion (e.g., a @Before aspect) and save the requested URL to the session, overwriting it each time
  2. In your view layer, access that Session object as needed, in your case for the back link.

This kind of design allows you to always have the most recent request available if you want to use it. Here's an example to write an aspect / interceptor in .NET. Additionaly, PostSharp is a .NET aspect project.

Indentation answered 15/9, 2011 at 3:22 Comment(0)
N
1

At present, a quick and dirty method has eluded me... so I'm using a practical method.

On a conceptual level, the 'back-ability' of a page should be determined by the page that you're currently on. The View can infer this (in most cases) if the parameters captured in the Controller are passed to it via the ViewModel.

Example:

Having visited Foo, I'm going to Bar to view some stuff, and the back button should return to Foo.

Controller

public ActionResult Foo(string fooId) // using a string for your Id, good idea; Encryption, even better.
{
    FooModel model = new FooModel() { fooId = fooId }; // property is passed to the Model - important.
    model.Fill();
    return View("FooView", model);
}

public ActionResult Bar(string fooId, string barId)
{
    BarModel model = new BarModel() { fooId = fooId; barId = barId };
    model.Fill()
    return View("BarView", model)
}

ViewModels

public class FooModel
{
    public string fooId { get; set; }

    public void Fill()
    {
        // Get info from Repository.
    }
}

public class BarModel
{
    public string fooId { get; set; }
    public string barId { get; set; }

    public void Fill()
    {
        // Get info from Repository.
    }
}

View (Partial) // No pun intended... or maybe it was. :)

Your BarView can now interpret from its model where it needs to go back to (using fooId).

On your BarView (using MVC2 syntax):

<a href="<%= string.Format("/Foo?fooId={0}", Model.fooId) %>">Back</a>

You can use Html.ActionLink as well.

Alternatively:

You can inherit your ViewModels from a BaseViewModel, which can have a protected property returnURL. Set this where necessary.

Example:

On your ViewModel:

public class BarModel : BaseViewModel
{
    public string fooId { get; set; }
    public string barId { get; set; }

    public void Fill()
    {
        returnURL = string.Format("/Foo?fooId={0}", fooId)
        // Get info from Repository.
    }
}

On View:

<a href="<%=returnURL %>">Back</a>
Nummulite answered 15/9, 2011 at 13:53 Comment(0)
K
1

Would this be better handled by partial actions that display without leaving the page and using JQuery to make a dialog/wizard workflow?

Then you only need to react to the 'Finish' button on the dialog to refresh the original view.

Kev answered 19/9, 2011 at 11:2 Comment(0)
I
1

For the part of your question regarding "saving the record they are editing" I would think the post-redirect-get (PGR) pattern would apply to you.

This might be a good place to read about it if you are not familiar with it.

Ingvar answered 21/9, 2011 at 17:22 Comment(0)
B
-1
  1. Encode the returnUrl using Url.Encode(returnUrl) for inclusion in the URL.
  2. When ready to redirect, use Url.Decode(returnUrl) and use the value for the actual redirect.
Blanchard answered 21/9, 2011 at 18:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.