Delete file from directory in MVC - c#

I wrote some code in MVC a year ago and my knowledge of the framework seems to have vanished. In the code block below, I list all the files in a directory and provide a link to download them (for authenticated users). What I want to do is give the option to delete each file as well. I just added a delete button, but I am not sure where to go from there?
#{IEnumerable<string> enumerateFiles = Directory.EnumerateFiles(Server.MapPath("~/Content/Documents"));}
#{
if (Request.IsAuthenticated)
{
<h3>Authenticated User: #User.Identity.Name</h3>
<h4>-Downloadable Files-</h4>
<ul>
#foreach (var fullPath in enumerateFiles)
{
var fileName = Path.GetFileName(fullPath);
<li> #fileName
<button type="button" id="fileRemover" value="Delete" onclick="return confirm('Are you sure?')" >Delete</button>
</li>
}
</ul>
}
else
{
<h3>Non-Authenticate User, register and/or login to see documents</h3>
}
}

The code for viewing files and deleting files should be contained within the controller. Your view is meant for simply displaying information (usually from your model) back to the user.
If I were you, I would structure my controller like this:
public class FilesController : Controller
{
public ActionResult List()
{
List<FileInfo> model = new List<FileInfo>();
// Grab all file names from the directory and place them within the model.
return View(model);
}
public ActionResult View(string fileName)
{
// Add header for content type
// Grab (and verify) file based on input parameter fileName
return File(...);
}
public ActionResult Delete(string fileName)
{
// Verify file exists
// Delete file if it exists
return RedirectToAction("List");
}
}

The filename should come as a HTTP POST variable.
So you must create an hidden field to hold the file name so that you can access the value on the action when the form is submitted.
Above the action name you would use the [HttpPost] attribute so that form submit lands on this action.
It is safe to have a HTTP POST instead of HTTP GET otherwise anyone with a url will be able to delete a file.
If you have multiple file names, then each hidden field can have the name as filename_1, filename_2 etc.
I have given you the direction where to look & investigate.

Related

Display a collection of images after each upload MVC

