How to get user input from a textarea using C# - c#

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.
}
}
}

Related

How do I upload files with Blazor?

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)

White listing vulnerable parameters within Controller

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.

Adding Image MediaType programatically Umbraco 7.1

Im a bit new to Umbraco, and i have to say i like it a lot.
But now i'm stuck on something simple i think. I Created a protected page that is only visible to members on my website. Where the member is able to upload multiple files at once. This is al working like a charm. First i created the upload form for multiple images then i created the SurfaceController to handle the submit. Also working like a charm.
My ActionResult on my SurfaceController receives an IEnumerable<HttpPostedFileBase> called files which is good. I see all my images that i'm posting with my form. But here comes the problem.
While looping over my files I try to create a Media (Image type) using the MediaService.CreateMedia giving my filename and parentid and the mediaType (Image).
But when i try to set the umbracoFile value on my just created media item i will get the following exception:
An unhandled exception of type 'Microsoft.CSharp.RuntimeBinder.RuntimeBinderException' occurred in Umbraco.Core.dll
Additional information: The best overloaded method match for
'Umbraco.Core.Models.ContentBase.SetPropertyValue(string, string)'
has some invalid arguments
I hope someone can tell my what i'm doing wrong. Below is my code i'm using
[HttpPost]
public ActionResult UploadFiles(IEnumerable<HttpPostedFileBase> files)
{
bool success = false;
//Get logged in member and look for the mediafolderID
var member = Services.MemberService.GetByUsername(HttpContext.User.Identity.Name);
var mediaFolderID = member.GetValue<int>("mediaFolderID");
//Get mediafolder
var mediaFolder = Services.MediaService.GetById(mediaFolderID);
try
{
// Create a media item from each file uploaded
foreach (var file in files)
{
var fileName = file.FileName; // Assumes no path information, just the file name
var ext = fileName.Substring(fileName.LastIndexOf('.') + 1).ToLower();
if (!UmbracoConfig.For.UmbracoSettings().Content.DisallowedUploadFiles.Contains(ext))
{
var mediaType = global::Umbraco.Core.Constants.Conventions.MediaTypes.File;
if (UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.Contains(ext))
{
mediaType = global::Umbraco.Core.Constants.Conventions.MediaTypes.Image;
}
var f = Services.MediaService.CreateMedia(fileName, mediaFolderID, mediaType);
// Assumes the file.InputStream is a Stream - you may have to do some extra work here...
f.SetValue(global::Umbraco.Core.Constants.Conventions.Media.File,(Stream)file.InputStream); // Real magic happens here.
Services.MediaService.Save(f);
}
}
success = true;
}
catch (Exception ex)
{
// On error show message
ViewData["exceptionMessage"] = ex.Message;
success = false;
}
// On success redirect to current page and show successmessage
ViewData["success"] = success;
if (success)
{
return RedirectToCurrentUmbracoPage();
}
return CurrentUmbracoPage();
}
Instead of f.SetValue(global::Umbraco.Core.Constants.Conventions.Media.File, (Stream)file.InputStream); you should just use the HttpPostedFileBase: f.SetValue(global::Umbraco.Core.Constants.Conventions.Media.File, file);
Some other notes:
Check that the file has a length and is not null: file != null && file.ContentLength > 0
You're not using your mediaFolder variable anywhere, can be removed.
Not sure why you'd need global::Umbraco.Core, consider adding using Umbraco.Core; and use Constants.Conventions.MediaTypes.Image etc.
Check that you really need to rely on DisallowedUploadFiles - I'm pretty sure that's checked during CreateMedia

ASP NET MVC 5 Delete File From Server

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);
}
}

Render error view when we encounter an exception

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?

Categories

Resources