Simple jQuery Delete Link For ASP.NET MVC

asp.net, asp.net mvc 0 comments suggest edit

UPDATE: I have a followup to this post that works for down-level browsers.

In a recent post, Stephen Walther pointed out the dangers of using a link to delete data. Go read it as it provides very good coverage of the issues. The problem is not restricted to delete operations. Any time you allow a GET request to modify data, you’re asking for trouble. Read this story about something that happened to BackPack way back in the day to see what I mean.

The reason that delete operations deserve special attention is that it’s the most common case where you would use a link to change information. If you were editing a product record, for example, you would use a form. But a delete operation typically only needs one piece of information (the id) which is easy to encode in the URL of a GET request.

If you are using jQuery, one simple way to turn any link into a POST link is to add the following onclick attribute value:

$.post(this.href); return false;

For example

<a href="/go/delete/1" onclick="$.post(this.href); return false;">Delete</a>

Will now make a POST request to /go/delete/1 rather than a GET. Of course, you need to enforce this on the server side. This is pretty easy with ASP.NET MVC.

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Delete(int id) {
  //Delete that stuff!
}

The AcceptVerbs attribute specifies that this action method only responds to POST requests, not GET requests.

At this point, you could easily write helpers specifically for delete links. I usually write very specific helper methods such as Html.DeleteProduct or Html.DeleteQuestion. Here’s an example of one I wrote for a sample app I’m building.

public static string DeleteAnswerLink(this HtmlHelper html, string linkText
  , Answer answer) {
    return html.RouteLink(linkText, "answer",
        new { answerId = answer.Id, action = "delete" }, 
        new { onclick="$.post(this.href); return false;" });
}

The nice thing about this approach is that you can leverage the existing helper methods by adding a minimal amount of extra information via the onclick attribute.

I hope the combination of Stephen’s post and this post will lead you to safer deleting.

Found a typo or error? Suggest an edit! If accepted, your contribution is listed automatically here.

Comments

avatar

