3

How do I prevent a user from manually typing in the URL of a page in MVC?

For instance, I have a controller method which returns an error view:

    // GET: /Checkout/Error
    public ActionResult Error()
    {
        return View("Error")
    }

EDIT: and a controller method that returns a success view:

        // GET: /Checkout/Complete
    public ActionResult Complete()
    {
        var order = /* get the order */
        return View("Complete", order)
    }

I redirect to it if something goes wrong in the order process:

    public ActionResult Submit()
    {

        if (/*order succeeds*/) {
            return RedirectToAction("Complete");
        }
        else
            return RedirectToAction("Error");
    }

But a user can manually type in "www.mysite.com/Checkout/Error" and get the error view outside of the context of the checkout workflow.

I did some looking around and found ChildActionOnly, but that only seems to apply to calling actions from within views, which doesn't apply here.

I suppose I could manually check at the beginning of the Error action method to see if indeed there was a problem with the order, and return the error view in that case and redirect the user otherwise, but it seems like there has to be a simpler way to prevent users from manually navigating to pages like this.

EDIT: The same applies to the completed view. A user can type in the URL which corresponds to the action. How do I prevent this action from being invoked like this?

jtheis
  • 916
  • 3
  • 12
  • 28
  • 2
    Unless you have some data that shouldn't be displayed in these pages, I would not worry about them. If you have a requirement to do it :( then you could always check the [Referrer](http://stackoverflow.com/questions/4258217/getting-the-http-referrer-in-asp-net) and verify the previous page was valid. – Erik Philips Mar 04 '14 at 21:31

1 Answers1

3

You may remove the Error Action method from your controller,(but still keep the view) and use return View() instead of the RedirectToAction. In this case your url stays same unlike RedirectToAction where it is a 302 response which tells the browser to make a new GET request to the Error action method.

public ActionResult Submit()
{
    if (/*order succeeds*/)
    {
        return RedirectToAction("Complete");
    }
    else
    {
        // TO DO : LOG ERROR
        return View("Error");
    }
}

Assuming you have a view called Error in the ~/Views/CurrentControllerName/ or in ~/Views/Shared

If your view is in a different directory, you can give the full path to the directory.

return View("~/Views/MyErrors/OrderError.cshtml");

If needed, you can pass some information to your Error view.

EDIT : As per the comment.

If you want to pass an order object to the Complete view, You can use TempData to pass your order object. In the Completed action method, you can check whether your specific TempData value is available and if yes, show the proper view, else tell the user that they are trying to access a page which does not exist/ or any other relevant message/view.

public ActionResult Submit()
{
    if (/*order succeeds*/)
    {
         // assuming newOrderId stores the Id of new order somehave.
        OrderVM orderVM=GetOrderFromSomeWhere(newOrderId);
        TempData["NewOrder"] =orderVM;
        return RedirectToAction("Complete");
    }
    else
    {
        // TO DO : LOG ERROR
        return View("Error");
    }
}

and in your Complete action method, check you have something in your TempData and then show the appropriate view to the user.

public ActionResult Complete()
{      
  var model=TempData["NewOrder"] as OrderVM;
  if(model!=null)
  {
     return View(model);
  }
  return View("NotFound");
}

TempData uses Session object behind the scene to store the data. TempData is meant to be a very short-lived instance, and you should only use it during the current and the subsequent requests only!

Rachel has written a nice blog post explaining when to use TempData /ViewData. Worth to read.

Shyju
  • 214,206
  • 104
  • 411
  • 497
  • So, this will work for my error view. But it seems I have the same problem with my Complete action. I don't want someone typing in www.mysite.com/Checkout/Complete and getting a view. Granted, right now that view expects a completed order object (which it doesn't have) and throws a null reference exception. I've added the Complete action to my original post. – jtheis Mar 04 '14 at 21:52
  • I actually have a bunch of backend infrastructure I'm not showing for clarity. I have no problem getting the Order to the Completed view whether I use a redirect or just return a completed view from the Submit action. The question is: should I have a Complete action or not? If I remove the complete action, it solves my problem, though the URL will still say "Submit". If I keep the action, I need to do special handling to return a "not found" view. Is there a reason to choose one over the other, other than simplicity? – jtheis Mar 04 '14 at 22:16
  • 2
    I prefer the PRG pattern.Post-Redirect-Get. So Yes, I would keep a completed action and redirect the user to that. IF you are not doing, What if user refreshes the page after once the order is completed ? It will create duplicate. rite ? So go for the PRG pattern.http://www.stevefenton.co.uk/Content/Blog/Date/201104/Blog/ASP-NET-MVC-Post-Redirect-Get-Pattern/ – Shyju Mar 04 '14 at 22:19
  • Ah, right! I had forgotten that was the reason I made that a separate action in the first place. Definitely a good reason to keep it! Thanks! – jtheis Mar 04 '14 at 22:29