Have gone through the first 3 pages of Google and still can't get to the bottom of this. I have a controller which I am using to upload images:
[HttpPost]
[Authorize(Roles = "Admin,Tradesman,Customer")]
public ActionResult UploadFile(HttpPostedFileBase file)
{
// to do: ensure only valid file types are sent
try
{
if (file.ContentLength > 0)
{
using (var ctx = new ApplicationDbContext())
{
if (ModelState.IsValid)
{
// Need to check we have a current UserId and JobId before we go any furthur
var profileData = Session["UserProfile"] as UserProfileSessionData;
if (profileData.JobIdGuid.ToString().Length != 36)
{
// to do: something went horribly wrong! Redirect back to main view
}
if (profileData.UserIdGuid.ToString().Length != 36)
{
// to do: something went horribly wrong! Redirect back to main view
}
var photo = new Photos();
photo.Guid = Guid.NewGuid();
photo.Url = Server.MapPath("~/Images/2017");
photo.Extension = Path.GetExtension(file.FileName);
photo.JobGuid = profileData.JobIdGuid;
photo.UserIdGuid = profileData.UserIdGuid;
photo.Timestamp = DateTime.Now;
ctx.Photo.Add(photo);
ctx.SaveChanges();
string _path = Path.Combine(photo.Url, photo.Guid.ToString() + photo.Extension);
file.SaveAs(_path);
}
}
}
ViewBag.Message = "File Uploaded Successfully.";
return View();
}
catch
{
ViewBag.Message = "File upload failed.";
return View();
}
}
Each image is saved to a given location, the location saved to the db, happy days. Want I want though is for my images to be displayed on the same page after each upload. The model is as you'd expect just Id, Guid, Url, Extension, UserId, Timestamp.
Here is the view that uploads the images:
#{
ViewBag.Title = "UploadFile";
}
<h2>Upload File</h2>
#using (Html.BeginForm("UploadFile", "Job", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<div>
#Html.TextBox("file", "", new { type = "file" }) 
<br />
<input type="submit" value="Next" />
#ViewBag.Message
</div>  
// to do display the images uploaded
}
Is it possible to just have some kind of for...each and have each displayed at the bottom? Anyone know how to do this! Btw this is my first C# MVC app so if this is daft question I apologise. Thanks in advance :)
You should be following the P-R-G pattern. After successfully saving the data in your HttpPost action method, you should do a redirect to your GET action method, where you will read the data you need and pass it to the view where you will display it.
I would create a view model to represent each image and use that
public class ProfileImageVm
{
public string FileName { set;get;}
public DateTime CreatedTime { set;get;}
}
Now, for your save partin your http post action method, i would advise you to not save the physical location of the file in the table. The Server.MapPath returns the physical path. Storing that is unnecessary. What if you decide to move the location to some other directory in the server tomorrow? You could simply store the unique fileName. Let's assume that you want to store all the files in the Images/2017 in app root ,you can use Server.MapPath to get the physical location so that you can store the file in disk, but do not use that to store your table record.
var fileName = Path.GetFileNameWithoutExtension(file.FileName);
photo.Url = fileName ;
photo.Extension = Path.GetExtension(file.FileName);
With this code, it is simply storing the file name(without extension) as it is, not a unique name. That means, if you are uploading a second file with same name, it will overwrite the first one in disk. If you want to generate a unique file name, use the GetUniqueName method from this post.
Now in the GET action method, you read the Photos collection and create a list of our view model from that.
public ActionResult UploadFile()
{
var list= ctx.Photos
.Select(x=>new ProfileImageVm { FileName=x.Url + x.Extension ,
CreatedTime = x.Timestamp })
.ToList();
return View(list);
}
Now in your UploadFile view will be strongly typed to a list of ProfileImageVm, you can loop through the model data and render the images.
#model List<ProfileImageVm>
#using (Html.BeginForm("UploadFile", "Job", FormMethod.Post,
new { enctype = "multipart/form-data" }))
{
#Html.TextBox("file", "", new { type = "file" })
<input type="submit" value="Next" />
}
<h3>Images</h3>
#foreach(var item in Model)
{
<img src="~/Images/2017/#item.FileName" />
<p>Uploaded at #item.CreatedTime </p>
}
Now, after successfully saving the photo and the record in table, you will return a redirect response to the GET action.
file.SaveAs(_path);
return RedirectToAction("Upload","Job");
You can also keep the base path ~/Images/2017 in a config settings/constant and use that across your app so if you ever decide to change it to ~/Images/profilepics, there is only one place you have to change.

ASP.NET MVC - How to call void controller method without leaving the view?

