77

I have a basic Edit method in my controller that redirects back to a top level listing (“Index”) when the edit succeeds. Standard behavior after MVC scaffolding.

I am trying to change this Edit method to redirect back to the previous page (not Index). Since my Edit method wasn't using the default mapped input parameter “id”, I first tried using that to pass in the previous URL.

In my Edit “get” method, I used this line to grab the previous URL and it worked fine:

ViewBag.ReturnUrl = Request.UrlReferrer;

I then sent this return URL to the Edit “post” method by using my form tag like this:

@using (Html.BeginForm(new { id = ViewBag.ReturnUrl }))

Now this is where the wheels fell off. I couldn't get the URL parsed from the id parameter properly.

*** UPDATE: SOLVED ***

Using Garry's example as a guide, I changed my parameter from "id" to "returnUrl" and used a hidden field to pass my parameter (instead of the form tag). Lesson learned: Only use the "id" parameter how it was intended to be used and keep it simple. It works now. Here is my updated code with notes:

First, I grab the previous URL using Request.UrlReferrer as I did the first time.

    //
    // GET: /Question/Edit/5

    public ActionResult Edit(int id)
    {
        Question question = db.Questions.Find(id);
        ViewBag.DomainId = new SelectList(db.Domains, "DomainId", "Name", question.DomainId);
        ViewBag.Answers = db.Questions
                            .AsEnumerable()
                            .Select(d => new SelectListItem
                            {
                                Text = d.Text,
                                Value = d.QuestionId.ToString(),
                                Selected = question.QuestionId == d.QuestionId
                            });
        // Grab the previous URL and add it to the Model using ViewData or ViewBag
        ViewBag.returnUrl = Request.UrlReferrer;
        ViewBag.ExamId = db.Domains.Find(question.DomainId).ExamId;
        ViewBag.IndexByQuestion = string.Format("IndexByQuestion/{0}", question.QuestionId);
        return View(question);
    }

and I now pass the returnUrl parameter from the Model to the [HttpPost] method using a hidden field in the form:

