I created a sample ASP.NET MVC application that I plan to post at the http://ww.ASP.net/mvc website. While the application was being code reviewed by the ASP.NET MVC Feature team, a surprising objection surfaced.
The application is extremely simple. It contains a view that renders a list of database records. Next to each record, there is an Edit link and a Delete link (see Figure 1). Pretty standard stuff. Or, so I thought…
Figure 1 – A Grid of database records

Here’s the objection. You should not use a link for deleting a record. Using a Delete link opens up a security hole.
The Security Objection
In theory, someone could send an email to you that contains an image. The image could be embedded in the message with the following tag:
<img src=”http://www.theApp.com/Home/Delete/23” _fcksavedurl="”http://www.theApp.com/Home/Delete/23”" />
Notice that the src attribute points at the Delete() method of the Home controller class. Opening the email (and allowing images in your email client) will delete record 23 without warning. This is bad. This is a security hole.
I had come across this security concern in the past, but had not given it much thought. Originally, Microsoft enabled you to invoke ASMX Web Services by performing a HTTP GET request. In .NET Framework 1.1, HTTP GET requests were disabled by default (You can re-enable HTTP GET requests in the Web.config file). This change in behavior was made to prevent these types of HTTP GET security attacks.
The REST Objection
There is one other reason that you should not use a link to perform deletes in an application. REST purists would defend the idea that GET requests should not change the state of your application. In other words, performing a GET operation should be a safe operation that has no side effects.
For example, you don’t want a search engine to delete all of the records in your application while crawling your website. Performing an HTTP GET should have no lasting effect on your application.
The proper HTTP operation to perform, when deleting a record, is an HTTP DELETE. The HTTP protocol supports the following HTTP operations:
· OPTIONS – Returns information about the communication options available (idempotent).
· GET – Returns whatever information is identified by the request (idempotent).
· HEAD – Performs the same operation as GET without returning the message body (idempotent).
· POST – Posts new information or updates existing information (not idempotent).
· PUT – Posts new information or updates existing information (idempotent).
· DELETE – Deletes information (idempotent).
· TRACE – Performs a message loop back (idempotent).
· CONNECT – Used for SSL tunneling.
These operations are defined as part of the HTTP 1.1 standard which you can read about at http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html.
Notice that the description of an HTTP POST and HTTP PUT are identical. To understand the difference between a POST and a PUT, you need to understand what it means for an operation to be idempotent. An idempotent operation is an operation that has the same outcome no matter how many times that it is performed.
For example, if you perform a POST operation to create a new database record, then you can create a new database record every time that you perform the POST. A POST operation is not idempotent because it can have a different effect on your application each time the operation is performed.
If, on the other hand, you perform a PUT operation, then the very same database record must be created each time you perform the PUT. A PUT operation is idempotent because performing a PUT operation a thousand times has the same effect as performing the operation one time.
Notice that an HTTP DELETE is also idempotent. Performing the same HTTP DELETE request multiple times should have the very same effect on your application each time the request is made. For example, the request /Home/Delete/23 should delete database record 23, and no other database record, regardless of how many times the request is made.
HTML Supports Only GET and POST
So, the proper thing to do when deleting a database record is to perform an HTTP DELETE operation. Performing an HTTP DELETE does not open a security hole and it does not violate REST principles.
Unfortunately, standard HTML does not support HTTP operations other than GET and POST. A link always performs a GET and a form can perform either a GET or POST. HTML does not support other types of HTTP operations.
According to the HTML 3.1 specification, the HTML FORM tag only supports GET and POST. It does not support other HTTP operations such as DELETE or PUT. See http://www.w3.org/TR/REC-html32.html#form. Furthermore, Internet Explorer only supports GET and POST (see http://msdn.microsoft.com/en-us/library/ms534167(VS.85).aspx).
Performing Ajax Deletes
If you are willing to go beyond standard HTML, you can perform HTTP DELETE operations by taking advantage of AJAX. The XmlHttpRequest object supports any of the HTTP operations. Therefore, if you are willing to make your application depend on JavaScript, then you can do everything the right way.
The Home controller in Listing 1 contains an Index() and Delete() method. The Index() method returns all of the movies from the Movies database and the Delete() method deletes a particular movie with a particular Id (this controller uses the Entity Framework).
Listing 1 – Controllers\HomeController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Tip46.Models;
namespace Tip46.Controllers
{
[HandleError]
public class HomeController : Controller
{
private MoviesDBEntities _entities = new MoviesDBEntities();
public ActionResult Index()
{
ViewData.Model = _entities.MovieSet.ToList();
return View();
}
[AcceptVerbs(HttpVerbs.Delete)]
public ActionResult Delete(int id)
{
var movieToDelete = (from m in _entities.MovieSet
where m.Id == id
select m).FirstOrDefault();
_entities.DeleteObject(movieToDelete);
_entities.SaveChanges();
return RedirectToAction("Index");
}
}
}
Notice that the Delete() method is decorated with an AcceptVerbs attribute. The Delete() method can only be invoked by an HTTP DELETE operation.
The Index view in Listing 2 displays the movies from the Movies database table within an HTML table. A Delete link is rendered next to each movie record (see Figure 2).
Listing 2 – Views\Home\Index.aspx
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<Tip46.Models.Movie>>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
<script src="../../Scripts/MicrosoftAjax.js" type="text/javascript"></script>
<script type="text/javascript">
function deleteRecord(recordId)
{
// Perform delete
var action = "/Home/Delete/" + recordId;
var request = new Sys.Net.WebRequest();
request.set_httpVerb("DELETE");
request.set_url(action);
request.add_completed(deleteCompleted);
request.invoke();
}
function deleteCompleted()
{
// Reload page
window.location.reload();
}
</script>
<h2>Index</h2>
<table>
<% foreach (var item in Model) { %>
<tr>
<td>
<%-- Ajax Delete --%>
<a onclick="deleteRecord(<%= item.Id %>)" href="JavaScript:void(0)">Delete</a>
<%-- GET Delete: Security Hole
<%= Html.ActionLink("Delete", "Delete", new { id=item.Id })%>--%>
</td>
<td>
<%= Html.Encode(item.Id) %>
</td>
<td>
<%= Html.Encode(item.Title) %>
</td>
<td>
<%= Html.Encode(item.Director) %>
</td>
<td>
<%= Html.Encode(item.DateReleased) %>
</td>
</tr>
<% } %>
</table>
</asp:Content>
Deletes are performed with an Ajax call. The Delete link invokes the JavaScript deleteRecord() function. This function uses the Microsoft ASP.NET AJAX WebRequest object to perform an Ajax call. The WebRequest performs an HTTP DELETE operation.
After the DELETE operation completes, the JavaScript deleteCompleted method is called. This method reloads the current page (a more elegant approach here would be to use the new ASP.NET AJAX Client Template functionality coming with the next version of ASP.NET AJAX. That way, you could just update the grid without reloading the entire page).
Figure 2 – The Index view

