View Code:
#if (File.Exists(Server.MapPath("~/Images/Cakes/" + Html.DisplayFor(modelItem => Model.CakeImage))))
{
#model TastyCakes.Models.Cakes
<form name="deletePhoto" action="/Cakes/DeletePhoto" method="post">
#Html.AntiForgeryToken()
File name of image to delete (without .jpg extension):
<input name="photoFileName" type="text" value="#Html.DisplayFor(modelItem => Model.CakeImage)" />
<input type="submit" value="Delete" class="tiny button">
</form>
} else {
<p>*File Needs to be uploaded</p>
}
Controller Code:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult DeletePhoto(string photoFileName)
{
ViewBag.deleteSuccess = "false";
var photoName = "";
photoName = photoFileName;
var fullPath = Server.MapPath("~/Images/Cakes/" + photoName);
if (File.Exists(fullPath))
{
File.Delete(fullPath);
ViewBag.deleteSuccess = "true";
}
}
Where it says if (File.Exists) AND File.Delete, the code has squiggly lines underneath it. So I am trying to figure out what syntax I need to get thif file deleted.
Here is a screenshot of my code in the controller:
UPPDATE: I have got the code working and created a simple code example on my blog on how I got it working and how the idea came about.
http://httpjunkie.com/2014/724/mvc-5-image-upload-delete/
use Request.MapPath
string fullPath = Request.MapPath("~/Images/Cakes/" + photoName);
if (System.IO.File.Exists(fullPath))
{
System.IO.File.Delete(fullPath);
}
File, as you're using it, is ambiguous, hence the "squiggly line". The IDE can't resolve which you mean;
System.Web.Mvc.Controller.File()
or
System.IO.File
Use a fully-qualified name when trying to use the File API within an MVC controller.
thanks for #Damith's Answer
I created this function
private bool RemoveFileFromServer(string path)
{
var fullPath = Request.MapPath(path);
if (!System.IO.File.Exists(fullPath)) return false;
try //Maybe error could happen like Access denied or Presses Already User used
{
System.IO.File.Delete(fullPath);
return true;
}
catch (Exception e)
{
//Debug.WriteLine(e.Message);
}
return false;
}
and here is a simple use of it
RemoveFileFromServer("Content\img\ProfilePictures\User12.png");
Add using System.IO; at the top of your controller.
you can also use HostingEnvironment.MapPath insted of Request.MapPath
This example works fine for me:
private bool DeleteFile(string image1_Address="")
{
try {
if (image1_Address != null && image1_Address.Length > 0)
{
string fullPath = HostingEnvironment.MapPath("~" + image1_Address);
if (System.IO.File.Exists(fullPath))
{
System.IO.File.Delete(fullPath);
return true;
}
}
}catch(Exception e)
{ }
return false;
}
Suppose you have a controller named PlacesController. make a IHostingEnvironment object in it and initialize it.
private readonly TouristPlaceInformationContext _context; //database context object. not necessary for this solving current problem. but it is used for database queries.
private readonly IHostingEnvironment _he;
public PlacesController(TouristPlaceInformationContext context, IHostingEnvironment he)
{
_context = context;
_he = he;
}
In the following function use _he.WebRootPath to get the path till "wwwroot" folder. use _he.ContentRootPath to get path till the root folder of the project. Suppose we want to delete a file in the following path: "projectRoot/wwwroot/images/somefile.jpg".Following function will get the job done.
public void deleteFile(string filename){
String filepath= Path.Combine(_he.WebRootPath,"images", filename);
if (System.IO.File.Exists(prevFilePath))
{
System.IO.File.Delete(prevFilePath);
}
}
Related
I'm trying to get the value of what the user has inputted in my textarea on my page and store that value as a string in a variable.
I have spent over an hour doing research on all different ways of getting the value from a textarea in C# and tried many combinations of example code and tried to adapt it to mine but neither of them work. Either the library doesn't exist anymore or something is wrong with example code and I don't want to fix something that is 8+ years old.
Is there any new ways in 2022 to get the value in my razor page textarea and store it in a string so I can re-use it for my needs?
I have seen the post on stack overflow that has been posted over 8+ years and it doesn't work or I'm implementing it wrong.
var mystr = document.getElementById(id).value;
you can put that in a javascript function then gets called on onlcick (like a submit button) and or on the textarea as OnTextChanged.
You have to provide the textarea's Id or use the Html helper method to get it. Here is a working example:
#using System.IO;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using System;
public class HomeController : Controller
{
IConfiguration Configuration { get; }
public HomeController(IConfiguration configuration) => Configuration = configuration;
public IActionResult Index() => View();
[HttpPost]
public IActionResult Index(string path, string text) // Here you can specify the path of the html file and a text for it in this case will be used to grab the value of an input text area named 'Text'. But it works as well if you want to grab an input from a button and save it as another one in a new html file or somewhere else if you want to do something like that
{
try { var files = Directory.GetFiles(path); } catch (DirectoryNotFoundException) { return View("Error"); } catch (UnauthorizedAccessException) { return View("Error"); } catch (ArgumentNullException) { return View("Error"); } catch (ArgumentException) { return View("Error"); } catch (PathTooLongException) { return View("Error"); } catch (NotSupportedException) { return View("Error"); } catch (SecurityException) { return View("Error"); }
// Here you can get the value of your textarea and save it for further use
var input = Request.Form["Text"]; // This is an example
foreach (var file in files) { // Here you can loop through all the files, edit them or delete them
if (file.Contains(".cshtml")) { // You can just look for a specific file extension here if you want or loop through all the files in your directory, edit or delete any file
var htmlDocument = new HtmlDocument();
htmlDocument.LoadHtml(File.ReadAllText(file));
var formElement = htmlDocument.GetElementbyId("example-form").OuterHtml;
formElement = Regex.Replace(formElement, #"<input name=""Text"" type=""text"" value=""\s*?.*?"" />", "<input name=\"Text\" type=\"text\" value=\"" + input + "\" />");
var streamWriter = new StreamWriter(file); // Replace the content of the file with a string created on the previous line
streamWriter.WriteLineAsync(formElement);
}
}
return View("Index"); // Return your view to be called after saving or editing a file or not depending on what you are trying to do.
}
}
}
I found the BlazorInputFile library, but there are still-open PRs from October of 2019, and I am not sure whether this library is still maintained. Also, I found a couple articles in blogs about how we can upload files with JS in Blazor. While I don't want to use JS if possible, I do need to upload files using Blazor... is it possible to do so without using JavaScript?
I was dabbling with installing SteveSandersonMS' repo and then realised that, as of February 2021, there is actually a native InputFile component in ASP.NET Core 5.0.
It supports uploading of single and multiple files in Blazor and is easy to use (and you don't need to add your own JS files etc.).
I used it for single file uploads - all you need to do is add the InputFile component in the Razor page:
<InputFile OnChange="#SingleUpload" />
and then in my case I needed the file in a byte array:
#code {
private async Task SingleUpload(InputFileChangeEventArgs e)
{
MemoryStream ms = new MemoryStream();
await e.File.OpenReadStream().CopyToAsync(ms);
var bytes = ms.ToArray();
//do something with bytes
}
}
InputFileChangeEventArgs gives you an IReadOnlyList of IBrowserFile which you can use to get the Name, LastModified, Size and ContentType, as well as an OpenReadStream method for getting a Stream.
There is good documentation and code on how to get multiple files in the ASP.NET docs.
Your will also need to add the System.IO namespace:
#using System.IO
As of June 2020, The best method (WA), assuming you are using a form is to use a Tewr's FileReader. Let start with the API, the post controller would be :
public async Task<IActionResult> PostMedia(
[FromForm] IFormFile Picture,
[FromForm] string Focus,
[FromForm] string ID,
[FromForm] string Title,
[FromForm] string FormType,
[FromForm] string AnimalType,
[FromForm] string Mode,
[FromForm] string AnimalID
)
{
Debug.WriteLine($"-------------------------------------{Focus}-----------------------------------------------");
Debug.WriteLine($"-------------------------------------{ID}-----------------------------------------------");
Debug.WriteLine($"-------------------------------------{Title}-----------------------------------------------");
Debug.WriteLine($"-------------------------------------{FormType}-----------------------------------------------");
Debug.WriteLine($"-------------------------------------{AnimalType}-----------------------------------------------");
Debug.WriteLine($"-------------------------------------{Mode}-----------------------------------------------");
Debug.WriteLine($"-------------------------------------{AnimalID}-----------------------------------------------");
//check if file was fully uploaded
if (Picture.Length == 0 || Picture == null)
return BadRequest("Upload a new File");
else
return Ok ("do something with this data....")
}
Then the post method on the client side would be:
public async Task PostFile()
{
//create content headers
var content = new MultipartFormDataContent();
content.Headers.ContentDisposition = new
System.Net.Http.Headers.ContentDispositionHeaderValue("form-data");
//create content
content.Add(new StreamContent(Pic.Stream, (int)Pic.Stream.Length), "Picture", Pic.FileName);
content.Add(new StringContent(Pic.Title), "Title");
content.Add(new StringContent(Pic.Focus), "Focus");
content.Add(new StringContent(Pic.ID), "ID");
content.Add(new StringContent(Pic.FormType), "FormType");
content.Add(new StringContent(Pic.AnimalType), "AnimalType");
content.Add(new StringContent(Pic.Mode), "Mode");
content.Add(new StringContent(Pic.AnimalID), "AnimalID");
//call to the server
var upload = await Http.PostAsync("Media",content);
//get server response
Pic.Message = await upload.Content.ReadAsStringAsync();
}
Tewr File reader helps you read the file into a stream which in my case is passed to the Pic object. The reading function which is binded to the onchange of your input element in the form would be :
public async Task ReadFile()
{
var file = (await fileReaderService.CreateReference(Xelement).EnumerateFilesAsync()).FirstOrDefault();
if (file == null) return;
var fileInfo = await file.ReadFileInfoAsync();
Pic.FileName = fileInfo.Name;
// Read into RAM
using (var memoryStream = await file.CreateMemoryStreamAsync((int)fileInfo.Size))
{
// Copy store image into pic object
Pic.Stream = new MemoryStream(memoryStream.ToArray());
}
}
Note that Xelement is ElementReference, and it used as ref on the input element in the form.
At the current state of affairs (as 2 April 2020), you will require JS, it is inevitable.
There are two main approaches you can take:
get the file data in the onchange event of the input, and call C# methods by passing the byte[] to them - that's basically the file selector approach you linked where you get the file data in the Blazor app to do whatever you want with it.
get the file data in the onchange event of the input, and use JS to call a remote endpoint that will receive the file and do something with it (like save it on your NAS or put it in your DB). This one is an actual file upload, as opposed to a file selector.
Both approaches are similar from coding perspective - you need JS. Perhaps in a future version of Blazor we will get an <InputFile> that will do the selection so you can to uploads with C# HTTP requests.
The File Selector approach is relatively easy to implement (literally a few lines), but it does not give you a file on the server, you have to work for it a little. The File Upload approach is harder to get right. I would personally use someone else's package for either. For file uploads things like Telerik UI for Blazor can be a commercial fit, and for the simpler selectors there is already another answer that links examples. By the way, Telerik's demos also have one such example as a component implemented for some of the demos.
I do this by using a component and some javascript (looks like a button). Once the component and js are incorporated, you never have to worry about it again...
Here's the Upload Component (Upload.Razor):
#inject IJSRuntime JSRuntime
#if (AllowMulitple)
{
<input id="Xinputfile00" type="file" accept="#Filter" #onchange="UploadFile" multiple hidden />
}
else
{
<input id="Xinputfile00" type="file" accept="#Filter" #onchange="UploadFile" hidden />
}
<button class="btn btn-default" #onclick="ClickUpload">#Title</button>
#code {
[Parameter]
public FileData[] Files { get; set; }
[Parameter]
public string Filter { get; set; }
[Parameter]
public string Title { get; set; }
[Parameter]
public bool AllowMulitple { get; set; }
[Parameter]
public Action Uploaded { get; set; }
async Task UploadFile()
{
string[] result = await JSRuntime.InvokeAsync<string[]>("blazorExtensions.GetFileData", "Xinputfile00");
List<FileData> results = new List<FileData>();
foreach (string file in result)
{
results.Add(new FileData(file));
}
this.Files = results.ToArray();
if (Uploaded != null)
{
Uploaded();
}
}
async Task ClickUpload()
{
await JSRuntime.InvokeVoidAsync("blazorExtensions.InvokeClick", "Xinputfile00");
}
public class FileData
{
public string Base64 { get; set; }
public string MIMEType { get; set; }
public byte[] Bytes
{
get
{
return Convert.FromBase64String(this.Base64);
}
}
public FileData(string data)
{
if (string.IsNullOrWhiteSpace(data) || !data.Contains(","))
{
return;
}
string[] alldata = data.Split(',');
this.MIMEType = alldata[0].Remove(0, 5).Replace(";base64", "");
this.Base64 = alldata[1];
}
}
Here's the javascript excerpt:
window.blazorExtensions = {
GetFileData: async function (id) {
var target = document.getElementById(id);
var filesArray = Array.prototype.slice.call(target.files);
return Promise.all(filesArray.map(window.blazorExtensions.fileToDataURL));
},
fileToDataURL: async function (file) {
var reader = new FileReader();
return new Promise(function (resolve, reject) {
reader.onerror = function () {
reader.abort();
reject(new DOMException('Error occurred reading file ' + file));
};
reader.onload = function (event) {
resolve(reader.result);
console.log('resolved');
};
reader.readAsDataURL(file);
console.log('returned');
})
},
InvokeClick: function (id) {
var elem = document.getElementById(id);
if (typeof elem.onclick == "function") {
elem.onclick.apply(elem);
}
elem.click();
},
}
And here's a calling markup sample:
<Upload #ref="upload" Filter=".xlsx" Title="Upload" AllowMulitple="false" Uploaded="DoMyExcelThingOrSomething" />
and the method it calls after upload:
Upload upload;
void DoMyExcelThingOrSomething()
{
if (upload.Files.Length < 1 || string.IsNullOrWhiteSpace(upload.Files[0].Base64))
{
//...nothing good here...
return;
}
//play with upload.Files here...
}
For Blazor Server, the following would upload the file to the server. There's no need to have a separate API server, or to use JS code. And it converts the stream into a file.
#using System.IO
#inject IWebHostEnvironment env
#*for ibrowser*#
#using Microsoft.AspNetCore.Components.Forms;
<h1>Blazor Server File Upload</h1>
<h3>#Message</h3>
<form #onsubmit="OnSubmit">
<InputFile OnChange="OnInputFileChange" multiple />
<br /><br />
<button type="submit">Upload Selected File(s)</button>
</form>
#code {
string Message = "No file(s) selected";
IReadOnlyList<IBrowserFile> selectedFiles;
void OnInputFileChange(InputFileChangeEventArgs e)
{
selectedFiles = e.GetMultipleFiles();
Message = $"{selectedFiles.Count} file(s) selected";
this.StateHasChanged();
}
async void OnSubmit()
{
foreach (var file in selectedFiles)
{
Stream stream = file.OpenReadStream();
var path = $"{env.WebRootPath}\\{file.Name}";
FileStream fs = File.Create(path);
await stream.CopyToAsync(fs);
stream.Close();
fs.Close();
}
Message = $"{selectedFiles.Count} file(s) uploaded on server";
this.StateHasChanged();
}
}
(Minor edit of http://www.bipinjoshi.net/articles/06473cc7-a391-409e-948d-3752ba3b4a6c.aspx)
I have an issue where potentials attackers can gain access to potentially sensitive information such as a web config file through one of my controller methods
public ActionResult GetPdfContent(string fileCode)
{
try
{
var relativePath = "~/files/content/" + fileCode;
if (!User.Identity.IsAuthenticated)
{
return RedirectToAction("Login", "Account");
}
if (System.IO.File.Exists(Server.MapPath(relativePath)))
{
return File("~/files/content/" + fileCode, "application/pdf", Server.UrlEncode(fileCode));
}
else
{
return View("ErrorNotExistsView");
}
}
}
As a recommendation the files codes should be white-listed for one.
Could it be as simple as adding a collection such as a list with all the white listed content and returning an error if the parameter is not contained within the list?
List<string> lstWhitelistedContent = new List<string>() { "code1", "code2", "code3"};
if (!lstWhitelistedContent.Contains(fileCode))
{
return View("ErrorNotExistsView");
}
Another approach would be to white-list a directory rather than list of files, and apparently your /files/content directory already seem like a good candidate.
Also, judging by the name of your method, it should only serve .pdf files, so you can add another restriction by the file extension.
Try this instead:
[Authorize]
public ActionResult GetPdfContent(string fileCode)
{
// this will remove path traversal attempts like '..'
var fileName = Path.GetFileName(fileCode);
// this will get you the file extension
var fileExtension = Path.GetExtension(fileName).ToLowerInvariant();
if (fileExtension != ".pdf")
return View("ErrorNotExistsView");
return File("~/files/content/" + fileName, "application/pdf", Server.UrlEncode(fileName));
}
}
Also, I took the liberty to remove the authorization check in the method and placed an [Authorize] attribute instead.
Background: I'm using the HTML 5 Offline App Cache and dynamically building the manifest file. Basically, the manifest file needs to list each of the static files that your page will request. Works great when the files are actually static, but I'm using Bundling and Minification in System.Web.Optimization, so my files are not static.
When in the DEBUG symbol is loaded (i.e. debugging in VS) then the actual physical files are called from the MVC View. However, when in Release mode, it calls a virtual file that could look something like this: /bundles/scripts/jquery?v=FVs3ACwOLIVInrAl5sdzR2jrCDmVOWFbZMY6g6Q0ulE1
So my question: How can I get that URL in the code to add it to the offline app manifest?
I've tried:
var paths = new List<string>()
{
"~/bundles/styles/common",
"~/bundles/styles/common1024",
"~/bundles/styles/common768",
"~/bundles/styles/common480",
"~/bundles/styles/frontend",
"~/bundles/scripts/jquery",
"~/bundles/scripts/common",
"~/bundles/scripts/frontend"
};
var bundleTable = BundleTable.Bundles;
foreach (var bundle in bundleTable.Where(b => paths.Contains(b.Path)))
{
var bundleContext = new BundleContext(this.HttpContext, bundleTable, bundle.Path);
IEnumerable<BundleFile> files = bundle.GenerateBundleResponse(bundleContext).Files;
foreach (var file in files)
{
var filePath = file.IncludedVirtualPath.TrimStart(new[] { '~' });
sb.AppendFormat(formatFullDomain, filePath);
}
}
As well as replacing GenerateBundleResponse() with EnumerateFiles(), but it just always returns the original file paths.
I'm open to alternative implementation suggestions as well. Thanks.
UPDATE: (7/7/14 13:45)
As well as the answer below I also added this Bundles Registry class to keep a list of the required static files so that it works in debug mode in all browsers. (See comments below)
public class Registry
{
public bool Debug = false;
public Registry()
{
SetDebug();
}
[Conditional("DEBUG")]
private void SetDebug()
{
Debug = true;
}
public IEnumerable<string> CommonScripts
{
get
{
if (Debug)
{
return new string[]{
"/scripts/common/jquery.validate.js",
"/scripts/common/jquery.validate.unobtrusive.js",
"/scripts/common/knockout-3.1.0.debug.js",
"/scripts/common/jquery.timepicker.js",
"/scripts/common/datepicker.js",
"/scripts/common/utils.js",
"/scripts/common/jquery.minicolors.js",
"/scripts/common/chosen.jquery.custom.js"
};
}
else
{
return new string[]{
"/scripts/common/commonbundle.js"
};
}
}
}
}
I'm by no means happy with this solution. Please make suggestions if you can improve on this.
I can suggest an alternative from this blog post create your own token.
In summary the author suggests using web essentials to create the bundled file and then creating a razor helper to generate the token, in this case based on the last changed date and time.
public static class StaticFile
{
public static string Version(string rootRelativePath)
{
if (HttpRuntime.Cache[rootRelativePath] == null)
{
var absolutePath = HostingEnvironment.MapPath(rootRelativePath);
var lastChangedDateTime = File.GetLastWriteTime(absolutePath);
if (rootRelativePath.StartsWith("~"))
{
rootRelativePath = rootRelativePath.Substring(1);
}
var versionedUrl = rootRelativePath + "?v=" + lastChangedDateTime.Ticks;
HttpRuntime.Cache.Insert(rootRelativePath, versionedUrl, new CacheDependency(absolutePath));
}
return HttpRuntime.Cache[rootRelativePath] as string;
}
}
Then you can reference the bundled file like so...
#section scripts {
<script src="#StaticFile.Version("~/Scripts/app/myAppBundle.min.js")"></script>}
Then you have control of the token and can do what you want with it.
How can I do this in another way ?
public ActionResult SomeAction(int id)
{
try
{
var model = GetMyModel(id);
return View(model);
}
catch(Exception e)
{
var notFoundViewModel = new NotFoundViewModel { Some Properties };
return View("~/Views/Shared/NotFound.cshtml", notFoundViewModel);
}
}
Exception will be thrown for url Controller/SomeAction/NotFoundId. I hate to have in project something like: ~/Views/Shared/NotFound.cshtml.
I realize this question is a few years old, but I figured I would add to the accepted answer. Following CodeCaster's recommendation of using the standard "Error.cshtml" as the file (view) to act as your generic error page, I recommend you let the MVC framework do the rest of the work for you.
If you place the Error.cshtml file in the Shared folder in your MVC project, you do not need to explicitly specify the path to the view. You can rewrite your code like the following:
public ActionResult SomeAction(int id)
{
try
{
var model = getMyModel(id);
return View(model);
}
catch(Exception e)
{
var NotFoundViewModel = new NotFoundViewModel { Some Properties };
return View("Error", NotFoundViewModel);
}
}
In fact, I've noticed that if you supply an explicit path and are running the Visual Studio IIS Express on your local machine, it sometimes isn't able to find the file and displays the generic 404 message :(
You can return HttpNotFoundResult object as:
catch(Exception e)
{
return new HttpNotFoundResult();
}
or
catch(Exception e)
{
return HttpNotFound("ooops, there is no page like this :/");
}
Make it a "~/Views/Shared/Error.cshtml" that displays a generic error model with a title and a message?