Francis Rodgers Francis Rodgers - 3 months ago 11
C# Question

Passing a filename to a view

I am trying to build a simple CMS which allows the author to upload files (specifically images but the file type is not really important for now).

The upload of the file is working fine. However I want to provide the ability to list and subsequently delete a file (maybe later multiple files but for now a single file at a time is fine).

I have looked around the net. I see plenty of examples using EF to store the location of the file in a DB because they have permissions and roles etc. While that is something I may need way off in the future, its not a layer of complexity I am willing to add right now.

All I want is to simply press a delete link (just as though you are deleting a record in a DB). To trigger an action which calls a delete confirmation view. Then on that view, a delete button to actually delete the file and return the user to the list. Below is my code so far:

This would be the view that lists the files:

@model IEnumerable<FileInfo>

@{
ViewBag.Title = "File List";
}

<h2>Index</h2>

<p>
@Html.ActionLink("Upload", "Upload")
</p>
<table class="table">
<tr>
<th>File Name</th>
<th>Actions</th>
</tr>

@foreach (FileInfo file in Model)
{
<tr>
<td>@file.Name</td>
<td>@Html.ActionLink("Delete", "Delete", new { fileName = @file.Name })</td>
</tr>
}
</table>


I wont show the controller for this view as it's relatively simple and not where I am having the problem (I think). I only showed this so you could see the delete link and tell me if there is anything wrong.

Below is the delete confirmation view:

@model FileInfo

@{
ViewBag.Title = "Delete";
}

<h2>Delete</h2>

<h3>Are you sure you want to delete this?</h3>

<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.FullName)
</dt>

<dd>
@Html.DisplayFor(model => model.FullName)
</dd>
</dl>
@using (Html.BeginForm("Delete", "FileManagement", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
@Html.AntiForgeryToken()

<div class="form-actions no-color">
@Html.ActionLink("Back to list of views", "Index", null, new { @class = "btn btn-success" })
&nbsp;|&nbsp;
@*@Html.ActionLink("Delete", "Delete", null, new { @class = "btn btn-danger" })*@
<input type="submit" value="Delete file" formaction="Delete" formmethod="delete" class="btn btn-danger" />
</div>
}


Below are the two Delete actions (GET and POST / DELETE)

// GET: FileManagement/Delete/filename
public ActionResult Delete()
{
return View();
}

// POST: FileManagement/Delete/filename
[HttpDelete]
[ValidateAntiForgeryToken]
public ActionResult Delete(string fileName)
{
var path = Path.Combine(Server.MapPath("~/UserFiles"), fileName);

if (System.IO.File.Exists(path))
System.IO.File.Delete(path);
else
return HttpNotFound();

return RedirectToAction("Index");
}


I don't have view models as I am not connecting to a database (yet). The files are just uploaded to the folder ~/UserFiles/someFileName.ext and the full path is got through appending this to the server.mappath in the normal way.

The problem I am having is getting the file name into the delete confirmation view, and also into the delete button which would pass it to the delete action to do the job.

Thanks for any help.

Answer

In your main view (I assume that Index.cshtml), you correctly generate a query string value for the fileName, but the GET method does not have a parameter to accept it. It would need to be

// GET: FileManagement/Delete/filename
public ActionResult Delete(string fileName)

and in that method you would need to initialize a new FileInfo class based on the fileName and pass that model to the view.

The next issue is that your form in the confirm page does not pass the file name back to the POST method, but that raises another issue in that you cannot have a GET and POST method with the same signatute, so you would need to change the name of one of the methods, for example

[HttpGet]
public ActionResult ConfirmDelete(string fileName)

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Delete(string fileName)

and in the confirm delete page, change the form to

@using (Html.BeginForm("Delete", "FileManagement", new { fileName = Model.Name })) // enctype not required
{
    @Html.AntiForgeryToken()
    <input type="submit" value="Delete file" class="btn btn-danger" />
}

However, you can greatly improve performance by generating the form in the Index view and displaying a confirm dialog (the GET method is no longer required)

@foreach (FileInfo file in Model)
{
    ....
    @using(Html.BeginForm("Delete", "FileManagement", new { fileName = file.Name }))
    {
        @Html.AntiForgeryToken()
        <input type="submit" value="delete" />
    }
}

and adding a script to display the dialog

$('form').submit(function() {
    return conform("Are your sure .... ");
});

which will display the browsers javascript confirm dialog. You can further enhance the UI by using a jquery plugin for the confirm dialog (or implement your own, as explained in this article)


You should also consider using ajax to submit the form (and in the success callback, remove the button and its associated table row). A typical implementation might look like

@foreach (FileInfo file in Model)
{
    <tr>
        <td>@file.Name</td>
        <td>
            <form class="deleteform">
                @Html.AntiForgeryToken()
                <input type="hidden" name="fileName" value="@file.Name" />
                <input type="submit" value="delete" />
            </form>
        </td>
    </tr>
}

var url = '@Url.Action("Delete", "FileManagement")';
$('.deleteform').submit(function() {
    var formData = $(this).serialize();
    var row = $(this).closest('tr');
    $.post(url, formData, function(response) {
        if (response) {
            row.remove();
        } else {
            // Oops - display message?
        }
    }).fail(function (response) {
        // Oops
    });
    return false; // cancel the default submit
});

and the controller method

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Delete(string fileName)
{
    .... // delete the file
    return Json(true); // indicate success
    // or return Json(null); to indicate failure
}
Comments