But, I Don’t Want to Depend on JavaScript
Many developers do not want their websites to depend on JavaScript. In other words, they want their websites to continue to work with JavaScript turned off. There are somewhat legitimate reasons for this requirement. Not all mobile devices support JavaScript (although most do). And, there are accessibility concerns about JavaScript (although Aria should fix these accessibility problems).
If you want your website to work with JavaScript disabled then you can’t perform an HTTP DELETE when deleting a database record. Instead, you should perform an HTTP POST. An HTTP POST does not expose the same security hole as an HTTP GET operation.
You can use the AcceptVerbs attribute to prevent a controller action from being invoked unless it is invoked with an HTTP POST operation. So, the Delete() action would look like this:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Delete(int id)
{
var movieToDelete = (from m in _entities.MovieSet
where m.Id == id
select m).FirstOrDefault();
_entities.DeleteObject(movieToDelete);
_entities.SaveChanges();
return RedirectToAction("Index");
}
Unfortunately, the only way to perform an HTTP POST with standard HTML is to use a <form> tag. Furthermore, you must use an <input type=”submit”>, <input type=”image”>, or <input type=”button”> tag to create a button for deleting a record.
There is no such thing as an <input type=”link”> tag. That’s a shame, because links look better than buttons. HTML should not tie appearance to behavior, but it does. You should be able to make a Delete link to delete a record.
The best option here is to use the <input type=”image”> tag. That way, you can make the Delete and Edit links look the same when displaying a grid of database records. Because I did not want to make my sample application depend on JavaScript, this is the approach that I am taking in the sample application.
The non-JavaScript dependent Index view is contained in Listing 3.
Listing 3 – Views\Home\Index.aspx (no JavaScript)
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<Tip46.Models.Movie>>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
<h2>Index</h2>
<table>
<% foreach (var item in Model) { %>
<tr>
<td>
<a href='<%= Url.Action("Edit", "Home", new { id = item.Id })%>'><img src="Content/Edit.png" alt="edit" border="0" /></a>
</td>
<td>
<% using (Html.BeginForm("Delete", "Home", new { id = item.Id }))
{ %>
<input type="image" src="Content/Delete.png" />
<% } %>
</td>
<td>
<%= Html.Encode(item.Id) %>
</td>
<td>
<%= Html.Encode(item.Title) %>
</td>
<td>
<%= Html.Encode(item.Director) %>
</td>
<td>
<%= Html.Encode(item.DateReleased) %>
</td>
</tr>
<% } %>
</table>
<p>
<%= Html.ActionLink("Create New", "Create") %>
</p>
</asp:Content>
I got the images for the Edit and Delete links from the Visual Studio image library (see Figure 3). You have these set of images on your hard drive at the following location:
C:\Program Files\Microsoft Visual Studio 9.0\Common7\VS2008ImageLibrary
Figure 3 – Using Images for Edit and Delete

To get the images to align correctly, I added a vertical-align style to the table cells. I used the following style rules:
table
{
border-collapse:collapse;
}
td
{
vertical-align:top;
padding:10px;
border-bottom: solid 1px black;
}
Conclusion
Don’t use Delete links to delete database records. Potentially, someone could perform a GET request and perform a delete without your consent or control.
The best option is to use JavaScript to perform an HTTP DELETE operation. Using JavaScript enables you to avoid the security hole. Using JavaScript also enables you to respect the semantics of the HTTP protocol.
If you don’t want your application to depend on JavaScript, the second best option is to perform an HTTP POST instead of an HTTP DELETE. Performing a POST operation requires you to use an HTML form. This can be ugly. However, you can improve the appearance of a form button by using an <input type=”image”> tag and adding Cascading Style Sheet styling.