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" })
|
#*#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.
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
}
Related
I might need an extra set of eyes but my delete btn is not working it does return a message but after clicking yes or ok it doesn't remove the data i wanted to delete basically nothing happens, I think I have an issue with the inventory.Id part. thank you, and i know this is not a good question for other users but i appreciate the help.
<tbody>
#foreach (var inventory in Model)
{
<tr>
<td>#Html.ActionLink(inventory.PartNumber, "Edit", "Inventory", new
{ id = inventory.Id }, null)</td>
<td>#inventory.PinNumber</td>
<td>#inventory.PartQuantity </td>
<td>#inventory.PartPrice </td>
<td>#inventory.PartDescrption</td>
<td> <button data-inventory-id="#inventory.Id" class="btn-link js-delete">Delete</button> </td>
</tr>
}
</tbody>
</table>
#section scripts
{
<script>
$(document).ready(function ()
{
$("#inventories").DataTable();
$("#inventories .js-delete").on("click", function () {
var button = $(this);
if (confirm("Are you sure you want to delete this Part Number?")) {
$.ajax({
url: "/inventory/" + button.attr("data-inventory-id"),
method: "DELETE",
success: function () {
button.parents("tr").remove();
}
});
}
});
});
</script>
}
this is my Controller for the Delete Action:
[HttpDelete]
public void DeleteInventory(int id)
{
var inventoryInDb = _context.Inventory.SingleOrDefault(c => c.Id == id);
_context.Inventory.Remove(inventoryInDb);
_context.SaveChanges();
}
I don't have an API in the Tutorial i am following He has an API but I didn't create one. I am trying to get around that.
Thank you.
How about using POST instead of DELETE as your ajax method? Or simply using the $.post method.
https://www.w3schools.com/jquery/ajax_post.asp
Most likely you did not create a DELETE method in your back-end API. To find out for sure, open Chrome's developer tools (making sure you're on the console tab) and then click your button. You will see an error message that says "Method DELETE is not found" or something similar.
If it says "method is not allowed" then that has to do with permissions (the user clicking the button does not have permission to access that API).
In controller:
public ActionResult Delete(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Inventory inventory = _context.Inventory.Find(id);
if (inventory == null)
{
return HttpNotFound();
}
return View(inventory);
}
in index add delete btn, this is an ajax call to the delete btn its used with dataTables to render data faster..
{
"render": function (data, type, row, meta) {
return '<a class="btn btn-danger"
href ="#Url.Action("Delete", "Inventory")/' + row.Id + '">Delete</a>';
}
create a Delete view:
#model InventoryTracker.Models.Inventory //this has to be re-named to your naming
convention.
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-actions no-color">
<input type="submit" value="Delete" class="btn btn-primary" />
#Html.ActionLink("Cancel", "Index", null, new { #class = "btn btn-primary" })
</div>
}
}
I have a view that has a textbox to enter a user name and then two checkboxes. It will be use to add said user name to the roles marked by the checkboxes. The text box should not allow empty/null strings to be entered and if the user name already exists, warn the person.
View
#model QIEducationWebApp.Models.UserAdmin
<h1 class="page-header">Add New User</h1>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<table class="table">
<tr>
<th class="table-row">
User Name:
</th>
<td class="table-row">
#Html.TextBoxFor(model => model.UserName)
#Html.ValidationMessageFor(model => model.UserName)
</td>
</tr>
<tr>
<th class="table-row">
Role:
</th>
<td class="table-row">
#Html.DropDownListFor(model => model.UserRole,
#ViewBag.Roles as SelectList, " -- Select Role -- ", new { #class="form-control" })
#Html.ValidationMessageFor(model => model.UserRole)
</td>
</tr>
<tr><td class="table-row-blank"></td></tr>
<tr>
<td class="table-row-button">
<input class="button" type="submit" value="Create" />
<input type="button" class="button" value="Cancel"
onclick="location.href='#Url.Action("AllUsers")'" />
</td>
</tr>
</table>
}
Model
[EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
[Required(ErrorMessage = "Required")]
[Remote("ExistUser", "Admin", HttpMethod = "POST",
ErrorMessage = "User is assinged, Edit instead")]
[DataMemberAttribute()]
public global::System.String UserName
{
get
{
return _UserName;
}
set
{
OnUserNameChanging(value);
ReportPropertyChanging("UserName");
_UserName = StructuralObject.SetValidValue(value, false);
ReportPropertyChanged("UserName");
OnUserNameChanged();
}
}
private global::System.String _UserName;
partial void OnUserNameChanging(global::System.String value);
partial void OnUserNameChanged();
Controller validation method
[HttpPost]
public JsonResult ExistUser(String UserName)
{
bool exist = db.UserAdmins.Any(u => u.UserName == UserName);
return Json(!exist);
}
Controller post method
[Authorize(Roles = "Director")]
[HttpPost]
public ActionResult AddNewUser(UserAdmin user)
{
if (ModelState.IsValid)
{
db.UserAdmins.AddObject(user);
db.SaveChanges();
return RedirectToAction("AllUsers");
}
ViewBag.Roles = new SelectList(db.UserRoles, "UserRoleID", "UserRole1", user.UserRole);
return View(user);
}
Currently it allows empty strings to be passed in on submit instead of showing the error. And my custom validation isn't even firing off and my debug is getting hit.
I have used this in other parts of the application and those still work.
P.S. If you guys need more code, just let me know and I'll get it up.
EDIT:: Completely different code than the original post.
Volkan Paksoy pointed out my missing ModelState.IsValid and view return.
And for the client side validation i was missing my
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
First off, as #Chris Bohatka suggested, you have to use your model as the parameter (User class in this case)
Then you have to check the status of the ModelState like this:
if (!ModelState.IsValid)
{
// There is an error on the page, load the same view to display the errors
}
// Model is fine, carry on with the other stuff...
I debugged and saw the "Must enter a User Name" in ModelState errors collection so what you have to do is return to the same view so that user can see the error and fix it.
Maybe you do not have client side validation enabled, in which case it will do the post even if the fields are not correct. Also, in your post controller method you aren't checking validity of ModelState, which may be invalid, meaning there's a validation issue you aren't trapping for. Add this to your controller method:
if (ModelState.IsValid) { do work };
And make sure you have these lines in web.config to enable client side (JavaScript) validation, in which case the post won't happen until the field values are valid:
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
I have a view that contains a submit button. When that submit button is clicked some code runs a process. I want to return a text message to a label on the view that will let the user know that their submission was successful or there was an error.
I've searched around and I have found many examples about labels but I haven't come across one that shows me how to do what I want.
My Controller:
public ActionResult Import()
{
//Some code that runs a process
//Need to know what code will return "Import was Successful" or "Erroring Importing"
return RedirectToAction("Import")
}
My View:
#{
ViewBag.Title = "Import";
}
<h2>Import</h2>
#using (Html.BeginForm("Importexcel", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<table>
<tr><td>Import Files</td><td><input type="file" id="FileUpload1" name="FileUpload1" /></td></tr>
<tr><td></td><td><input type="submit" id="Submit" name="Submit" value="Submit" /></td></tr>
**<tr><td>#Html.Label(returned results)</td></tr>** // Need to know how to do this
</table>
}
In your view:
#using (Html.BeginForm("Importexcel", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<table>
<tr><td>Import Files</td><td><input type="file" id="FileUpload1" name="FileUpload1" /></td></tr>
<tr><td></td><td><input type="submit" id="Submit" name="Submit" value="Submit" /></td></tr>
**<tr><td>#Html.Label(returned results)</td></tr>** // Need to know how to do this
</table>
#ViewBag.Message
}
In your controller:
[HttpPost]
public ActionResult Import(){
//Some code that runs a process
//Need to know what code will return "Import was Successful" or "Erroring Importing"
if(something){
ViewBag.Message = "Import Failed";
}
else
{
ViewBag.Message = "Import Successful";
}
return View();
}
Try and give that a shot.
You can always pass either a key to a look-up table of messages or the message itself via the query string. Here's an example:
Controller Action
public ActionResult Import(string message = null)
{
// Detect presence of message (i.e. !String.IsNullOrWhiteSpace(message)) and show it.
// Additional logic after this...
return RedirectToAction("Import", "YourControllerNameHere", new { message = "Your message here..." });
}
Then it's just a matter of wiring up your model or ViewModel in the Import view so it displays the appropriate message.
I'm working on asp.net mvc 3 application. I'm implementing a razor view which have tow main functions - to build/display form based on a data from a data base and to show images related to this form in a custom (made by me) image gallery that allows upload and delete of image.
So generally this is my view with both forms for visualizing the form and showing and uploading image(s) :
#model List<DataAccess.MCS_DocumentFields>
#{
ViewBag.Title = "Документ";
}
<div id="alabala">
<div id="drawForm">
#using (Html.BeginForm("UpdateDocument", "Forms", FormMethod.Post))
{
<table border="1" id="drawDocument">
<colgroup>
<col span="1" style="width: 10%;" />
<col span="1" style="width: 40%;" />
<col span="1" style="width: 25%;" />
<col span="1" style="width: 25%;" />
</colgroup>
#Html.Partial("_PartialHeader", Model)
#Html.Partial("_PartialDrawing", Model)
#Html.Partial("_PartialBody", Model)
#Html.Partial("_PartialFooter", Model)
</table>
if (ViewBag.Status == 1)
{
<button type="submit" id="submitDocument">Запази</button>
<button style="float:right;" id="finalizeDocument">Приключи</button>
}
else
{
#Html.ActionLink("Назад", "Index")
}
}
</div>
<div id="imageContainer">
<div id="imageGallery" style="overflow: scroll">
<img src="file:\\..." alt="docImg" style="width: 190px; height: auto"/>
#Ajax.ActionLink("Delete", "DeletePicture", new { documentID = Model[0].Id },
new AjaxOptions
{
Confirm = "Are you sure?",
OnComplete = "$('#blah').attr('src', '#').attr('style', 'display:none;'); $('#Image1').attr('src', '#').attr('style', 'display:none;'); $('#DelPic').attr('style', 'display:none;');"
})
<img src="file:\\..." alt="docImg" style="width: 190px; height: auto"/>
#Ajax.ActionLink("Delete", "DeletePicture", new { documentID = Model[0].Id },
new AjaxOptions
{
Confirm = "Are you sure?",
OnComplete = "$('#blah').attr('src', '#').attr('style', 'display:none;'); $('#Image1').attr('src', '#').attr('style', 'display:none;'); $('#DelPic').attr('style', 'display:none;');"
})
</div>
#using (Html.BeginForm("Upload", "Forms", FormMethod.Post))
{
<input name=#Model[0].DocumentId type="hidden" />
<input type="file" name="datafile" id="file" onchange="readURL(this);" />
<input type="button" name="Button" value="Upload" id="UploadButton" onclick="fileUpload(this.form,'/forms/upload','upload'); return false;"/>
<div id="upload" style="display: inline-block;">
<img id="blah" src="#" alt="your image" style="display:none;"/>
</div>
}
</div>
</div>
The first form is where I show the data for a current form/document and because they are editable I have submit button. I need the second form to submit the selected picture to my controller and there to perform the business logic.
So once a picture is selected and Upload button is clicked I get to my controller :
public ActionResult Upload(FormCollection collection)
{
WebImage UploadImage = WebImage.GetImageFromRequest();
long documentID;
string finalImageName = null;
if (!long.TryParse(collection.AllKeys[0], out documentID))
//More code...
Where I have the image and the id of the document that it belongs to and what I need is to perform some checks/validations and finally to coy the selected image to dedicated directory and save the name to the data base.
The problem is that I have all the logic written except the one that will show the correct messages for the different outputs like :
if (imagePath.Length > 247)
{
//TODO message that the path is too long
//TODO this return View() is temp, replace with something suitable
return View();
}
//...
System.IO.File.Copy(UploadImage.FileName, imagePath);
}
catch (Exception ex)
{
//TODO copy failed return message
return View();
}
//...
These are all different outputs from the execution of the same method and in the main view I want to show a proper message for each one of them. What I'm not sure is if I still have an option to save my work and still implement the message logic? Thinking now it seems that if I was using Ajax in some form it would be a lot easier now but I'm not. The only think I can think of know is creating ViewBag property, and returning it with the model to the view where to check the different properties and show some message if necessary based on that, but this means a lot of additional logic in my view, resending data from the database that I already have shown in my view and a lot of double work said in short, something that I consider as a bad programming, but maybe I got myself into this. So what is the best course of action from here on. Is it best to just remove my code and search for a way to do this with AJAX?
You can't upload a file using AJAX - you'd need some sort of 3rd party workaround. You need to return the original view from the Upload() method, with the appropriate model, and also a flag within the ViewBag somewhere to display the message, e.g.
public ActionResult Upload(UpdateDocumentModel model) {
...
if (imagePath.Length > 247) {
model.ErrorMessage = Errors.Over247;
return View("UpdateDocument", model);
}
...
return RedirectToAction("UploadOk");
}
I've changed your FormCollection to a strongly-typed model for ease of reading, plus that's what MVC.net is there for. The Errors.Over247 could be a string resource somewhere in your project, or a boolean flag which the View then reads to show a certain piece of HTML.
just use TempData instead of ViewBag
TempData["ErorrMessegge"] = "SomeMessage to view";
#TempData["ErorrMessegge"]
You can simply use TempData[] in order to pass parameter from Controller to View like below:
Controller:
[HttpPost]
public ActionResult Add(Applicant applicant)
{
repository.SaveApplicant(applicant);
TempData["message"] = "The applicant has been saved succesfully.";
return View(applicant);
}
View:
#if (TempData["message"] != null)
{
<div>#TempData["message"]</div>
}
The problem is I have two forms in the same view. The controller is Users and I have two actions for the two forms: Edit and UploadPhoto.
I have been using the Edit portion for awhile and it's working. Now I want to allow users to upload their photo on the same page. There is a separate button for saving the user information and another for saving the picture.
The page model is #model sportingbiz.Models.ViewModels.UserEditViewModel
The second form
<fieldset title="Upload photo">
<legend>Change Profile Picture</legend>
<div>
#using(Html.BeginForm("UploadPhoto", "Users", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<table>
<tr>
<td>
<img src="#Url.Content("~/Content/images/noprofilepic.jpg")"
</td>
<td>
<input type="file" name="photoFile" />
<br /><br />
<input type="submit" value="Upload" />
</td>
</tr>
</table>
}
</div>
</fieldset>
Controller
public class UsersController : Controller
{
UsersRepository usersRepo = new UsersRepository();
[Authorize]
[HttpPost]
public void UploadPhoto(HttpPostedFile photoFile)
{
if (photoFile == null)
{
return;
}
if (photoFile.ContentLength <= 0)
{
return;
}
string filename = Path.GetFileName(photoFile.FileName);
if (Path.GetExtension(filename) == "")
return;
}
}
When I click upload my page navigates to http://localhost/mvcapp/Users/UploadPhoto with a blank screen. What I want is to return to the Edit page and probably show errors to the user that file was not uploaded. Features like ModelState.IsValid or ModelState.AddModelError
There is also no option of returning the same view e.g. return View(model)
Am I doing this wrong way? Some suggestion is to use Ajax uploader or Uploader. Am also thinking of separating the forms putting the Upload portion in its own view. Can PartialView help in this respect
[Authorize]
[HttpPost]
public void UploadPhoto(HttpPostedFile photoFile)
{
An Action with a void return is very strange. Make it return an ActionResult and determine on each branch where you want to go.
[Authorize]
[HttpPost]
public void UploadPhoto(HttpPostedFile photoFile)
{
if (photoFile == null)
{
return RedirectToAction("ErrorPage");
}
...
var viewModel = ...; // but your've lost the Edit part
return View(viewModel);
}
try to validate the using jquery like below, which will make sure that user are force to select some file...
lets us assume you a view like below
<input type="file" name="Document" id="imgFile" style="height:25px;"/>
<input type="submit" value="Submit" id="btnSubmit"/>
below is the jquery to validate
$('#imgFile').change(function() {
if ( ! $('#imgFile').val() ) {
alert('Chose a file!');
return false;
}
});
or you can do this same on the Button click like below
$('#btnSubmit').click(function() {
if ( ! $('#imgFile').val() ) {
alert('Chose a file!');
return false;
}
});
hope this might help you...
you uplode your image like that
public void UploadPhoto(HttpPostedFile photoFile)
{}
i think you should little bit change you code like this
public void UploadPhoto(HttpPostedFileBase photoFile)
{}