Question background:
I am implementing some basic 'shopping cart' logic to an MVC app. Currently when I click a link - denoted as 'Add To Cart' on the screen shot below this calls to an 'AddToCart' method in the 'ProductController' as shown:
Product.cshtml code:
#Html.ActionLink("Add To Cart", "AddToCart")
'AddToCart' method in the ProductController:
public void AddToCart()
{
//Logic to add item to the cart.
}
The issue:
Not an issue as such but currently when I click the 'Add To Cart' button on the ActionLink on the ProductDetail.cshtml view the page calls the 'AddToCart' method on the ProductController and gives a blank view on the page - as shown below. I want the view to stay on 'ProductDetail.cshtml' and just call the 'AddToCart' method, how do I do this?
Basically #Html.ActionLink() or <a></a> tag uses get request to locate the page. Hence whenever you clicked it, you request to your AddToCart action method in ProductController and if that action method returns null or void so a blank or empty page is shown as you experienced (because or #Html.ActionLink() get request by Default).
So if you want to add your value to cart then call AddToCart method using ajax i.e:
HTML:
#Html.ActionLink("Add To Cart", "AddToCart", null, new { id="myLink"})
Jquery or Javascript:
$("#myLink").click(function(e){
e.preventDefault();
$.ajax({
url:$(this).attr("href"), // comma here instead of semicolon
success: function(){
alert("Value Added"); // or any other indication if you want to show
}
});
});
'AddToCart' method in the ProductController:
public void AddToCart()
{
//Logic to add item to the cart.
}
Now this time when the call goes to AddToCart method it goes by using ajax hence the whole page will not redirect or change, but its an asynchronous call which execute the AddToCart action method in your ProductController and the current page will remains same. Hence the product will also added to cart and page will not change to blank.
Hope this helps.
The answer of Syed Muhammad Zeeshan is what you are looking for, however you may return an EmptyResult.
public ActionResult AddToCart()
{
//Logic to add item to the cart.
return new EmptyResult();
}
According to this it has no impact on your code ASP.Net MVC Controller Actions that return void
But maybe sometime you want to return data and then you could do something like this:
if (a)
{
return JSon(data);
}
else
{
return new EmptyResult();
}
As many people mentioned here you will need to use AJAX if your using asp.net MVC to hit a controller POST function without having to leave your view.
A good use case for this is if you want to upload a file without refreshing the page and save that on the server.
All of the
return new EmptyResult();
Wont work, they will still redirect you.
Here is how you do it, in your view have the follow form as an example:
<form enctype="multipart/form-data" id="my-form">
<p>
The CSV you want to upload:
</p>
<input type="file" class="file-upload" name="FileUpload" />
</div>
<div>
<button type="submit" class="btn btn-default" name="Submit" value="Upload">Upload</button>
</div>
</form>
Then in the JavaScript side you need to add this to your view with within Script tags.
$("#my-form").on('submit', function (event) {
event.preventDefault();
// create form data
var formData = new FormData();
//grab the file that was provided by the user
var file = $('.file-upload')[0].files[0];
// Loop through each of the selected files.
formData.append('file', file);
if (file) {
// Perform the ajax post
$.ajax({
url: '/YourController/UploadCsv',
data: formData,
processData: false,
contentType: false,
type: 'POST',
success: function (data) {
alert(data);
}
});
}
});
Your controller might look something like this to process this type of file:
[HttpPost]
public void UploadCsv()
{
var listOfObjects = new List<ObjectModel>();
var FileUpload = Request.Files[0]; //Uploaded file
//check we have a file
if (FileUpload.ContentLength > 0)
{
//Workout our file path
string fileName = Path.GetFileName(FileUpload.FileName);
string path = Path.Combine(Server.MapPath("~/App_Data/"), fileName);
//Try and upload
try
{
//save the file
FileUpload.SaveAs(path);
var sr = new StreamReader(FileUpload.InputStream);
string csvData = sr.ReadToEnd();
foreach (string r in csvData.Split('\n').Skip(1))
{
var row = r;
if (!string.IsNullOrEmpty(row))
{
//do something with your data
var dataArray = row.Split(',');
}
}
}
catch (Exception ex)
{
//Catch errors
//log an error
}
}
else
{
//log an error
}
}
There are many ways to accomplish what you want, but some of them require a lot more advanced knowledge of things like JavaScript than you seem aware of.
When you write ASP.NET MVC applications, you are required to have more intimate knowledge of how browsers interact with the web server. This happens over a protocol called HTTP. It's a simple protocol on the surface, but it has many subtle details that you need to understand to successfully write ASP.NET MVC apps. You also need to know more about Html, CSS, and JavaScript.
In your case, you are creating an anchor tag (<a href="..."/>), which when click upon, instructs the browser to navigate to the url in the href. That is why you get a different page.
If you don't want that, there are a number of ways change your application. The first would be, instead of using an ActionLink, you instead simply have a form and post values back to your current controller. And call your "add to cart" code from your post action method.
Another way would be have your AddToCart method look at the referrer header (again, part of that more subtle knowledge of http) and redirect back to that page after it has processed its work.
Yet another way would be to use Ajax, as suggested by Syed, in which data is sent to your controller asynchronously by the browser. This requires that you learn more about JavaScript.
Another option is to use an embedded iframe and have your "add to cart" be it's own page within that iframe. I wouldn't necessarily suggest that approach, but it's a possibility.
Controller should return ActionResult. In this case a redirect to the caller page.
using System.Web.Mvc;
using System.Web.Mvc.Html;
public ActionResult Index()
{
HtmlHelper helper = new HtmlHelper(new ViewContext(ControllerContext, new WebFormView(ControllerContext, "Index"), new ViewDataDictionary(), new TempDataDictionary(), new System.IO.StringWriter()), new ViewPage());
helper.RenderAction("Index2");
return View();
}
public void Index2(/*your arg*/)
{
//your code
}
I was struggling with this and couldn't get it working with ajax.
Eventually got a working solution by making my controller method return type ActionResult rather than void and returning a RedirectToAction() and inputting the action relating to the view I wanted to remain on when calling the controller method.
public ActionResult Method()
{
// logic
return RedirectToAction("ActionName");
}