@using (Html.BeginForm())
{
    <input type="hidden" name="returnUrl" value="@ViewBag.returnUrl" />
    ...

In the [HttpPost] method we pull the parameter from the hidden field and Redirect to it....

    //
    // POST: /Question/Edit/5

    [HttpPost]
    public ActionResult Edit(Question question, string returnUrl) // Add parameter
    {
        int ExamId = db.Domains.Find(question.DomainId).ExamId;
        if (ModelState.IsValid)
        {
            db.Entry(question).State = EntityState.Modified;
            db.SaveChanges();
            //return RedirectToAction("Index");
            return Redirect(returnUrl);
        }
        ViewBag.DomainId = new SelectList(db.Domains, "DomainId", "Name", question.DomainId);
        return View(question);
    }
tereško
  • 58,060
  • 25
  • 98
  • 150
Jason Enochs
  • 1,436
  • 1
  • 13
  • 20
  • 1
    Even if you're not using `id`, I wouldn't use it to pass a URL. That seems like a hack, and I'm sure there's a better way to do it. – Eric Andres Mar 19 '12 at 16:35
  • Yes, Eirc you are correct. I was using an ugly hack. This is my first MVC app. I tried many things and read many posts and the hack was my best attempt. I fixed it based on Gary's example. Thanks, – Jason Enochs Mar 19 '12 at 17:53

4 Answers4

66

I am assuming (please correct me if I am wrong) that you want to re-display the edit page if the edit fails and to do this you are using a redirect.

You may have more luck by just returning the view again rather than trying to redirect the user, this way you will be able to use the ModelState to output any errors too.

Edit:

Updated based on feedback. You can place the previous URL in the viewModel, add it to a hidden field then use it again in the action that saves the edits.

For instance:

public ActionResult Index()
{
    return View();
}

[HttpGet] // This isn't required
public ActionResult Edit(int id)
{
   // load object and return in view
   ViewModel viewModel = Load(id);

   // get the previous url and store it with view model
   viewModel.PreviousUrl = System.Web.HttpContext.Current.Request.UrlReferrer;

   return View(viewModel);
}

[HttpPost]
public ActionResult Edit(ViewModel viewModel)
{
   // Attempt to save the posted object if it works, return index if not return the Edit view again

   bool success = Save(viewModel);
   if (success)
   {
       return Redirect(viewModel.PreviousUrl);
   }
   else
   {
      ModelState.AddModelError("There was an error");
      return View(viewModel);
   }
}

The BeginForm method for your view doesn't need to use this return URL either, you should be able to get away with:

@model ViewModel

@using (Html.BeginForm())
{
    ...
    <input type="hidden" name="PreviousUrl" value="@Model.PreviousUrl" />
}

Going back to your form action posting to an incorrect URL, this is because you are passing a URL as the 'id' parameter, so the routing automatically formats your URL with the return path.

This won't work because your form will be posting to an controller action that won't know how to save the edits. You need to post to your save action first, then handle the redirect within it.

Manfred
  • 5,320
  • 3
  • 35
  • 29
Garry Marsland
  • 1,211
  • 1
  • 13
  • 14
  • Yes, but I need it to redirect back to the previous page if the edit succeeds. I think that it wasn't working because I was using "id." I changed the parameter to "url" as shown below and it seems to have worked on my first attempt but I need to test it more....hold on and I'll post what I have. I'm new to posting on this site and taking a tic to get used to it. – Jason Enochs Mar 19 '12 at 16:34
  • In that case you can place the previous URL in the ViewBag when initially loading the Edit page, then place it in a hidden field. Then you can access it as part of your ViewModel in the post action and redirect to it. I would prefer something like this than sending it via querystring. If you'd like an example let me know. – Garry Marsland Mar 19 '12 at 16:37
  • see, Garry's if(success) clause. Here you can redirect to Any page you desire. Aka back to previous page. `redirectToAction` can also take parameters, controller-action-variables. – IAmGroot Mar 19 '12 at 16:45
  • Ok, Gary. Hold on a few minutes. I am trying your technique. Mine last attempt seems to work but yours looks more like the correct way to do it...I'm doing three things at once so this will take me a couple minutes. – Jason Enochs Mar 19 '12 at 16:56
  • Gary, there are some slight differences in our code because you are using MVVM while I'm using MVC but I was still able to use your example to fix my problem and to clean up my code. I also used your example to get rid of that parameter in my form tag. THANK YOU!!!! – – Jason Enochs Mar 19 '12 at 20:44
  • Despite the age of the answer, this answer solved a problem I was having. Great explanation! – LifeOf0sAnd1s Aug 02 '17 at 12:51
  • 1
    I think there is a small error in the code here. Instead of using: "return RedirectToAction(viewModel.PreviousUrl);" you should use: "return Redirect(viewModel.PreviousUrl);" – AidanO Sep 15 '17 at 08:59
5

I know this is very late, but maybe this will help someone else.

I use a Cancel button to return to the referring url. In the View, try adding this:

@{
  ViewBag.Title = "Page title";
  Layout = "~/Views/Shared/_Layout.cshtml";

  if (Request.UrlReferrer != null)
  {
    string returnURL = Request.UrlReferrer.ToString();
    ViewBag.ReturnURL = returnURL;
  }
}

Then you can set your buttons href like this:

<a href="@ViewBag.ReturnURL" class="btn btn-danger">Cancel</a>

Other than that, the update by Jason Enochs works great!

NateM
  • 1
  • 1
  • 2
4

For ASP.NET Core You can use asp-route-* attribute:

<form asp-action="Login" asp-route-previous="@Model.ReturnUrl">

An example: Imagine that you have a Vehicle Controller with actions

Index

Details

Edit

and you can edit any vehicle from Index or from Details, so if you clicked edit from index you must return to index after edit and if you clicked edit from details you must return to details after edit.

//In your viewmodel add the ReturnUrl Property
public class VehicleViewModel
{
     ..............
     ..............
     public string ReturnUrl {get;set;}
}



Details.cshtml
<a asp-action="Edit" asp-route-previous="Details" asp-route-id="@Model.CarId">Edit</a>

Index.cshtml
<a asp-action="Edit" asp-route-previous="Index" asp-route-id="@item.CarId">Edit</a>

Edit.cshtml
<form asp-action="Edit" asp-route-previous="@Model.ReturnUrl" class="form-horizontal">
        <div class="box-footer">
            <a asp-action="@Model.ReturnUrl" class="btn btn-default">Back to List</a>
            <button type="submit" value="Save" class="btn btn-warning pull-right">Save</button>
        </div>
    </form>

In your controller:

// GET: Vehicle/Edit/5
    public ActionResult Edit(int id,string previous)
    {
            var model = this.UnitOfWork.CarsRepository.GetAllByCarId(id).FirstOrDefault();
            var viewModel = this.Mapper.Map<VehicleViewModel>(model);//if you using automapper
    //or by this code if you are not use automapper
    var viewModel = new VehicleViewModel();

    if (!string.IsNullOrWhiteSpace(previous)
                viewModel.ReturnUrl = previous;
            else
                viewModel.ReturnUrl = "Index";
            return View(viewModel);
        }



[HttpPost]
    public IActionResult Edit(VehicleViewModel model, string previous)
    {
            if (!string.IsNullOrWhiteSpace(previous))
                model.ReturnUrl = previous;
            else
                model.ReturnUrl = "Index";
            ............. 
            .............
            return RedirectToAction(model.ReturnUrl);
    }
Mohamed Elamin
  • 345
  • 4
  • 7
1

Here is just another option you couold apply for ASP NET MVC.

Normally you shoud use BaseController class for each Controller class.

So inside of it's constructor method do following.

public class BaseController : Controller
{
        public BaseController()
        {
            // get the previous url and store it with view model
            ViewBag.PreviousUrl = System.Web.HttpContext.Current.Request.UrlReferrer;
        }
}

And now in ANY view you can do like

<button class="btn btn-success mr-auto" onclick="  window.location.href = '@ViewBag.PreviousUrl'; " style="width:2.5em;"><i class="fa fa-angle-left"></i></button>

Enjoy!

NoWar
  • 36,338
  • 80
  • 323
  • 498
  • 1
    I have not tried this yet but I sure like the way it looks. If this works then I think it is better than the example I posted several years ago. I would try this now but I'm neck deep in Python on a Linux box. Thank you for sharing this. – Jason Enochs Jul 12 '19 at 13:17
  • What happens if the user visits web page directly? – Tomas Aug 13 '19 at 09:48