MoonKnight
MoonKnight

Reputation: 23833

ActionLink in Table MVC

Edit: I have read and implemented the method oulined in this article but this does not work.

I have the following table which is populated correctly.

<table class="table">
   <tr>
       <th>Product</th>
       <th>File Name</th>
       <th>Release Date</th>
       <th>Size</th>
       <th></th>
   </tr>
   @{
       if (Model != null)
       {
           foreach (var item in Model.Uploads)
           {
               <tr>
                   <td>@Html.DisplayFor(modelItem => item.Product)</td>
                   <td>@Html.DisplayFor(modelItem => item.DisplayName)</td>
                   <td>@Html.DisplayFor(modelItem => item.ReleaseDate)</td>
                   <td>@Html.DisplayFor(modelItem => item.Size)</td>
                   <td>@Html.ActionLink("Delete File", "Upload", "Tools", new { id = item.DisplayName }, null)</td>
               </tr>
           }
       }
   }
</table>

I also have a controller with the action

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> DeleteUser(
    AdministratorViewModel model, string userName)
{
    // Do amazing stuff...
    return Index();
}

I want to pass to this action the username that was selected for deletion. I thought I could achive this with @Html.ActionLink as above, but this is not the way to go.

How can I pass the selected username that was selected to my action method?

Thanks for your time.


Edit: changing the Ajax code to the following (using Index as the called method)

@Ajax.ActionLink(
    "Remove",
    "Index", 
    "Tools",
    new
    {
        model = item,
        userName = item.UserName
    },
    new AjaxOptions
    {
        InsertionMode = InsertionMode.Replace,
        HttpMethod = "POST"
    })

and changed the name of the DeleteUser(AdministratorViewModel model, string userName) method to Index(AdministratorViewModel model, string userName). This now fires the Index method in the ToolsController. But the method called is the NON-POST attributed method(!?) so I now have two questions:

  1. How can it be calling the method not marked with [HttpPost] attribute?
  2. Why can I call the DeleteUser method using Ben Griffiths' answer below?
  3. How can I call my DeleteUser method in the Tools controller and pass in the model and the name of the user I want to delete?

Thanks for your time.

Upvotes: 1

Views: 11162

Answers (2)

Ben Griffiths
Ben Griffiths

Reputation: 1696

The fourth argument to the overload of the ActionLink extension method that you are using when you call

Html.ActionLink("Delete File", "Upload", "Tools", new { id = item.DisplayName }, null)

is for the route values parameter, which you can use to pass data back to your controller.

Currently, in the above call, the Upload action method on the Tools controller will be receiving an id argument. If you want to pass a display name back to your DeleteUser action method, you could use

Html.ActionLink("Delete User", "DeleteUser", "[ControllerName]", new { userName = item.DisplayName }, null)

However, the DeleteUser method is decorated with the HttpPost attribute, meaning that the action will only accept requests that use the post method. This means that you have three options:

(1) Remove the [HttpPost] attribute from the action method - probably NOT a good idea, since I imagine you don't want to be exposing this action to get requests for good reason.

(2) Use a form containing a submit input and a hidden DisplayName input instead of a link.

(3) Use the AjaxHelper.ActionLink extension method to make an asynchronous postback to your controller. e.g.:

Ajax.ActionLink("Delete User", "DeleteUser", "[ControllerName]", new { userName = item.DisplayName }, new AjaxOptions{ HttpMethod = "Post" })

Update: a more complete example of the third option

Here's a working (albeit very simple) example of the third option. I'm not 100% sure what the ultimate goal is, so rather than try to provide a realistic example I've tried to create one that is simple but clear. Note that I've omitted the handling of the anti forgery token to aid in this clarity, but I've stuck with an async action method so as not to deviate too far from real life.

Controller:

public class HomeController : Controller {

    private async Task<string> Delete(string displayName) {
        Thread.Sleep(1000);
        return string.Format("{0} has been deleted", displayName);
    }

    [HttpPost]
    [AllowAnonymous]
    public async Task<JsonResult> DeleteItem(string displayName, int product) {

        Task<string> deleteTask = Delete(displayName);

        return new JsonResult() {
            Data = new { 
                product = product,
                result = await deleteTask }
        };
    }



    public ActionResult Index() {

        AdministratorViewModel model = new AdministratorViewModel() {
            Uploads = new List<ItemModel>() {
                new ItemModel() {
                    DisplayName = "First one",
                    Product = 1,
                    ReleaseDate = DateTime.Now,
                    Size = 11
                },
                new ItemModel() {
                    DisplayName = "Second one",
                    Product = 2,
                    ReleaseDate = DateTime.Now.AddDays(1),
                    Size = 12
                }
            }
        };


        return View(model);
    }

}

Model:

public class AdministratorViewModel {
    public IEnumerable<ItemModel> Uploads { get; set; }
}

Layout:

<!DOCTYPE html>
<html>
    <head>
        <title>Demo</title>
    </head>
    <body>
        @RenderBody()
        <script src="~/Scripts/jquery-1.10.2.js"></script>
        <script src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
        @RenderSection("scripts", false)
    </body>
</html>

Home/Index view:

 @model AdministratorViewModel

 <table class="table">
    <tr>
        <th>Product</th>
        <th>File Name</th>
        <th>Release Date</th>
        <th>Size</th>
        <th></th>
    </tr>
    @{
        if (Model != null) {
            foreach (var item in Model.Uploads) {
                <tr>
                    <td>@Html.DisplayFor(modelItem => item.Product)</td>
                    <td>@Html.DisplayFor(modelItem => item.DisplayName)</td>
                    <td>@Html.DisplayFor(modelItem => item.ReleaseDate)</td>
                    <td>@Html.DisplayFor(modelItem => item.Size)</td>
                    <td>@Ajax.ActionLink("Remove", "DeleteItem", "Home", new { displayName = item.DisplayName, product = item.Product }, new AjaxOptions { HttpMethod = "POST", OnComplete = "itemDeleted" }, new { id = item.Product })</td>
                </tr>
            }
        }
    }
</table>

@section scripts {
    <script>
        var itemDeleted = function (data) {
            var $link = $('#' + data.responseJSON.product);
            $link.parents('tr')
                 .children()
                 .css('text-decoration', 'line-through');

            $link.remove();

            alert(data.responseJSON.result);
        };
    </script>
}

Upvotes: 5

Ktt
Ktt

Reputation: 469

I think first you have to handle the url by adding routing like

routes.MapRoute(
                name: "Default",
                url: "{User}/{DeleteUser}/{username}",
                defaults: new { controller = "Home", action = "Index", username = UrlParameter.Optional }
            );

Upvotes: 1

Related Questions