How do I get files AND form values from an ASP.NET MVC 4 website?

I have an ASP.NET MVC Website that has a dropdown list that is being created using this in the view...
#Html.DropDownList("Programs")
Programs is populated from a Business Object collection and stuffed into the ViewBag in the index action on the Home Controller...
\\get items...
ViewBag.Programs = items;
The view also has potentially three files I am getting like this in the same view...
<input type="file" name="files" id="txtUploadPlayer" size="40" />
<input type="file" name="files" id="txtUploadCoaches" size="40" />
<input type="file" name="files" id="txtUploadVolunteers" size="40" />
All of the aforementioned controls are contained in a Form that is created in the view using...
#using (Html.BeginForm("Index", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<!-- file and other input types -->
<input type="submit" name="btnSubmit" value="Import Data" />
}
My problems is that I cannot find a way to process all of the files AND reference the form fields.
Specifically, I need to know what Program the user selected from the dropdown.
I can process the files using this code with no problem...
[HttpPost]
public ActionResult Index(IEnumerable<HttpPostedFileBase> files)
//public ActionResult Index(FormCollection form)
{
_tmpFilePath = Server.MapPath("~/App_Data/uploads");
if (files == null) return RedirectToAction("Index");
foreach (var file in files)
{
if (file != null && file.ContentLength > 0)
{
var fileName = Path.GetFileName(file.FileName);
var path = Path.Combine(_tmpFilePath, fileName);
if (System.IO.File.Exists(path)) System.IO.File.Delete(path);
_file = file;
file.SaveAs(path);
break; //just use the first file that was not null.
}
}
//SelectedProgramId = 0;
//DoImport();
return RedirectToAction("Index");
}
But I cannot figure how to ALSO get access to the POST form values especially the Programs dropdown selected value (and for the record there is also a checkbox that I cannot read the value from.) Fiddler shows me that the Response has the file references AND the selected program but I cannot figure out how to get them out of the POST using ASP.NET MVC.
I know this question is pretty basic but I am stilling learning the whole web/http thing not just MVC.
EDIT
Thanks for your answers. I had the thought that the answer might lie in passing in both the files and the form values into the POST.
So my last question is ... how do I change the HTML.BeginForm block to pass in both the files and form values? Right now I have ...
#using (Html.BeginForm("Index", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
//do stuff
}
What should that using statement be to get both form values and files as separate parameters of the ActionResult?
EDIT MY EDIT
It seems that I don't have to make any changes...the debugger is showing that both files and form are non-null. Cool! Is that right?
I think that this should do it
[HttpPost]
public ActionResult Index(IEnumerable<HttpPostedFileBase> files, FormCollection form)
{
//handle the files
//handle the returned form values in the form collection
}
You should be able to pass in 2 parameters in the [HttpPost] action. you can also pass in the HTML name.
Edit: I also had problems with forms in ASP.net. I suggest looking into this blog post by Scott Allen.
http://odetocode.com/blogs/scott/archive/2009/04/27/6-tips-for-asp-net-mvc-model-binding.aspx
Use a ViewModel type that contains both the posted files and form values, or use the HttpRequest (accessed via the Controller.Request property) object, which has .Form[key] for POST values and .Files[key] for posted files.

