I have a requirement to implement Back button functionality in MY MVC2 application(with explicit Back buttons in the application, not via browser back button).
I have gone through the link here Back button functionality using ASP.net MVC which advises to use Request.UrlReferrer. and it works well whenever I have a HTTP Get (as it comes up with querystring.) but I face issue when previous page was a HTTP POST with no querystring.
Has anyone worked on a solution like this?
We have initial thoughts of generating and storing links like Stack, and popping out urls one by one when user clicks on 'Back' button. But if there is a better solution\design pattern which helps me achieve that?
You could store the returnurl to actionresult routevalues and have a html helper for back link.
Helper:
public static class HtmlHelpersExtensions
{
public static MvcHtmlString BackLink(this HtmlHelper helper,string defaultAction)
{
string returnUrl = HttpContext.Current.Request["returnUrl"];
// If there's return url param redirect to there
if (!String.IsNullOrEmpty(returnUrl))
{
return new MvcHtmlString("<a href=" + returnUrl + " >Back</a>");
}
// if user just posted and there's no returnurl, redirect us to default
else if (HttpContext.Current.Request.HttpMethod == "POST")
{
return helper.ActionLink("Back", defaultAction);
}
// we didn't post anything so we can safely go back to previous url
return new MvcHtmlString("<a href=" + HttpContext.Current.Request.UrlReferrer.ToString() + " >Back</a>");
}
}
Controller:
public ActionResult MyAction(string returnUrl)
{
return View(new MyModel());
}
[HttpPost]
public ActionResult MyAction(MyModel mm, string returnUrl)
{
if (ModelState.IsValid)
{
// No returnurl, redirect us to somewhere
if (string.IsNullOrEmpty(returnUrl))
return RedirectToAction("Index");
// redirect back
return Redirect(returnUrl);
}
return View(mm);
}
Usage:
#Html.ActionLink("MyAction", "MyAction", new { returnUrl = Request.RawUrl.ToString() })
Backlink in MyAction.cshtml
#Html.BackLink("Index")
Now the helper decides whether it uses returnUrl param, default action or takes you back via urlreferrer.
Related
I'm using returnUrl to return to the last requested page when logging in after being logged out due to inactivity. In itself this works fine, unless the user does not requests a page, but some other action. What I would like is that in case the first action after SessionTimeOut is for example "addItem", the returnUrl is set as "controller/index" instead of "controller/addItem".
After SessionTimeOut the application does not automatically redirect to the login page. The redirect happens as soon as the user makes a new request after SessionTimeOut (as requested by client). So when the user clicks a menu item, the returnUrl is set to the requested page. Great so far.
The issue is though when the user click for example the addItem button that the returnUrl is set as the action method linked to that button. I don't understand how I can exclude this kind of methods from returnUrl.
This is a very scaled down example of the application, to give an idea of how it hangs together.
MainController:
public ActionResult MainPage(){
return View();
}
public ActionResult addItem(){
--- Do Something ---
}
public ActionResult SecondPage(){
return View();
}
Login method:
public ActionResult Login([Binde(Include = "UserName, Password")]LoginModel model, string returnUrl)
{
if(Modelstate.IsValid)
{
--- Do Validation ---
return Redirect(returnUrl);
}
}
In case the user is on the MainPage and makes after SessionTimeOut the request for "SecondPage", the returnUrl should be and is "controller/SecondPage".(OK)
In case the suer is on the MainPage and makes after SessionTimeOut the request for "addItem", returnUrl should be "controller/MainPage" and not "controller/addItem"
One quick solution is to put Action Filters into effect.
As you might know, an action filter is an attribute that you can apply to a controller action -- or an entire controller -- that modifies the way in which the action is executed. More details, see documentation here
In this case, you need to override OnActionExecuting method, is called before a controller action is executed, so that prior to execution, you can decide to redirect or not based on the return value.
I provide you with a simple example :
public class myCheckUrlActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
List<string> avoidList = new List<string> { "addItem", "removeItem"};
foreach(string i in avoidList)
{
if(filterContext.HttpContext.Request.Url.Query.Contains(i))
{
filterContext.Result = new RedirectResult("/Home/MainPage") ;
break;
}
}
}
}
Now, Let's apply this filter to all action methods inside your Home Controller, though you may apply it individually to only certain action methods.
[myCheckUrlActionFilter]
public class HomeController : Controller
{ .... }
Access to HttpContext inside the body of the filter, enables to extract session, and even current controller and action name. Therefore, you can check session state here in addition returnUrl existence to further decide on redirect. So, customize it as you need.
Hope this helps.
The following code snippet is from my ASP.NET MVC 5 application:
public ActionResult Ask(string id) {
if (!this.User.Identity.IsAuthenticated) {
string retUrl = Request.Url.AbsoluteUri;
return RedirectToAction("Login", "Account", new { returnUrl = retUrl });
}
...
}
The idea is, if the user has not yet logged in, he will be taken to the login page and subsequently be returned back to this "Ask" page.
When the user enters, for example, http://example.com/Home/Ask/12345678, method Ask() gets invoked with the correct value for id. The user is now redirected to the login page.
After Login() code in AccountController successfully authenticates the user, it calls ReturnToLocal(), passing in the url that we expect (http://example.com/Home/Ask/12345678). However, instead of invoking the Ask() method , ASP .NET somehow ends up invoking Index() method.
Appreciate your help in understanding why my redirection is broken. Regards.
Since you did not provide us neither the Login action method nor RedirectToLocal method I am assuming you are using the AccountController from MVC5 template.
If so
private ActionResult RedirectToLocal(string returnUrl)
{
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
return RedirectToAction("Index", "Home");
}
You can see that RedirectToLocal method checkes if the returnUrl parameter is 'localUrl'.
If not it does invoke Index.
In your Ask method you are passing string retUrl = Request.Url.AbsoluteUri as returnUrl which is not local (starts with http://)!
Try string retUrl = Request.Url.PathAndQuery
I have an MVC5 application which has several controllers, scaffolded with EF6 CRUD actions and associated views. One of these controller/view sets is used for managing a table of patient identifiers, and on completion of an edit or delete, the controller returns an action link to the identifiers index view, as expected.
However, the patient identifiers are also displayed on the various views of the patients controller, and from the Patient.Edit view I have Html.ActionLink calls to the identifier controller's edit or delete actions. When the latter are called from the Patient.Edit view, I would like them to return to that on completion.
Is there any way I can accomplish this?
Yes, but this is always a manual process. There's nothing built into MVC specifically for return URLs.
Essentially, your links to edit/delete will need to include a GET param, usually called returnUrl, though the name doesn't matter, which will be set to the current page URL. For example:
#Html.ActionLink("Edit", new { id = patient.Id, returnUrl = Request.RawUrl })
Then, your edit/delete GET action should accept this parameter, and set a ViewBag member:
public ActionResult Edit(int id, string returnUrl = null)
{
ViewBag.ReturnUrl = returnUrl;
return View();
}
In your edit form, add a hidden field:
#Html.Hidden("returnUrl", ViewBag.ReturnUrl)
In your POST edit action, again, accept the param:
[HttpPost]
public ActionResult Edit(int id, Patient model, string returnUrl = null)
But inside this action is where you'll do something different now. Typically, when you've got a successful post and have saved the object or whatever, you then do something like:
return RedirectToAction("Index");
However, instead, you should now check to see if returnUrl has a value, and if it does, redirect to that instead:
if (!string.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
return RedirectToAction("Index");
The MVC5 with Identity sample project has a nice helper method that it uses:
private ActionResult RedirectToLocal(string returnUrl)
{
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
That would just go into your controller and basically does the same as I've already described with two notable differences:
It uses Url.IsLocalUrl to check that the return url is actually a URL on this site. That's a smart check, as since this is initially passed in the query string of the URL, it's open to be manipulated by a user.
It encapsulates the logic, so you don't have to remember how to this should be handled. When you have a successful POST, you simply return RedirectToLocal(returnUrl), and if there's a return URL set, it will be used. Otherwise, the fallback redirect will used.
This is how I did it in one of my projects:
public ActionResult Edit(int id, string returnUrl)
{
// find the model (code not shown)
return View(model);
}
In the Edit view you don't need to do anything special, in the Post Action you have
[HttpPost]
public ActionResult Edit(Model model)
{
if (ModelState.IsValid)
{
// save Model...
return Redirect(Request.Params["returnUrl"]);
// Request.Query will work as well since it is in the querystring
// of course you should check and validate it as well...
}
// else return the View as usual, not shown
}
To use it, when creating the "Edit" link from your pages you simply need to specify the extra returnUrl parameter:
#Html.ActionLink("Edit", "Edit",
new { controller = "YourController",
returnUrl = Url.Action("Index", "ThisController",)
})
Hope it helps.
New to MVC and having trouble trying to "Logout" of my site. I have a LogOn() method and view that comes up first to show the Log On page. After I log on then I go to a another page with a button to log out. (note: this is not likely directly related to logging in/out. that is just the story I'm working on)
<asp:Content runat="server" ContentPlaceHolderID="GlobalNavbarPrimary" ID="Content3">
Logout
</asp:Content>
When I press the button then it correctly finds my SignOut method. However, after I logout I try to run a redirect to go back to the LogOn action. I'm not understanding why my LogOn method is not run/found. Here are my LogOn methods and my SignOut method.
public ActionResult LogOn()
{
return View();
}
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
string sourceIp = string.Empty;
if (!string.IsNullOrEmpty(Request.UserHostAddress))
sourceIp = Request.UserHostAddress;
try
{
XX.User user = new XX.User();
XX.SessionKey sk = null;
sk = user.Logon(model.EmailAddress, model.Password, sourceIp);
Session[MyAuthorizeAttribute.MySessionKeyName] = sk.Value;
return RedirectToAction("Index", "Message");
}
catch (Exception ex)
{
ModelState.AddModelError(ex.Message, "Fatal Error");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
public ActionResult SignOut()
{
CM.User user = new CM.User(Key);
user.Logout(Key);
// This does not go to the LogOn action!!!!!
return RedirectToAction("LogOn", "Account");
What is also troubling is that the address bar shows http://localhost:1159/#/Account/SignOut, whereas I was expecting to see http://localhost:1159/#/Account/LogOn (or ....//localhost:1159/)
Any insight (or suggestions for debugging strategies) would be most welcome.
I found that if I removed jQuery-mobile from my master page then it works as expected. I had seen some strange things, such as the source of the page (View Source) not matching what I was looking at (actually showing the source of a different page) - an impossibility, so I thought.
I'll have to look at jQuery-mobile closer. I know it is in beta, so perhaps I just hit a known issue.
I am new to ASP.MVC. My background is in ASP.NET Web Forms, I think this is what is causing my confusion. I understand that the "M" basically represents the data source, the "V" represents the resource I'm requesting and the "C" dictates what gets shown to an end-user. But then I get confused.
For instance, I'm just trying to create a Login screen. I envision a user visiting "http://www.myapp.com/Account/Login" and they will be presented with a traditional Login screen. To accomplish this, I have added the following in the RegisterRoutes method in my Global.asax file:
routes.MapRoute(
"Login",
"{controller}/{action}",
new { controller = "Account", action = "Login", id = "" }
);
The Login action executes, but this is where I get confused. You see, the first time the login screen loads, I would expect to just show a username/password field. Then on post, I would expect the form to be validated and processed. In an attempt to do this, I have created the following method:
public ActionResult Login()
{
bool isFormValid = ValidateForm();
if (isFormValid)
LoginUser();
else
ShowErrors();
return View();
}
My confusion rests with the Login action. Initially, there is no data. But the next time, I want to validate the data. How do I determine if the Action is a postback or not?
Thank you!
The easiest way to handle this is with two actions: one for get, one for post. Use the AcceptVerbs attribute to control which gets invoked depending on the method. BTW, the default routes should work just fine for this case since when the controller and action is supplied it gets directed as you would expect. I thought that this scenario was also covered in the project template -- did you set up a project using the template or an empty one?
[AcceptVerbs( HttpVerbs.Get )]
public ActionResult Login()
{
}
[AcceptVerbs( HttpVerbs.Post )]
public ActionResult Login( string userName, string password )
{
}
You need two different methods, for Post and Get.
[AcceptVerbs (HttpVerbs.Get]
public ActionResult Login ()
{
return View ();
}
[AcceptVerbs (HttpVerbs.Post]
public ActionResult Login (FormCollection form)
{
if (AuthenticationSuccess ())
return RedirectToAction ("Account");
else
return View ();
}
For Post version you can use the model binding mechanism:
public ActionResult Login (LoginModel loginModel)
Or
public ActionResult Login (string LoginName, string Password)