I want to upload my files to the filesystem after I have pressed the "save" button in the Edit view. To do that I am trying to call the UploadToFileSystem method inside the Edit (POST) action.
I learned how to do that here but this tutorial shows you how to do it in the Index action grid view. I have been trying to reverse engineer that logic so as to do it in the edit action.
This is what it looked like before I call UploadToFileSystem in a button that uses only that method and it worked with saving the path to the file system but not the database.
I would give you a Github link to the app but it has lots of dependencies that you would need to install and gain access to.
BEFORE:
Debugging in VS Code showing UploadToFileSystem being called by itself.
AFTER: (Where it didn't work.)
Debugging in VS Code showing UploadToFileSystem being called by itself.
So after that error in the second image showing there were no files being retrieved I have tried to use a view model called ProblemInputModel
but I get a null reference exception once the file gets passed to the view.
ProblemsController.cs
// POST: Problems/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,ProblemTitle,ProblemDescription,ProblemStartDate,ProblemFileAttachments,ProblemSeverity,ProblemComplete")] Problem problem, List<IFormFile> iFormFile)
{
//Used for file attachment upload.
if (id != problem.ID)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(problem);
await _context.SaveChangesAsync();
//Upload or update any attachments user inserted.
await UploadToFileSystem(iFormFile ,problem.ID, problem.ProblemTitle, problem.ProblemDescription,
problem.ProblemStartDate, problem.ProblemSeverity);
}
catch (DbUpdateConcurrencyException)
{
if (!ProblemExists(problem.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(problem);
}
[HttpPost]
public async Task<IActionResult> UploadToFileSystem(List<IFormFile> files, int? id, string title,
string description, DateTime dateTime, int severity)
{
foreach (var file in files)
{
//Get the base Path, i.e, The Current Directory of the application /Files/.
var basePath = Path.Combine(Directory.GetCurrentDirectory() "\\Files\\");
//Checks if the base path directory exists, else creates it.
bool basePathExists = System.IO.Directory.Exists(basePath);
if (!basePathExists) Directory.CreateDirectory(basePath);
//Gets the file name without the extension.
var fileName = Path.GetFileNameWithoutExtension(file.FileName);
//Combines the base path with the file name.
var filePath = Path.Combine(basePath, file.FileName);
//If the file doesnt exist in the generated path, we use a filestream object, and create a new file, and then copy the contents to it.
var extension = Path.GetExtension(file.FileName);
if (!System.IO.File.Exists(filePath))
{
using (var stream = new FileStream(filePath, FileMode.Create))
{
await file.CopyToAsync(stream);
}
//Create a new Problem object with required values.
var problem = await _context.Problems.FindAsync(id);
problem = new Problem
{
ProblemFileAttachments = filePath,
ProblemTitle = title,
ProblemDescription = description,
ProblemStartDate = dateTime,
ProblemSeverity = severity
};
//Inserts this model to the db via the context instance of EF Core.
_context.Problems.Add(problem);
_context.SaveChanges();
}
}
//Loads all the File data to an object and sets a message in the TempData.
TempData["Message"] = "File successfully uploaded to File System.";
return RedirectToAction("Index");
}
Edit.cshtml
@model Pitcher.Models.Problem
@{
ViewData["Title"] = "Edit";
}
<h1>Edit</h1>
<h4>Problem</h4>
<hr />
<div >
<div >
<form asp-action="Edit" enctype="multipart/form-data" method="post">
<div asp-validation-summary="ModelOnly" ></div>
<div >
<label asp-for="ID" ></label>
<input asp-for="ID" readonly="true" disabled="true" />
</div>
<div >
<label asp-for="ProblemTitle" ></label>
<input asp-for="ProblemTitle" />
<span asp-validation-for="ProblemTitle" ></span>
</div>
<div >
<label asp-for="ProblemDescription" ></label>
<textarea asp-for="ProblemDescription" rows="10" cols="50"></textarea>
<span asp-validation-for="ProblemDescription" ></span>
</div>
<div >
<label asp-for="ProblemStartDate" ></label>
<input asp-for="ProblemStartDate" />
<span asp-validation-for="ProblemStartDate" ></span>
</div>
<div >
<label asp-for="ProblemFileAttachments" ></label>
<input asp-for="ProblemFileAttachments" type="file" name="files"/>
@* <button type="submit" asp-controller="Problems" asp-action="UploadToFileSystem">Upload to File System</button> *@
</div>
<div >
<label asp-for="ProblemSeverity" ></label>
<select asp-for="ProblemSeverity" >
<option value="">Choose severity level</option>
<option value="1">Very Low</option>
<option value="2">Low</option>
<option value="3">Medium</option>
<option value="4">High</option>
<option value="5">Very High</option>
</select>
<span asp-validation-for="ProblemSeverity" ></span>
</div>
<div >
<label >
<input asp-for="ProblemComplete" /> @Html.DisplayNameFor(model => model.ProblemComplete)
</label>
</div>
<div method="post">
<input type="submit" value="Save" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Problem.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
namespace Pitcher.Models
{
public class Problem
{
public int ID {get;set;}
[Required]
[StringLength(180, MinimumLength = 2, ErrorMessage = "Problem Title must be bettween 2 to 20 characters.")]
[DataType(DataType.Text)]
[Display(Name = "Problem Title")]
[Column("ProblemTitle")]
public string ProblemTitle {get;set;}
[Required]
[StringLength(int.MaxValue, MinimumLength = 5, ErrorMessage = "Problem Title must be at least 5 characters.")]
[DataType(DataType.Text)]
[Display(Name = "Problem Description")]
[Column("ProblemDescription")]
public string ProblemDescription {get;set;}
[Required]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = " Problem Start Date")]
[Column("ProblemStartDate")]
public DateTime ProblemStartDate {get;set;}
[DataType(DataType.Upload)]
[Display(Name = " Upload file")]
[Column("ProblemFileAttachments")]
public string ProblemFileAttachments {get;set;}
[Required]
[Display(Name = "Problem Severity")]
[Range(1,5, ErrorMessage
= "Problem Severity value for {0} must be between {1} and {2}.")]
[Column("ProblemSeverity")]
public int ProblemSeverity {get;set;}
[Display(Name = "Problem Complete")]
[Column("ProblemComplete")]
public bool ProblemComplete {get;set;}
public ICollection<Result> Result {get;set;}
public ICollection<Chat> Chat {get;set;}
//public List<Problem> ProblemFileModel {get; set;}
}
}
ProblemInputModel
//This is a view model only.
public class ProblemInputModel
{
[DataType(DataType.Upload)]
[Display(Name = " Upload file")]
[Column("ProblemFileAttachments")]
public List<IFormFile> ProblemFileAttachments {get;set;}
}
CodePudding user response:
For the two situations, both of them get null value are caused by model binding failure.
You need know two things below:
ASP.NET Core Tag Helper
asp-for
will generate theid
andname
attribute.Model Binding system looks through the sources for the name pattern
prefix.property_name
. If nothing is found, it looks for justproperty_name
without the prefix.
Model binding failure here is because the name attribute does not match the parameter name.
For the first situation to directly call UploadToFileSystem
method, you get the null parameters:
[HttpPost]
public IActionResult UploadToFileSystem(List<IFormFile> files, int? ID, string ProblemTitle,
string ProblemDescription, DateTime ProblemStartDate, int ProblemSeverity)
{
//.......
return RedirectToAction("Index");
}
For the second situation to call the UploadToFileSystem
method inside the Edit
action:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(int id, [Bind("ID,ProblemTitle,ProblemDescription,ProblemStartDate,ProblemFileAttachments,ProblemSeverity,ProblemComplete")] Problem problem,
List<IFormFile> files) //change here...
{
//.......
return View(problem);
}
If you want to use ProblemInputModel
model, you need firstly change the view code to remove the name="files"
, then asp-for
will generate the name="ProblemFileAttachments"
which matches the ProblemFileAttachments
property in ProblemInputModel
model:
<input asp-for="ProblemFileAttachments" type="file" @*name="files"*@/>
Then remember to add the ProblemInputModel
model as a parameter:
public IActionResult Edit(int id, [Bind("ID,ProblemTitle,ProblemDescription,ProblemStartDate,ProblemFileAttachments,ProblemSeverity,ProblemComplete")] Problem problem,
ProblemInputModel model)