set src property in view to a url outside of the MVC3 project

I am trying to create an application that will display images that are stored locally on the webserver. Here is what I have in my view, note that "entry" are absolute addresses like "C:\Images\Image1.jpg". However, when I run it, I get "Not allowed to load local resource: file:///C:/Images/ImageName.jpg" in the console log. So maybe it tries to access the image on the client. How do I tell my view to access the local webserver path and not look for the image source on the client? Please note that moving the images into project directory is not an option, because the images are stored on a different drive on the webserver.
<!-- language: c# -->
#model List<String>
<div style="height: 500px; overflow:scroll;">
<h2>
ScreenShots for testMachine</h2>
#foreach (var entry in Model)
{
<div class="nailthumb-container square-thumb">
<img alt="screenshot" src="#Url.Content(entry)" />
</div>
}
</div>
You cannot directly serve images outside of your ASP.NET MVC 3 application to the client. That would be a huge security vulnerability if the client could access arbitrary files on your server.
You will need to write a controller action that will return them and then point your src property of your <img> tags to this controller action.
public class ImagesController: Controller
{
public ActionResult SomeImage()
{
return File(#"C:\Images\foo.jpg", "image/jpeg");
}
}
and inside your view:
<img src="#Url.Action("SomeImage", "Images")" alt="" />
You could also pass the image name as parameter to the controller action:
public class ImagesController: Controller
{
public ActionResult SomeImage(string imageName)
{
var root = #"C:\Images\";
var path = Path.Combine(root, imageName);
path = Path.GetFullPath(path);
if (!path.StartsWith(root))
{
// Ensure that we are serving file only inside the root folder
// and block requests outside like "../web.config"
throw new HttpException(403, "Forbidden");
}
return File(path, "image/jpeg");
}
}
and in your view:
<img src="#Url.Action("SomeImage", "Images", new { image = "foo.jpg" })" alt="" />
The above code was useful for me, with a change like this
System.Web.UI.Page page = new System.Web.UI.Page();
string filePath = page.Server.MapPath("~/Log/" + fileName);
if (!filePath.StartsWith(filePath))
{
throw new HttpException(403, "Forbidden");
}
return File(filePath, "Content-Disposition", "attachment;filename=TableImportLog.csv");
}
the file thrown to the user is with file name like this "attachment;filename=TableImportLog.csv", but i want the file name as "TableErrorLog.csv"
need help for the same!

A public action method was not found on controller

I am working on a project on asp.net MVC3, I have a controller named UserProfile when i run my project and login, it shows error A public action method images was not found on controller UserProfile
I don't have any action method named images in any of my controllers,below is my UserProfile's index method
[CustomAuthorizeAttribute]
public ActionResult Index()
{
var userName = string.Empty;
if (SessionHelper.GetSession("login") != null)
{ userName = SessionHelper.GetSession("login").ToString(); }
else
{ return View(); }
SessionHelper.SetSess("issetup", null);
UserProfileModel model = GetUserProfileData(userName);
StateandCityDropdown();
return View(model);
}
I have two forms on userprofile index view one with some textboxes and other fields for entering data and second for uploading images
It sounds to me like the routes are picking up on a url you have and mistaking them for an action. It could be that you have a link to an image directory underneath a directory that matches your controller such as /User/Images would thow this error because the routing would then expect you to have an Images action when you dont. Check the page source for anything linking to an images folder but without an image included. The other option is that the routes are picking up the images as well as the actions you want them to. If this is the case in your Global.asax.cs file check the RegisterRoutes method has some ignores in for images.
routes.Ignore("{*allpng}", new { allpng = #".*\.png(/.*)?" });
routes.Ignore("{*allgif}", new { allgif = #".*\.gif(/.*)?" });
routes.Ignore("{*alljpg}", new { alljpg = #".*\.jpg(/.*)?" });
Hope this helps
Andy

Categories

Resources