Imagine a page where the user can update, delete, edit or even vote the input. Since each action will be handled by a different ActionResult, the url of the page will have to change in case there are errors. My question is simple: Say your page is "http://localhost/Input/" and when you there are some errors, I want to redirect to "http://localhost/Input/Error" and display an error message. I don't want to use sessions for this therefore please don't show me this link. Basically, want I want to do this is something similar:
public ActionResult Create(FormCollection form) {
try {
// some code here
return RedirectToAction("Index");
}
catch () {
string errorMessage = "You have done something bad!";
// and I want to pass this string to my Error Action.
return RedirectToAction("Error");
}
}
The Solution I've Used
I know that I said I don't want to use TempData, but apparantly that is the best option and I've used that for my issue. As for the answer I've chosen DigBySwift's answer because that's the most logical thing you can do if you don't want to use Session for this type of operation.
As you say, if you don't want to use sessions then TempData is not an option. Ideally, you should try not to pass the error message to the next page. Instead store the error message(s) in a resource file.
You could pass an id/enum to you next page and then retrieve the error based on the passed parameter:
return RedirectToAction("Error", new { ErrorId = 4 });
public ActionResult Error(int errorId){ ... }
Edit: An alternative (since a db write-read would be expensive considering) would be to write the message to a cookie and then retrieve it after the redirect, and then delete the cookie. This is not great and I personally would not use this. But if you need to customise the message, it is an option.
Is there an option of saving the error message in database from the sender page and getting it back on the other page ?
I know its similar to the one posted above but it will be more dynamic as you dont need to put the message in resource file and you can save the error message in db and get that on other page
I'd like to suggest an alternative way. I use AJAX to do these types of operations, thus instead of redirecting user to another view, I can return a view, or mostly a partial view from my controller. Thus I can use that string message right inside my view using ViewBag.
public ActionResult Create(FormCollection form) {
try {
// some code here
return View('CreationSuccess');
}
catch () {
ViewBag.errorMessage = "You have done something bad!";
// and I want to pass this string to my Error Action.
return View('CrudError');
}
}
Related
I have a page that when accessed has some sensitive information. I understand there are better ways to go about this like Active Directory, etc, but I'm aiming for the simplest way possible at the moment.
Is there a way to prompt the user for a string via textbox, whether it be with an alert or any other method, that will pause the DOM to be displayed and accessed via "view page source"? So that if entered incorrectly, the view is not returned?
Could someone please provide a code example?
The page I have currently is being accessed by this actionresult:
public ActionResult ViewRecords() {
return View();
}
Is there anyway to make is so that the code above relies on the correct string to be passed in like so:
public ActionResult ViewRecords() {
alert(//some sort of text box//);
if (string == 'password')
return View;
else
return View("Index.cshtml");
}
I've done some research and found some people doing this through a webconfig definition, but am confused on the exact code I'd need to put in both my web.config, c# controller file, and .cshtml view to make this work.
[Http.Get]
public ActionResult SafePage() {
return View("PageThatPromptsForPassword");
}
inside "PageThatPromptsForPassword" view, write some JS to prompt the user for a password. Pass it along (using AJAX or a form) to a 2nd controller action to verify.
[Http.POST]
public ActionResult ConfirmPassword(string passWord) {
if (passWord == MY_SECRET_PASSWORD) {
return View("SecretInformationPage");
} else {
return View ("PageThatPromptsForPassword");
}
}
** Note this is, as you asked, very simple and may have potential security issues. But it's a starting point.
The following are two examples of a controller for submitting data. One returns the input model if validation fails the other doesn't. Can someone enlighten me on which is method correct or preferred? They seem to behave exactly the same.
With returning model
[HttpPost]
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
//do stuff
}
return View(model);
}
Without returning model
[HttpPost]
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
//do stuff
}
return View();
}
Usually you want the user to show an error message if the data he submitted is not valid (e.g. he did not enter a valid email address into a field that requries to be an email - see MVC ModelValidation for more information).
You use the ModelState.IsValid to check for that. In this case you usually show him the page he came from ("Register") and you also want to show the data he entered into the form fields (= RegisterModel). But you want to display an errormessage that tells him what fields are not correct.
If the user's data is correct and your action succeeds (in this case he was registered successfully) you usually do not show him the registration form again, but you would redirect him to a success page.
Here is a simple example how to validate, show errors and redirect after successfull action.
Example from your code:
[HttpPost]
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
//do stuff
// redirect to a success page
return RedirectToAction("Success");
}
// data is not valid - show the user his data and error messages
// using Html Helper methods (see link for details)
return View(model);
}
On the Register view (Ifno - if you have a strongly typed view, you can use the HTML Helper overloads for that - e.g. use Html.LabelFor, TextboxFor etc methods):
Show general error message/summary;
...
<%= Html.ValidationSummary("Register was not successfull") %>
Show error message next to the inputs:
...
<%= Html.TextBox("Email") %>
<%= Html.ValidationMessage("Email", "*") %>
Will you view render without model passed to it? Probably not, so the second one will fail.
I would suggest you read about Post Redirect Get pattern
http://en.wikipedia.org/wiki/Post/Redirect/Get
The first action at the end is passing the Model to the view and the second one your returning back an empty view. So I would suggest you go for the first one
But when validation fails you should do the error handling this way
if (ModelState.IsValid)
{
//do stuff
}
else
{
ModelState.AddModelError("RegisterModel Errors", "some error occured");
}
return View(model);
Since you have a strongly typed view, in either case you will need to pass the corresponding model to the view (else your view may crash on trying to access properties on a null model). On success you can redirect to another page or you should be able to give user a notification, in the same page itself updating whether the operation is a success or fail (depends on whether you want a usual HTML based solution or an AJAX enabled solution). As for validation there are the options of usual client side validation and the jQuery based unobtrusive client side validation.
Here are couple of useful links
ASP.NET Model validation (this is MVC2 based but should be same for MVC3)
MVC3 unobtrusive client validation
I have a purchase page and I don't want the user to be able to refresh the page and resubmit the form once they get to the 'order complete' page because it automatically sets them up in our system via database values and charges their card via paypal (only want these to happen ONCE)... I have seen some sites that say 'Don't hit refresh or you will get charged twice!' but that is pretty lame to leave it open to possibility, what's a good way to only allow it to be submitted once or prevent them from refreshing, etc?
PS: I saw a few similar questions: PHP: Stop a Form from being accidentally reprocessed when Back is pressed and How do I stop the Back and Refresh buttons from resubmitting my form? but found no satisfactory answer... an ASP.NET MVC specific answer would be ideal too if there is a mechanism for this.
EDIT: Once they click submit it POSTS to my controller and then the controller does some magic and then returns a view with an order complete message, but if I click refresh on my browser it does the whole 'do you want to resend this form?' that is bad...
The standard solution to this is the POST/REDIRECT/GET pattern. This pattern can be implemented using pretty much any web development platform. You would typically:
Validate submission after POST
if it fails re-render the original entry form with validation errors displayed
if it succeeds, REDIRECT to a confirmation page, or page where you re-display the input - this is the GET part
since the last action was a GET, if the user refreshes at this point, there is no form re-submission to occur.
I 100% agree with RedFilter's generic answer, but wanted to post some relevant code for ASP.NET MVC specifically.
You can use the Post/Redirect/Get (PRG) Pattern to solve the double postback problem.
Here's an graphical illustration of the problem:
What happens is when the user hits refresh, the browser attempts to resubmit the last request it made. If the last request was a post, the browser will attempt to do that.
Most browsers know that this isn't typically what the user wants to do, so will automatically ask:
Chrome -
The page that you're looking for used information that you entered.
Returning to that page might cause any action you took to be repeated.
Do you want to continue?
Firefox - To display this page, Firefox must send information that will repeat any action (such as a search or order confirmation) that was performed earlier.
Safari -
Are you sure you want to send a form again?
To reopen this page Safari must resend a form. This might result in duplicate purchases, comments, or other actions.
Internet Explorer -
To display the webpage again, the web browser needs to
resend the information you've previously submitted.
If you were making a purchase, you should click Cancel to
avoid a duplicate transaction. Otherwise, click Retry to display
the webpage again.
But the PRG pattern helps avoid this altogether by sending the client a redirect message so when the page finally appears, the last request the browser executed was a GET request for the new resource.
Here's a great article on PRG that provides an implementation of the pattern for MVC. It's important to note that you only want to resort to a redirect when an non-idempotent action is performed on the server. In other words, if you have a valid model and have actually persisted the data in some way, then it's important to ensure the request isn't accidentally submitted twice. But if the model is invalid, the current page and model should be returned so the user can make any necessary modifications.
Here's an example Controller:
[HttpGet]
public ActionResult Edit(int id) {
var model = new EditModel();
//...
return View(model);
}
[HttpPost]
public ActionResult Edit(EditModel model) {
if (ModelState.IsValid) {
product = repository.SaveOrUpdate(model);
return RedirectToAction("Details", new { id = product.Id });
}
return View(model);
}
[HttpGet]
public ActionResult Details(int id) {
var model = new DetailModel();
//...
return View(model);
}
While serving up the order confirmation page you can set a token that you also store in the DB/Cache. At the first instance of order confirmation, check for this token's existence and clear the token. If implemented with thread safety, you will not be able to submit the order twice.
This is just one of the many approaches possible.
Note that the PRG pattern does not completely guard against multiple form submissions, as multiple post requests can be fired off even before a single redirect has taken place - this can lead to your form submissions not being idempotent.
Do take note of the answer that has been provided here, which provides a workaround to this issue, which I quote here for convenience:
If you make use of a hidden anti-forgery token in your form (as you
should), you can cache the anti-forgery token on first submit and
remove the token from cache if required, or expire the cached entry
after set amount of time.
You will then be able to check with each request against the cache
whether the specific form has been submitted and reject it if it has.
You don't need to generate your own GUID as this is already being done
when generating the anti-forgery token.
Give each visitor's form a unique ID when the page is first loaded. Note the ID when the form is submitted. Once a form has been submitted with that ID, don't allow any further requests using it. If they click refresh, the same ID will be sent.
Simply do a redirect from the page that does all the nasty stuff to the "Thank you for your order" page. Having done that, the user can hit refresh as many times as he likes.
If you doesn't like redirect the user to other page, then by using my way you dose not need Post/Redirect/Get (PRG) Pattern and the user remain on the current page without fear of the negative effects of re-submitting of the form!
I use a TempData item and a Hidden field (a property in the ViewModel of the form) to keep a same Guid in both sides (Server/Client) and it is my sign to detect if the form is Resubmitting by refresh or not.
Final face of the codes looks like very short and simple:
Action:
[HttpPost]
public virtual ActionResult Order(OrderViewModel vModel)
{
if (this.IsResubmit(vModel)) // << Check Resubmit
{
ViewBag.ErrorMsg = "Form is Resubmitting";
}
else
{
// .... Post codes here without any changes...
}
this.PreventResubmit(vModel);// << Fill TempData & ViewModel PreventResubmit Property
return View(vModel)
}
In View:
#if (!string.IsNullOrEmpty(ViewBag.ErrorMsg))
{
<div>ViewBag.ErrorMsg</div>
}
#using (Html.BeginForm(...)){
#Html.HiddenFor(x=>x.PreventResubmit) // << Put this Hidden Field in the form
// Others codes of the form without any changes
}
In View Model:
public class OrderViewModel: NoResubmitAbstract // << Inherit from NoResubmitAbstract
{
// Without any changes!
}
What do you think?
I make it simple by writing 2 class:
NoResubmitAbstract abstract class
ControllerExtentions static class (An Extension class for System.Web.Mvc.ControllerBase)
ControllerExtentions:
public static class ControllerExtentions
{
[NonAction]
public static bool IsResubmit (this System.Web.Mvc.ControllerBase controller, NoResubmitAbstract vModel)
{
return (Guid)controller.TempData["PreventResubmit"]!= vModel.PreventResubmit;
}
[NonAction]
public static void PreventResubmit(this System.Web.Mvc.ControllerBase controller, params NoResubmitAbstract[] vModels)
{
var preventResubmitGuid = Guid.NewGuid();
controller.TempData["PreventResubmit"] = preventResubmitGuid ;
foreach (var vm in vModels)
{
vm.SetPreventResubmit(preventResubmitGuid);
}
}
}
NoResubmitAbstract:
public abstract class NoResubmitAbstract
{
public Guid PreventResubmit { get; set; }
public void SetPreventResubmit(Guid prs)
{
PreventResubmit = prs;
}
}
Just put them in your MVC project and run it... ;)
Off the top of my head, generate a System.Guid in a hidden field on the GET request of the page and associate it with your checkout/payment. Simply check for it and display a message saying 'Payment already processed.' or such.
Kazi Manzur Rashid wrote about this (together with other asp.net mvc best-practices). He suggests using two filters to handle data transfer between the POST and the follwing GET using TempData.
Here's the scenario: when a new user registers to our web site, we want to send an email to verify that the user owns the email address. In the email there's a link to a page that will do the verification, something like this:
http://www.mysite.com/account/verify/token
The verify method looks like this:
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Verify(Nullable<Guid> id)
{
// tries to get the user based on the verification code
if (ValidId(id))
{
// if the id is correct, update user data in the model and redirect
return RedirectToAction("Index", "Dashboard");
}
else
{
// redirects the user to the verify view
return View("Verify");
}
}
The "Verify" view is simply a textbox with a button, so the user can enter the verification code manually (the user can get to this page from the site, and might prefer just to copy-paste the code). When the user clicks on the button, I want to do the same thing that my GET method does; so I ended up with something like this:
[AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post)]
public ActionResult Verify(Nullable<Guid> id) { ... }
I have a couple of issues with this code (which works, but...):
Is it OK to have a GET and POST method? Or is there a better way to handle this scenario?
I'm modifying data in a GET method (if the id is correct, I update the user data to reflect that it's verified) and that's a big NO NO ... still, I want the user to just be able to click on the link and verify the token. Is there a better way to achieve this?
Thanks
I personally wouldn't bother with the AcceptVerbs attribute. (** See note below) You could then combine this into one action, which could respond as needed. (Showing some untested code below.) The reason I'm adding an answer instead of just a comment is that I wanted to recommend you add one more branch to your logic, to handle a failed code (i.e., to present an error message).
public ActionResult Verify(Nullable<Guid> id)
{
if (!id.HasValue)
{
// nothing was submitted
ViewData["message"] = "Please enter your ID and press Submit";
return View("Verify");
}
if (!ValidId(id))
{
// something was submitted, but wasn't valid
ViewData["message"] = "ID is invalid or incomplete. Pleaes check your speeling";
return View("Verify");
}
// must be valid
return RedirectToAction("Index", "Dashboard");
}
You then of course could display <%=ViewData["message"]%> in your Verify view. This is of course just a simple example.
** OK, here is my note RE: not bothering with the AcceptVerbs attribute:
In your scenario you could also just choose to make your form's method GET instead of POST. Because you're already "taking action" and modifying state on the handy link your users click on, I wouldn't see any difference. I'm just mentioning this to be thorough even though I'd personally opt for my previous recommendation.
Good luck!
I'm modifying data in a GET method ... and that's a big NO NO
I wouldn't say it's always a big no no. HTTP says that the GET method "SHOULD" be "safe", that is it SHOULD have no effect other than information retrieval. In this case, I think it's reasonable to stretch the definition of "safe" to mean that it doesn't have any harmful side effects, and indeed the only possible side effect your verification link can have is a desirable one.
The other property that the GET method is supposed to have is idempotence: if the user clicks the verification link multiple times, it's the same as if they clicked it just once. Hopefully you have this property, since a verification link is generated with a single-use code.
I have to say it is rare to find somebody so concerned with using the proper HTTP verb. I don't believe the original intention of the HTTP spec was to confine all data editing submissions to POST and all retrievals to GET. I think what you're doing is just fine.
if you are worried about it then what is wrong with this?
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Verify()
{
return View("Verify");
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Verify(Guid? id)
{
if (ValidId(id))
{
return RedirectToAction("Index", "Dashboard");
}
return View("Verify");
}
I'd like to take data entered in an MVC user form and display it in a different view.
The class has the following private variable:
IList<string> _pagecontent = new List<string>();
The following action accepts a FormCollection object, validates it, and passes it on to the "Preview" view as a List:
[Authorize(Roles = "Admins")]
[ValidateInput(false)]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult UpdateContent(FormCollection collection)
{
if (ModelState.IsValid)
{
string PageToInsert = collection["PageToInsert"];
string PageHeader = collection["PageHeader"];
string PageBody = collection["PageBody"];
//validate, excluded...
_pagecontent.Add(PageToInsert);
_pagecontent.Add(PageHeader);
_pagecontent.Add(PageBody);
}
return RedirectToAction("Preview", _pagecontent);
}
The Preview view has the following Page Directive for passing a strongly typed object List:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Template.Master" Inherits="System.Web.Mvc.ViewPage<List<string>>" %>
I would expect to be able to use the Model object to get my data, but alas I cannot. At the following line, I get an error index out of bounds exception, stating the index must be non-negative and less than the size of the collection:
<% if (Model[0].ToString() == "0") { %>
And some strange parameters have been added to the URL, as it resolves to
http://localhost:1894/Admin/Preview?Capacity=4&Count=3
So I have two questions:
When I call RedirectToAction and pass it my List, why is it inaccessible in the view's Model object?
What is the proper way to go about doing what I'm trying to do, namely pass a collection of strings to a view for display there?
Try using TempData. It is like a single-shot session object. You put values you want into TempData, immediately redirect and get them out. There is a good writeup here: http://blogs.teamb.com/craigstuntz/2009/01/23/37947/
Be careful when using TempData. It works great in a single server environment but in a cloud environment it may not work as expected since you cannot guarantee that the request will hit the same machine. This happens because TempData rely on the asp.net session. But if you are using other session manager like SQL or AppFabric Cache it will work fine.
The second parameter to RedirectAction is routeValues, not model.
protected internal RedirectToRouteResult RedirectToAction(string actionName, object routeValues);
Try using TempData for the model. Its for persisting data between redirects.
The problem with RedirectToAction is it's returning a HTTP 302 and the browser is then on it's own going and doing a brand new HTTP request. You may want to consider using a cookie and/or session object to persist the data between requests.
This is not working because RedirectToAction is actually sending back a Http 302 to the browser. When the browser receives this 302, it does a new request to the server asking for the new page. New request, new temp variables.
You will also face this problem when you try to save/edit/delete something and for some reason you deny it and you have to return the old form again.
So, instead of:
return RedirectToAction("Preview", _pagecontent);
Put the Preview logic in a separate method and just call it:
return PreviewLogic(_pagecontent);
You can also use the TempData[] dic to persist data for the next request like others have said, but then you will not avoid the 302 additional round trip to the server.
It sounds like you're trying to do:
public ActionResult UpdateContent(FormCollection form) {
...
return View("Preview", _pagecontent);
}
Note that a redirection is supposed to be a "clean slate" for the browser (except for things like the auth cookie). You don't get to tell the browser to pass information along to the next request, since the next request should be able to stand on its own. All you get to do is tell the browser what URL to request next. In ASP.NET MVC, when you pass an arguments-object to RedirectToAction, the public properties of that object are appended as query-string parameters to the generated URL.
Can't you just make 2 action results with the same name and mark 1 of them with HttpPost?
public ActionResult UpdateContent(FormCollection preview = null)
{
return View(preview);
}
[HttpPost]
public ActionResult UpdateContent(FormCollection collection = null, bool preview = false)
{
if (preview)
return UpdateContent(collection);
else
return UpdateContent(null);
}
It looks like you are looking for the UpdateModel command:
Check out ScottGu's blog post on the topic:
Improved UpdateModel and TryUpdateModel methods