tags:

views:

139

answers:

3

Greetings.

In attempt to wrap my head around MVC I've just implemented a simple add/delete page which views records as well. This makes it easier for users to do most things from one page instead of having to navigate away to do simple tasks. The page lets you add a record (a business which has an ID and a name) which works fine. However when I delete a record I've done the following to allow deletions to occur:

<%= Html.ActionLink("delete", "DeleteBusiness", new { businessToDelete = B.BusinessID }) %>

This works fine to delete the record. This is what my controller action looks like for it:

public ActionResult DeleteBusiness(string businessToDelete)
{
     try
     {
       if (!ModelState.IsValid)
         return View("Businesses", _contractsControlRepository.ListBusinesses());

         _contractsControlRepository.DeleteBusiness(businessToDelete);

         return View("Businesses", _contractsControlRepository.ListBusinesses());
     }
     catch
     {
       return View("Businesses", _contractsControlRepository.ListBusinesses());
     }
}

So from the Businesses page I have an ActionLink which just directs me to this action in the controller which does the work and then returns the view that I was previously at. Then problem is that once I've deleted the record my actual URL ends up like this:

http://localhost:3340/Accounts/ContractsControl/DeleteBusiness?businessToDelete=TEST

This isn't good, because when I then go to add a new record, it won't let me. My controller action which lets me add records looks like this:

  [AcceptVerbs(HttpVerbs.Post)]
  public ActionResult Businesses(Business business)
  {
    try
    {
      if (!ModelState.IsValid)
        return View(_contractsControlRepository.ListBusinesses());

      _contractsControlRepository.CreateBusiness(business);
      return View(_contractsControlRepository.ListBusinesses());
    }
    catch
    {
      return View(_contractsControlRepository.ListBusinesses());
    }
  }

Any post request is seen as creating new records. Is this correct? Here's my view just to top it all off:

<% using (Html.BeginForm())
   { %>

  <fieldset class="inline-fieldset">
    <legend>Add Business</legend>

    <p>
    <label for="ID">ID</label>

    <%= Html.TextBox("BusinessID", null, new { style = "width:50px;", @class = "uppercase", maxlength = "4" })%>
    <%= Html.ValidationMessage("BusinessID", "*")%>

    <label for="Business">Business</label>

    <%= Html.TextBox("BusinessCol")%>
    <%= Html.ValidationMessage("BusinessCol", "*")%>

    <input type="submit" value="Add" />
  </p>

  <%= Html.ValidationSummary("Uh-oh!") %>

  </fieldset>

<% } %>

<table>
<tr>
<th>ID</th>
<th>Business</th>
<th></th>
<th></th>
</tr>
  <% foreach (Business B in ViewData.Model)
  { %>
    <tr>
      <td><%= B.BusinessID %></td>
      <td><%= B.BusinessCol %></td>
      <td class="edit"><%= Html.ActionLink("edit", "EditBusiness", new { id = B.BusinessID }) %></td>
      <td class="delete"><%= Html.ActionLink("delete", "DeleteBusiness", new { businessToDelete = B.BusinessID }) %></td>
    </tr>
  <% } %>
</table>

As you can see at the very bottom I have the action links (ignore the edit one). So if I delete a record, then immediately try to add one, it won't work because (I'm assuming) of the URL being wrong. What am I doing wrong here?

FIXED

  public RedirectToRouteResult DeleteBusiness(string businessToDelete)
  {
    try
    {
      if (!ModelState.IsValid)
        return RedirectToAction("Businesses");

      _contractsControlRepository.DeleteBusiness(businessToDelete);

      return RedirectToAction("Businesses");
    }
    catch
    {
      return RedirectToAction("Businesses");
    }
  }
+4  A: 

Why don't you redirect user back do List Businesses page if delete succeded?

maciejkow
Bingo. I'll update my question to show the fix.
Kezzer
+1  A: 

I second maciejkow's answer, redirecting eliminates the chance of the user re-posting the data if they try refresh the page.

Another thing, I wouldn't put delete actions in a link (i.e. a GET), these sorts of actions should always be POSTs (automated tools can sometimes follow links on a page for caching reasons), so I would wrap a button in a mini-form to do your delete, with your businessToDelete variable in a hidden field.

JonoW
That gets a bit messy though, does it not? However I fully understand the logic in your statement. It's just in this example it's handy to ahve a table of data with delete buttons next to each entry. In fact, this is a requirement of the system really.
Kezzer
In the NerdDinner tutorial the specific reason given for not allowing DELETE on a HTTP GET is "we want to be careful to guard against web-crawlers... inadvertently causing data to be deleted when they follow links..."
Paul Rowland
A very good reason indeed. But what if I wanted a delete button on every one? Would I really have to provide a form and a button for each one? I need them as hyperlinks as opposed to forms.
Kezzer
Yes you'd probably need a small form around each delete button (easiest way). If you worry about messiness, you could write a HtmlHelper extension method to generate the neccessary markup.
JonoW
A link describing about delete link in gridview - http://stephenwalther.com/blog/archive/2009/01/21/asp.net-mvc-tip-46-ndash-donrsquot-use-delete-links-because.aspx
Paul Rowland
+1  A: 

I've just gone through the NerdDinner tutorial.

In there the controllers for Delete are

// GET: /Dinners/Delete/1
public ActionResult Delete(int id)
{
    Dinner dinner = dinnerRepository.GetDinner(id);
    if (dinner == null)
    {
        return View("NotFound");
    }
    else
    {
        return View(dinner);
    }
}


    // POST: /Dinners/Delete/1
    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Delete(int id, string confirmButton)
    {
        Dinner dinner = dinnerRepository.GetDinner(id);

        if (dinner == null)
        {
            return View("NotFound");
        }

        dinnerRepository.Delete(dinner);
        dinnerRepository.Save();

        return View("Deleted");
    }

and the controllers for Get are.

// GET: /Dinners/Edit/2
[Authorize]
public ActionResult Edit(int id)
{

    Dinner dinner = dinnerRepository.GetDinner(id);

    if (!dinner.IsHostedBy(User.Identity.Name))
    {
        return View("InvalidOwner");
    }

    return View(new DinnerFormViewModel(dinner));
}

//
// POST: /Dinners/Edit/2
[AcceptVerbs(HttpVerbs.Post),Authorize]
public ActionResult Edit(int id, FormCollection formValues)
{
    Dinner dinner = dinnerRepository.GetDinner(id);
    if (!dinner.IsHostedBy(User.Identity.Name))
    {
        return View("InvalidOwner");
    }
    try
    {

        UpdateModel(dinner);
        dinnerRepository.Save();

        return RedirectToAction("Details", new { id = dinner.DinnerID });
    }
    catch (Exception ex)
    {
        foreach (var issue in dinner.GetRuleViolations())
        {
            ModelState.AddModelError(issue.PropertyName, issue.ErrorMessage);
        }
        return View(new DinnerFormViewModel(dinner));
    }           
}
Paul Rowland