ASP.NET MVC - How to maintain ModelState from a different controller? - asp.net-mvc

I have a HomeController with an Index action that shows the Index.aspx view. It has a username/password login section. When the user clicks the submit button, it POSTs to a Login action in the AccountController.
<% Html.BeginForm("Login", "Account", FormMethod.Post); %>
In that action, it tests for Username/Password validity and if invalid, sends the user back to the Login page with a message that the credentials were bad.
[HttpPost]
public ActionResult Login(LoginViewModel Model, string ReturnUrl)
{
User user = MembershipService.ValidateUser(Model.UserName, Model.Password);
if (user != null)
{
//Detail removed here
FormsService.SignIn(user.ToString(), Model.RememberMe);
return Redirect(ReturnUrl);
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
// If we got this far, something failed, redisplay form
return RedirectToAction("Index", "Home"); // <-- Here is the problem. ModelState is lost.
}
But here's the problem: the ValidationSummary is always blank because we're losing the Model when we RedirectToAction.
So the question is: How do I send the user to the action on a different controller without a Redirect?

As others have said it's common to return the view if validation fails but as you are calling from your account controller you will want to specify the full path of your view
return View("~/Views/Home/Index.aspx", model);
Or
It is also common to have a seperate login page and redirect to that page if the login fails. Both pages will submit to the same login action. Facebook does this for example.
Or
As you only want to display an error message
return RedirectToAction("Index", "Home", new { LoginAttempts = 1 });
then in your Index action read the LoginAttempts parameter and choose to display the error message accordingly.

Use TempData to save state between requests. Use special attributes for convenience as shown here.
Few moments to mention:
Don't return View directly from your POST-action, respect Post-Redirect-Get pattern.
Don't overuse TempData. It's only supposed to save model state right before redirect and to retrieve it right after being redirected.

Well you could always do this
return View("~/Views/Home/Index.aspx", myModel);
It's not a real redirect, the clients url will still point to /login/ but at least you have your modalstate

Three options
You could call the action directly, but the client side will not have its URL changed. So instead of calling RedirectToAction you could call the Index() method of the HomeController class directly.
HomeController c = new HomeController();
c.ViewData = this.ViewData;
return c.Index(data);
The one is a bit tricky. Maybe you will have to set other things as well apart from ViewData which is needed for ModelState.
You could as well use TempData dictionary and fill it with whatever data you want and use that.
The simplest one where you provide full path to the view
return View("~/Views/Home/Index.aspx", data);
A better suggestion used by big players
If we look at how other sites do this kind of scenario. Take for instance Twitter (As #David says Facebook apparently does it the same). You can sign in from the Home/Index action (so to speak if it was developed using Asp.net MVC). But when login fails it displays a separate login page, that displays validation errors. In your case it would be Account/SignIn. Which would make sense and you could directly return its view with validation errors. When everything would be ok, you'd do it as you do it now. Redirect back to Home/Index.

Try using
return View("Index", "Home", Model)

Related

My ASP.NET MVC app takes me back to the login page when I access the home controller even though I'm logged in

When I log in as a user, and type the url http://localhost:50562/Home/Index into the address bar, the browser takes me to the login page, which is my home page, but the funny thing is that the message "welcome, user!" and Logout link are still there in the upper right corner. When I click on the other links in the nav bar, the session continues normally, meaning, I was never logged out.
Can someone tell me how to configure the routing engine to take me to another page when I access the Home controller while being logged in?
You can achieve this by modifying the POST method for Login in the Account Controller (assuming you are using MVC 4 and the Simple Membership it uses).
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
{
return RedirectToLocal(returnUrl);
}
...
}
You can set the routing in a few ways.
return RedirectToAction("Profile", "Home"); ... where Profile is a Controller
return View(returnUrl); ... where you have set and assigned the variable in your method
or retain as above with RedirectToLocal(returnUrl);
Good luck.
What you're describing happens because the user cookie isn't read until after the redirect to the home page is initiated. The best way to initialize your membership provider when users may enter on different pages is to use a global filter.
You can add this functionality for the SimpleMembership provider in the RegisterGlobalFilters method in the FilterConfig class in the App_Start folder
filters.Add(new YourAppNameSpace.Filters.InitializeSimpleMembershipAttribute());
Using a filter keeps you from having to repeat yourself with decorators all over your controller classes. It's easy to overlook adding decorators and, when they aren't there, your site will have unexpected bugs.
Thank you for your answers, but it seems that the project had a SessionObject class all along, which was handled by the controller. I am not the original author of the project, so it took me a while to figure it out. I was tasked to give it some additional features and bugfixes.
I ran into the method:
#region getSessionObject()
protected SessionObject getSessionObject() {
SessionObject loReturnValue = null;
if (Session[SessionObject.SESSION_CONSTANT] != null)
loReturnValue = (SessionObject) Session[SessionObject.SESSION_CONSTANT];
return loReturnValue;
}
It was all I needed to call in the index action of my home controller to check whether the user is logged in or not.

ASP.NET MVC How to Prevent re-POST

Using MVC 4.
When an order is placed on our site the order is POSTed to:
[HttpPost]
public ActionResult ConfirmOrder(ABCModel model)
{
//Do Stuff
return View("ConfirmedOrder", model);
}
The user sees the Confirmed page.
If they press REFRESH in their browser, the page POSTs again.
Is there a way in MVC to prevent the POST again, perhaps in a redirect or some sort?
Instead of doing
return View("ConfirmedOrder", model)
separate your confirmation logic into a controller and do
return RedirectToAction("ConfirmOrderActionName").
Here your ConfirmOrderActionName controller can retrieve the order information from the data store and sent it to its own view, or your ConfirmedOrder view.
P.S.
Note that RedirectToAction() helper method also returns a type of ActionResult (just like returning a View() does).
If you're interested see:
MSDN: Controllers and Action Methods in ASP.NET MVC Applications and MSDN: ActionResult Class
You might want to redesign the logic a little bit. It is a command problem in Shopping Cart check out.
Here is how most Shopping Cart works -
Step 1. Cart (Create a Session here)
... Shipping, Payment and so on
Step 2: ConfirmOrder - Get (If no Session, redirect to Cart page.)
ConfirmOrder - Post (If no Session, redirect to Cart page. If valid and
check out successful, redirect to Complete page)
Step 3: Complete (Clear the Session)

Best way to implement form in Layout page in ASP.Net MVC 3 project

My site has a login box in the Layout page on which every page is based. When the Login button is clicked, no matter it is successful or failure, the user will be sent to the same page where the Login button is clicked.
The login form is posted to Account.Login action method. However, how can I return the correct view at the end of this action method? I also want to show error information beside the login box ie: wrong username/password in the case of failure to login.
What's the best way to design and implement this?
[HttpPost]
public ActionResult Login(LoginModel model)
{
if(ModelState.IsValid)
{
//TODO : Log user in
return View("ValidModelView");
}
// If login model is not valid
return View("InvalidModelView");
}
One approach might be this. However, I am not sure whether it is the best one.
Just do a Redirect on successful login.
return RedirectToAction("Index", "Home");

How can I make an MVC POST return me to the previous page?

I have the following action which is called from a screen with a list of records.
[HttpPost]
//[Authorize(Roles = "admin")]
public ActionResult Edit(EditViewModel itemView)
{
Once the action has completed I would like to return to the page where the action was called from. However I don't want to refresh that page. I just want to go back to the populated screen using something similar to the "Previous" button in the browser. Right now when I click "save" my action does the following which is not what I want:
return RedirectToAction("Index");
Is there some way to Redirect to previous page with MVC3?
Something like the Stackoverflow functionality after I click edit to edit an answer where it returns to the post.
Instead of a redirection,
[HttpGet]
public ActionResult Index()
{
/// preform any processing necessary for your index page on GET
return View("Index");
}
[HttpPost]
public ActionResuit Edit(EditViewModel itemView)
{
if (ModelState.IsValid)
{
/// do whatever you want with your model...
}
// return the contents as they'd be rendered by the Index action
return Index();
}
Note that with this method the URL in the browser will still display the Edit url (like /area_name/edit), but you can fix that by:
Using a redirect (which you've said you don't want to do)
Using JavaScript to update the URL, or use history.back() as #AlanStephens suggested
Probably other methods that don't immediately come to mind.
However, I'd question whether this is really the best approach. Typically, users expect different URLs to do different things.
Or, if I understand you correctly and the edit action is being called from the Index page,
[HttpGet]
public ActionResult Index()
{
return View();
}
[HttpPost] /// from a form on Index
public ActionResult Index(EditViewModel model)
{
if (ModelState.IsValid)
{
////
}
return View();
}
and just take the /Edit out of play entirely. Again, I don't really care for this approach.
Based off the code you've given, it looks like you've got a paginated screen, with the ability to click edit on each row. Here's how I've solved this problem in the past.
On the Index page, when the page loads, whether it be from the main index or a paging method, add the following:
Session["CurrentUrl"] = Request.Url.ToString();
So now, at the end of the POST method for your edit page, do:
return Session["CurrentUrl"] == null ?
Index() :
Redirect(Session["CurrentUrl"]);
What you described is easily achieved using ajax calls. That way you perform whatever action you like and afterwards (on successful response), you can easily navigate from the current page, using javascript.
If you POST to a page and in response you return the same view you receive with a GET request (index page), then some users might hit F5 to reload that index page and get a warning in the browser, which actually says it will send the POST request again. This is pretty confusing for users and not really user friendly (not to mention numerous concerns related to idem-potency of it).
Although you don't like the redirect approach, because of the additional response, I guess, I should say that, in MVC, this is the correct way to do it, assuming you don't want to use ajax calls.

How to create a view that is not accessible directly but can only be redirected to?

I'm currently working on the user registration in my project. After the registration is done I wish to show some confirmation to the user. I decided to create another view. That's fine.
Now, if after the registration I just return the view like:
public class MyController : Controller
{
[AcceptVerbs (HttpVerbs.Post), ValidateAntiForgeryToken]
public ActionResult Registration (FormCollection form)
{
/* Some logic goes here */
return View ("ConfirmationView");
}
}
Everything is working as desired. No changed url in the title bar. But... If I click the refresh button, the browser will submit the data from the form again which I do not want.
Then I decided to create a separate action, but that means it will produce a new url in the address bar. I do not want the user to click refresh now because this view will not be able to sensibly display the confirmation information again. Is there any way to make an action not accessible directly? Or at least any way to determine whether it was called directly or by redirection? In the latter case I would just take the user away from that page to maybe the home page.
Any way to accomplish this?
So I found the solution myself.
One can use TempData to detect the repeated or external action calls.
public class MyController : Controller
{
[AcceptVerbs (HttpVerbs.Post), ValidateAntiForgeryToken]
public ActionResult Registration (FormCollection form)
{
/* Some logic goes here */
TempData["RedirectCall"] = true;
return RedirectToAction ("Confirmation");
}
[AcceptVerbs (HttpVerbs.Get)]
public ActionResult Confirmation ()
{
if (TempData["RedirectCall"] == null)
return RedirectToAction ("StartPage", "Home");
return View ();
}
}
Nice and simple. :)
One way to solve your problem is to attach a guid or similar type of "random" data to a user session, and check for a valid session when the page is requested. If there is none, you redirect to a page saying that this url is not available at the moment, and that the user will soon be redirected (and then redirect to home after 5 seconds or so using js).
Roughly it would work like this:
When the user is registered, a session cookie is created with for example a GUID. The GUID is also stored in a database table, in which you have one column for the UserID primary key and one for the GUID. You also create an authentication cookie, thus logging the user on to your site.
When all datacalls etc are done, the user has been successfully registered and so on, you redirect to the confirmation page.
When the confirmation page is loaded, the user is automatically logged on (because you created the authentication cookie in step 1). You can then check for a row in the UserID-GUID table corresponding to the logged on user.
a) If there is such a row, you delete the row, and display the confirmation page with all the information.
b) If there is no such row, you display the error message and redirect. As you deleted the row when you showed the message the first time, the user will not be able to access the confirmation page again.
Note: If you use this approach (or some other that makes the confirmation page available only once) you should make sure that it is clearly stated on the confirmation page that the user won't be able to access that page again.
if(Request.UrlReferrer == null)
{
return RedirectToAction("Index");
}

Resources