I'm new to asp.net mvc, so please bear with me.
I'm using TempData when I redirect to another Action and I don't want to "dirty" the URL with information. For example:
[AllowAnonymous]
public ActionResult ConfirmationEmailSent()
{
if (TempData["Username"] != null)
{
ViewBag.Username = TempData["Username"];
return View("ConfirmationEmailSent");
}
return View("Error");
}
So far so good, the user gets a simple and innocent looking web page with some message containing his username. But if the user hits the 'refresh' button then he gets the "Error" view, because TempDate is unavailable.
I would like to have the ability to redirect to action with information not presented in the querystring and also that if the user hits the refresh button then he gets just the same page.
Any ideas how to do this? (without session)
Thank you.
What you need is a way to have information persist across more than one request in a way that is associated with the browser. QueryString and Session are your two best options.
If the view is the result of a form post, you could make it a hidden input, but the user will get a prompt when they refresh (do you want to resubmit form?), but this is not good, as you should be doing a Post/Redirect/Get (PRG).
Erick
It sounds like cookies would do what you want. Then just delete them when you're done (or don't set an expiration date on them, in which case the browser will delete them for you when the session completes).
Related
In my web app, after clicking the Submit button on an ASP.NET MVC form, the user is displayed either an Error screen or a Success screen. In the case of the Error, the user is instructed to click the Back button on the browser and fix whatever they didn't do right and try submitting again.
This works fine because when the user clicks back, all the previously entered data is still in the screen in the various fields. But in the case of the Success screen, I would like the data to be cleared if the user clicks Back, so that they cannot just accidentally re-submit the data again.
How does one do this in ASP.NET MVC?
The way you can accomplish this is to use the Post-Get-Redirect pattern. It's not asp-mvc specific but as Wiki says it
a common design pattern for web developers to help avoid certain duplicate form submissions and allow user agents to behave more intuitively with bookmarks and the refresh button.
Clicking the back is not safe way to handle this. Some browsers don't maintain the form state after submission. This pattern directly address the accidentally re-submit data issue.
In terms of code, have a look at this blog post by Jag Reehal concerning how to unit test controllers.
[HttpPost]
public ActionResult Create(SomeViewModel model)
{
if (ModelState.IsValid)
{
// do some stuff that saves your data and then...
return RedirectToAction("Success");
}
else
{
// your model is not valid so return the form back to the user for
// additional modifications.
return View(model);
}
}
Note: while the above uses ModelState for data validation, you may have your own set of validations and error checking routines which can also be used in this pattern.
I'm fetching a partial view via $.ajax() and in the situation where you set idle for 30 minutes and then try to fetch that partial view, the forms authentication has timed out and instead of getting my partial view returned to me, I'm getting the login page render into my .
Any suggestions on how to deal with a situation like this?
Thank you.
$(function () {
$("#addContact").click(function () {
$.get('/Contacts/Add', function (data) {
$("#content").html(data); <--gets login page as data
});
});
});
Does your Add Action have any non-Ajax consumers? If not, I'd suggest removing the [Authorize] attribute from the action, which would remove the timeout-redirect problem. (If you have your entire controller decorated with [Authorize], you'd need to remove the controller-level attribute and adorn all of your other actions. Annoying, I know).
For extra security, you could then do something like this to prevent non-Ajax calls from calling your Add action.
public ActionResult Add()
{
if (Request.IsAjaxRequest())
return View("Error");
return View();
}
If, on the other hand, your Add action needs to support Ajax and normal calls, one way you can address this issue is to create a new Attribute class that inherits from and overrides AuthorizeAttribute. Check out the source for guidance: http://aspnet.codeplex.com/SourceControl/changeset/view/23011#266447
You should be able to do the trick by overriding the AuthorizeCore method, like so
public class AjaxAuthorizeAttribute: AuthorizeAttribute
{
protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)
{
if (httpContext.Request.IsAjaxRequest())
return true;
return base.AuthorizeCore(httpContext);
}
}
Now you can use [AjaxAuthorize] on your controller and/or action.
To be clear, what you're doing here is giving the user an extension on their timeout if they initiate a call via Ajax. Once they refresh the page, or navigate away, they would be prompted to log back in, as normal.
Hope that helps. Let me know if you run into any issues.
Prior to making the Ajax call, can you make another one to an unauthorized controller to ensure that the user is authenticated? If they are, continue as normal, otherwise you can just show a login lightbox so you don't leave the page and maintain the user experience.
Another solution would be to add some script to your login page to check if it's being rendered within a pop-up. If it is you can use location.href() to redirect the whole page to the login page.
It depends a little on if you're ok with changing the length of time users will be logged in for. If you are, you can change your config file to something like the following...
<authentication mode="Forms">
<forms loginUrl="Login.aspx" timeout="512640" />
</authentication>
This will keep users logged in for one year. If changing the amount of time users are logged in is not an option, you would need to handle the ajax response and redirect users to a login form again.
Is it possible to block an action to be executed directly from the browser, but still be able to redirect to it from inside the site? I know it sounds stupid but this is what I want:
Let's say there are two routes, Index and Index2 of HomeController.
www.website.com/home will cause Index to be executed, I then want to execute some code and then redirect (or server transfer) to www.website.com/home/index2.
I don't want the URL www.website.com/home/index2 to be accessed directly by the user, without going to home/index first.
Is there something I can do to deactivate/activate the route at runtime?
EDIT: Sorry, this question is titled "Session Fixation" because I need Index to run prior to Index2 to ensure we are killing all Sessions in Index and renewing them before Index2 executes.
I cannot kill the session and execute the code in Index2 in the same request since Index2 needs session to do its job.
Not if you do a redirect. If you do a redirect it will appear to the controller the same way as if the request simply originated at the browser (which it does). You could gin something up with TempData -- i.e. set a flag there and only execute the action if the flag is in TempData -- but if it isn't the next request from the browser it will fail. That is, you may get another request, say via AJAX, that comes between the original and the redirect. The TempData would be wiped out by that request.
If, on the other hand, you simply want to execute the code, then don't expose Index2 as a public method and call it directly. If it's in the same controller, this should just work. You'll end up with the same URL, but if it's just getting the correct view that you want this should be ok.
If the user is redirected to www.website.com/home/index2, then they have to make the browser request even if it's automatically happening on their behalf. The only way to prevent that is to perform a check on the Index2 action that checks the HTTP Referer.
Another option would be to have Index return the ActionResult of Index2, that way the URL remains at /home/index, but the results of Index2 are returned and as far as the user is concerned they were never redirected.
You can put an ActionFilter and do your session cleanup / etc from there. This way your code will always be executed before Index2 - and before any action for which you put this attribute.
public class AbandonSessionAttribute: ActionFilterAttribute
{
public void OnActionExecuting(ActionFilterContext context)
{
if (Session.User.IsAuthenticated || Session.Count > 0) // <<-- this is the important line
{
Session.Clear();
FormsAuthentication.SignOut(); // and so on
context.Result = new RedirectResult("/Index2");
}
}
}
// now, if this is called with non-empty session, it will be cleared and user will be redirected again here
[AbandonSession]
public ActionResult Index2()
{
}
Notice the "this is the important line". There you need to detect if user is allowed to enter Index2. I do it by checking that Session is not empty. However, after the redirect MVC may add it's own Session variables, so Session won't ever be empty and the filter will redirect again and again... You'll need to find out what is OK to be presented in Session. From your question it's not clear (for me) if ANY navigation to Index2 should clear the session - i.e. during site browsing. If so, code above is OK, if not - it's up to you to decide how to detect this (for example, put something into session on some pages and remove from there on others).
Again, I don't see why Session won't work for you (and by the way TempData also works via Session). If the action filter you detect that request is not "clean" - there're login, session vars, etc - so you cleanup this and redirect once again to the same action - this causes new Session creation and so on. This time the filter doesn't find anything - since the Session has just been cleaned up - and so action is allowed to execute.
OK, this is what we ended up doing. Anyone see any caveats with this approach?
Index()
{
expire all session and forms-Authententication cookies
}
Index2()
{
if(Session.IsNewSession)
{
redirect to Index action
}
else
{
Show view
}
}
You could define routing:
routes.MapRoute(
"Index2Redirect",
"HomeController/Index2",
new { controller = "HomeController", action = "Index", id = "" }
);
EDIT:
No, it is not good.
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");
}
I am trying to get the hang of MVC framework so bear with me.
Right now, the only thing I'm using the session store for is storing the current logged in user. My website is simple. For this example, consider three domain objects, Person, Meeting, and File. Users can log in and view a "members only" profile of a meeting and can add files to it, or view a meeting's public "profile" if they aren't logged in.
So, from the meeting's private profile, with a logged in user, I have a "add files" link. This link routes to FileContoller.Add(int meetingId). From this action, I get the meeting the user want to add files to using the meeting id, but after the form is posted, I still need to know which meeting the user is adding files to. That's where my question lies, should I pass the "currently interacting with" meeting through TempData, or add it to the Session store?
This is how I currently have the Add action setup, but it's not working:
public ActionResult Add(int meetingId)
{
try
{
var meeting = _meetingsRepository.GetById(meetingId);
ViewData.Model = meeting;
TempData[TempDataKeys.CurrentMeeting] = meeting; /* add to tempdata here */
}
catch (Exception)
{
TempData[TempDataKeys.ErrorMessage] = "Unable to add files to this meeting.";
return RedirectToRoute("MeetingsIndex");
}
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Add(FormCollection form)
{
var member = Session[SessionStateKeys.Member] as Member;
var meeting = TempData[TempDataKeys.CurrentMeeting] as Meeting; /* meeting ends up null here */
if (member == null)
{
TempData[TempDataKeys.ErrorMessage] = "You must be logged in to add files to an meeting.";
return RedirectToRoute("LoginPage");
}
if (meeting == null)
{
TempData[TempDataKeys.ErrorMessage] = "An error occurred. No meeting selected.";
return RedirectToRoute("MeetingsIndex");
}
// add files to meeting
TempData[TempDataKeys.Notification] = "Successfully added.";
return RedirectToRoute("AddFiles", new {meetingId = meeting.MeetingId});
}
Edit:
Based on most of the answers, can any one provide any examples on what kind of data (other than messages) should be stored in TempData vs Session?
TempData is session, so they're not entirely different. However, the distinction is easy to understand, because TempData is for redirects, and redirects only. So when you set some message in TempData and then redirect, you are using TempData correctly.
However, using Session for any kind of security is extremely dangerous. Session and Membership are entirely separate in ASP.NET. You can "steal" sessions from other users, and yes, people do attack web sites this way. So if you want to selectively stop a post information based on whether a user is logged in, look at IsAuthenticated, and if you want to selectively show information based on what type of user is logged in, you use a Role provider. Because GETs can be cached, the only way to selectively allow access to an action in a GET is with AuthorizeAttribute.
Update In response to your edited question: You already have a good example of using TempData in your question, namely, returning a simple error message after a failed POST. In terms of what should be stored in Session (beyond "not much"), I just think of Session as a user-specific cache. Like the non-user-specific Cache, you should not put security-sensitive information there. But it's a good place to stick stuff which is relatively expensive to look up. For example, our Site.Master has the user's full name displayed on it. That is stored in a database, and we don't want to do a database query for it for every page we serve. (An installation of our application is used in a single company, so a user's full name is not considered "security-sensitive.") So if you think of Session as a cache which varies by a cookie which the user has, you won't be far wrong.
The default TempData provider uses the session so there really isn't much of a distinction, except that your TempData is cleared out at the end of the next request. You should use TempData when the data needs only to persist between two requests, preferably the second one being a redirect to avoid issues with other requests from the user -- from AJAX, for example -- deleting the data accidentally. If the data needs to persist longer than that, you should either repopulate the TempData or use the Session directly.
You can use it as per your requirement. A clarification can be,
TempData Vs Session
TempData
TempData allow us to persisting data for the duration of single subsequent request.
ASP.net MVC will automatically expire the value of tempdata once consecutive request returned the result (it means, it alive only till the target view is fully loaded).
It valid for only current and subsequent request only
TempData has Keep method to retention the value of TempData.
Example:
TempData.Keep(), TempData.Keep(“EmpName”)
TempData internally stored the value in to Session variable.
It is used to stored only one time messages like validation messages, error messages etc.
Session:
Session is able to store data much more long time, until user session is not expire.
Session will be expire after the session time out occurred.
It valid for all requests.
N/A
Session varible are stored in SessionStateItemCollection object (Which is exposed through the HttpContext.Session property of page).
It is used to stored long life data like user id, role id etc. which required throughout user session.
TempData and session, both required typecasting for getting data and check for null values to avoid run time exception.
"It doesn't work" isn't very descriptive, but let me offer a couple suggestions.
Under the hood, TempData uses Session to store values. So there isn't much difference in terms of storage mechanisms or anything like that. However, TempData only lasts until the next request is received.
If the user makes an ajax request in between form posts, TempData is gone. Any request whatsoever will clear TempData. So it's really only reliable when you're doing a manual redirect.
Why can't you just simply render the meeting ID to a hidden field in your View form? You're already adding it to the model. Alternately, add it to your route as a parameter.
I prefer to maintain that kind of data in the page itself. Render meetingID as a hidden input, so it gets submitted back to the controller. The controller handling the post can then feed that meeting ID back to whatever view will be rendered, so that the meetingID basically gets passed around as long as you need it.
It's kind of like the difference between storing a value in a global variable before calling a method that will operate on it, vs. passing the value directly to the method.
I would suggest MvcContrib's solution:
http://jonkruger.com/blog/2009/04/06/aspnet-mvc-pass-parameters-when-redirecting-from-one-action-to-another/
If you don't want full MvcContrib, the solution is only 1 method + 1 class that you can easily grab from MvcContrib sources.
The TempData property value is stored in session state. The value of TempData persists until it is read or until the session times out. If you want pass data one controller view to another controller view then you should use TempData.
Use Session when the data need for the throughout application