26 responses

  1. Avatar for Kevin Pang
    Kevin Pang January 30th, 2009

    I like the approach, although I wish there was a way to utilize it while still having graceful degradation for users with javascript disabled.

  2. Avatar for Ralph Whitbeck
    Ralph Whitbeck January 30th, 2009

    @Kevin the nice thing though is that it won't work so there is no ability for errant deletes.

  3. Avatar for Kevin Pang
    Kevin Pang January 30th, 2009

    Yes, it's nice that you won't get errant deletes. It would be nicer if it would actually work so people with javascript disabled (or on mobile phones that don't support jQuery) can still use your site. :-)

  4. Avatar for ReneS
    ReneS January 30th, 2009

    The common sense is: GET for fetching data, POST for modifying data.

  5. Avatar for Richard Spiller
    Richard Spiller January 30th, 2009

    @Kevin, if you look at the post by Stephen Walther that Phil mentions, he demonstrates a couple of other ways to provide safe delete links. One of which specifically addresses the issue of working well without Javascript. -HTH

  6. Avatar for Chad Myers
    Chad Myers January 30th, 2009

    I'm not sure, but I do recall reading something once that putting JS in 'onclick' attributes is not preferable and can cause memory leaks or something. Forgive as I forget the specifics, but I do remember it being strongly advised against.
    One thing you might consider is having a CSS class like "deleteLink" and then, in your document ready doing:

    $(document).ready(function(){
    $('.deleteLink').click(function(){
    $.post(this.href); return false;
    });
    });
    The jQuery experts seem to encourage this type progressive enhancement which has other benefits besides "not having JS in your markup."
    The "What if Javascript is disabled?" problem is also an issue, but I'm guessing that if you have the requirement to support no-JS that you have other, larger issues than just delete links, lol.

  7. Avatar for Steve
    Steve January 30th, 2009

    Since you require jquery, it might make sense to take the onclick off and add a class instead, then unobtrusively use:
    $("a.whatever").live('click', function(ev) { ev.preventDefault(); $.post(this.href); }

  8. Avatar for Steve
    Steve January 30th, 2009

    (Chad's comment above is correct, but won't work with AJAX content that's loaded later. That's the nice thing about jq 1.3's "live" events)

  9. Avatar for Chris Hardy
    Chris Hardy January 30th, 2009

    Phil,
    On the subject of AcceptVerbs - is there ever going to be the option to put the AcceptVerbs attribute on a Controller so all actions on the controller will adhere to this unless overwritten on the action attribute? This will be useful when 80% of the methods on a API might be need to be post for reasons mentioned above (as they are Create/Edit and Delete actions).
    ChrisNTR

  10. Avatar for haacked
    haacked January 30th, 2009

    @Steve ah, good point. I should try updating my sample. For down-level browsers, I could render a form with a submit button for deletion. Then the javascript could simply hide the form and do the submit itself.

  11. Avatar for Kazi Manzur Rashid
    Kazi Manzur Rashid January 30th, 2009

    I think the whole issue is security not http get vs post or any client side scripting. No one should write any code against http Get that can modify data which makes the application vulnerable. Those who have played with early bits of ASP.NET Ajax(Atlas) or has pure (WebForm less) http progarmming already knows this issue.

  12. Avatar for Ricky
    Ricky January 30th, 2009

    Agreed with Chad Myers on the better way to write this code...
    BUT, this JavaScript doesn't provide any security at all. All I have to put on my evil page is a 1 pixel iframe with a form that auto posts on load to that URL.
    Isn't this the point of the Anti-Request Forgery token thingy you guys have in the framework or futures?

  13. Avatar for Ricky
    Ricky January 30th, 2009

    And BTW, $.post(url) will do an XHR (AJAX) POST so you will not see anything happen if you don't add a callback to handle the response...

  14. Avatar for haacked
    haacked January 30th, 2009

    @Ricky yeah, I haven't addressed the CSRF aspect of this, which is probably something I should cover.
    I did create a new post addressing the downlevel browser issue.

  15. Avatar for configurator
    configurator January 31st, 2009

    Phil, you could easily render a form with nothing but a submit button:
    <form method="post"><input type="submit" value="Confirm delete" /></form>
    This would render in the delete action when accessed with GET - i.e. browsers without javascript. So there would be no need to use javascript to submit the form; nobody with javascript would see it.

  16. Avatar for Mark
    Mark February 1st, 2009


    Just use css to make your submit button look like a link, Done, simple.
    Forget all the javascript etc.

  17. Avatar for Slashene
    Slashene February 14th, 2009

    Why not using the DELETE verb instead of POST ???

  18. Avatar for haacked
    haacked February 14th, 2009

    Impossible to make a DELETE request from a downlevel browser.

  19. Avatar for Ruairi
    Ruairi April 6th, 2009

    I'm "trying" to do best practice by POSTing when doing a delete, but I'm guessing I should be using the [ValidateAntiForgeryToken] attribute with the post. Am I going to far with this security with CSRF?
    The page gets messy because the API creates the token in a hidden form field, but If I'm doing a JQuery $.post(...) I'd prefer the token without the hidden element. Curently I've got Jquery traversing the HTML to find the
    $('input[hidden]').attr('value')
    Any suggestions?

  20. Avatar for Kara
    Kara January 13th, 2010

    I have a simple [href] tag which use to open aspx page inside jquery modal popup. but when click the image, redirect to "can not find page". basically can' t recognize the URL. here is what i' m talking about:-

    <img src="../../App_Themes/DefaultTheme/images/car.jpeg" alt="" border="0px" />
    Please, any idea about this?

  21. Avatar for ASM
    ASM February 19th, 2010

    It's still very dangerous no matter what you as long as there is an action to delete records from the DB. I can simply type in the address bar of the browser after the document is loaded
    Javascript:$.post('/Home/Delete/1'); and it will delete.
    Mind you every thing you put in the page is exposed wheather it's a hidden field or whatever. Javascripst is not good if you allow it to handle operations that should be made on the server side.
    You should not use Ajax or Javascript to delete anything from the DB. it's very risky and it is EXPOSED

  22. Avatar for ASM
    ASM February 19th, 2010

    What if there is a mechanism to encrypt/decorate action names?
    I also think the javascript call should be tied with a control of the page's chidren tree not any source.

  23. Avatar for Andrew Siemer
    Andrew Siemer April 20th, 2010

    Phil,
    Great little snippet! It inspired a recipe in my "ASP.NET MVC Cookbook" (currently towards the end of chapter 5) to be posted here shortly: groups.google.com/.../aspnet-mvc-2-cookbook-review
    Thanks for sharing.
    Andrew Siemer

  24. Avatar for m-r Tarakanoff
    m-r Tarakanoff June 2nd, 2010

    I immediately rewrote their draft in the light of this message :) Thank!

  25. Avatar for Stephen Wilson
    Stephen Wilson August 7th, 2010

    Hi Phil,
    Thanks for this tip! I am having a small issue using it however that I hope you could shed some light on.
    Currently I have the link:
    <code<% Response.Write(supplier.IsActive ? "Deactivate" : "Activate"); %>
    This works fine and fires the 'Activate' action:

    [HttpPost]
    [Authorize]
    public ActionResult Activate(int id)
    {
    SupplierRepository sr = new SupplierRepository();
    Supplier supplier = sr.GetSupplier(id);
    sr.SetSupplierActive(id, !supplier.IsActive);
    return RedirectToAction("Index");
    }

    Which in turn fires the index action:

    [Authorize]
    public ActionResult Index()
    {
    SupplierRepository sr = new SupplierRepository();
    return View(new SuppliersViewModel
    {
    suppliers = sr.GetAllSuppliers().ToList()
    });
    }

    But because I am redirecting back to the same page that made the post (at least thats what I think the issues is) The page does not get updated / re-rendered.
    Letting the link fire as a 'get' request works fine, presumably because in the browsers mind it has gone somewhere, so the re-render is necessary.
    I have tried to find a solution for this but all the search terms I could think of to find an answer didnt come up with anything.
    This seams like a pretty basic issue, what am I missing?
    Thanks
    Stephen.

  26. Avatar for Frank
    Frank August 22nd, 2010

    Who still uses down-level browsers? Even the worst UK govt depts are are on IE 3.0. Surely lynx has died by now