用户在编辑视图中单击保存按钮后,如何将文件上传到文件系统并将路径保存到数据库?
How do I upload file to file system and save path to database AFTER user clicks save button in Edit view?
我想在编辑视图中按下“保存”按钮后将我的文件上传到文件系统。为此,我尝试在编辑 (POST) 操作中调用 UploadToFileSystem 方法。
我学会了如何做到这一点 here but this tutorial 向您展示了如何在索引操作网格视图中做到这一点。我一直在尝试对该逻辑进行逆向工程,以便在编辑操作中做到这一点。
这就是我在仅使用该方法的按钮中调用 UploadToFileSystem 之前的样子,它可以将路径保存到文件系统而不是数据库。
我会给你一个 Github link 的应用程序,但它有很多依赖项,你需要安装并获得访问权限。
之前:
Debugging in VS Code showing UploadToFileSystem being called by itself.
之后:(它不起作用的地方。)
Debugging in VS Code showing UploadToFileSystem being called by itself.
因此,在第二张图片中显示没有检索到文件的错误之后,我尝试使用名为 ProblemInputModel
的视图模型,但是一旦文件传递到视图,我就会收到空引用异常。
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 class="row">
<div class="col-md-4">
<form asp-action="Edit" enctype="multipart/form-data" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="ID" class="control-label" ></label>
<input asp-for="ID" readonly="true" disabled="true" class="form-control"/>
</div>
<div class="form-group">
<label asp-for="ProblemTitle" class="control-label"></label>
<input asp-for="ProblemTitle" class="form-control" />
<span asp-validation-for="ProblemTitle" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ProblemDescription" class="control-label"></label>
<textarea asp-for="ProblemDescription" class="form-control" rows="10" cols="50"></textarea>
<span asp-validation-for="ProblemDescription" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ProblemStartDate" class="control-label"></label>
<input asp-for="ProblemStartDate" class="form-control" />
<span asp-validation-for="ProblemStartDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ProblemFileAttachments" class="control-label"></label>
<input asp-for="ProblemFileAttachments" type="file" name="files"/>
@* <button type="submit" class="btn btn-primary" asp-controller="Problems" asp-action="UploadToFileSystem">Upload to File System</button> *@
</div>
<div class="form-group">
<label asp-for="ProblemSeverity" class="control-label"></label>
<select asp-for="ProblemSeverity" class="form-control">
<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" class="text-danger"></span>
</div>
<div class="form-group form-check">
<label class="form-check-label">
<input class="form-check-input" asp-for="ProblemComplete" /> @Html.DisplayNameFor(model => model.ProblemComplete)
</label>
</div>
<div class="form-group" method="post">
<input type="submit" value="Save" class="btn btn-primary" />
</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;}
}
}
问题输入模型
//This is a view model only.
public class ProblemInputModel
{
[DataType(DataType.Upload)]
[Display(Name = " Upload file")]
[Column("ProblemFileAttachments")]
public List<IFormFile> ProblemFileAttachments {get;set;}
}
这两种情况,都是模型绑定失败导致的,都得到null值
您需要了解以下两点:
ASP.NET Core Tag Helper asp-for
将生成 id
和 name
属性。
模型绑定系统在来源中查找名称模式 prefix.property_name
。如果什么都找不到,它只查找没有前缀的 property_name
。
此处模型绑定失败是因为name属性与参数名不匹配
第一种情况到直接调用UploadToFileSystem
方法,得到空参数:
[HttpPost]
public IActionResult UploadToFileSystem(List<IFormFile> files, int? ID, string ProblemTitle,
string ProblemDescription, DateTime ProblemStartDate, int ProblemSeverity)
{
//.......
return RedirectToAction("Index");
}
第二种情况调用UploadToFileSystem
方法insideEdit
动作:
[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);
}
如果要使用ProblemInputModel
模型,需要先修改视图代码去掉name="files"
,然后asp-for
会生成name="ProblemFileAttachments"
匹配ProblemFileAttachments
属性 在 ProblemInputModel
型号中:
<input asp-for="ProblemFileAttachments" type="file" @*name="files"*@/>
然后记得添加ProblemInputModel
模型作为参数:
public IActionResult Edit(int id, [Bind("ID,ProblemTitle,ProblemDescription,ProblemStartDate,ProblemFileAttachments,ProblemSeverity,ProblemComplete")] Problem problem,
ProblemInputModel model)
我想在编辑视图中按下“保存”按钮后将我的文件上传到文件系统。为此,我尝试在编辑 (POST) 操作中调用 UploadToFileSystem 方法。
我学会了如何做到这一点 here but this tutorial 向您展示了如何在索引操作网格视图中做到这一点。我一直在尝试对该逻辑进行逆向工程,以便在编辑操作中做到这一点。
这就是我在仅使用该方法的按钮中调用 UploadToFileSystem 之前的样子,它可以将路径保存到文件系统而不是数据库。
我会给你一个 Github link 的应用程序,但它有很多依赖项,你需要安装并获得访问权限。
之前:
Debugging in VS Code showing UploadToFileSystem being called by itself.
之后:(它不起作用的地方。)
Debugging in VS Code showing UploadToFileSystem being called by itself.
因此,在第二张图片中显示没有检索到文件的错误之后,我尝试使用名为 ProblemInputModel
的视图模型,但是一旦文件传递到视图,我就会收到空引用异常。
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 class="row">
<div class="col-md-4">
<form asp-action="Edit" enctype="multipart/form-data" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="ID" class="control-label" ></label>
<input asp-for="ID" readonly="true" disabled="true" class="form-control"/>
</div>
<div class="form-group">
<label asp-for="ProblemTitle" class="control-label"></label>
<input asp-for="ProblemTitle" class="form-control" />
<span asp-validation-for="ProblemTitle" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ProblemDescription" class="control-label"></label>
<textarea asp-for="ProblemDescription" class="form-control" rows="10" cols="50"></textarea>
<span asp-validation-for="ProblemDescription" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ProblemStartDate" class="control-label"></label>
<input asp-for="ProblemStartDate" class="form-control" />
<span asp-validation-for="ProblemStartDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ProblemFileAttachments" class="control-label"></label>
<input asp-for="ProblemFileAttachments" type="file" name="files"/>
@* <button type="submit" class="btn btn-primary" asp-controller="Problems" asp-action="UploadToFileSystem">Upload to File System</button> *@
</div>
<div class="form-group">
<label asp-for="ProblemSeverity" class="control-label"></label>
<select asp-for="ProblemSeverity" class="form-control">
<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" class="text-danger"></span>
</div>
<div class="form-group form-check">
<label class="form-check-label">
<input class="form-check-input" asp-for="ProblemComplete" /> @Html.DisplayNameFor(model => model.ProblemComplete)
</label>
</div>
<div class="form-group" method="post">
<input type="submit" value="Save" class="btn btn-primary" />
</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;}
}
}
问题输入模型
//This is a view model only.
public class ProblemInputModel
{
[DataType(DataType.Upload)]
[Display(Name = " Upload file")]
[Column("ProblemFileAttachments")]
public List<IFormFile> ProblemFileAttachments {get;set;}
}
这两种情况,都是模型绑定失败导致的,都得到null值
您需要了解以下两点:
ASP.NET Core Tag Helper
asp-for
将生成id
和name
属性。模型绑定系统在来源中查找名称模式
prefix.property_name
。如果什么都找不到,它只查找没有前缀的property_name
。
此处模型绑定失败是因为name属性与参数名不匹配
第一种情况到直接调用UploadToFileSystem
方法,得到空参数:
[HttpPost]
public IActionResult UploadToFileSystem(List<IFormFile> files, int? ID, string ProblemTitle,
string ProblemDescription, DateTime ProblemStartDate, int ProblemSeverity)
{
//.......
return RedirectToAction("Index");
}
第二种情况调用UploadToFileSystem
方法insideEdit
动作:
[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);
}
如果要使用ProblemInputModel
模型,需要先修改视图代码去掉name="files"
,然后asp-for
会生成name="ProblemFileAttachments"
匹配ProblemFileAttachments
属性 在 ProblemInputModel
型号中:
<input asp-for="ProblemFileAttachments" type="file" @*name="files"*@/>
然后记得添加ProblemInputModel
模型作为参数:
public IActionResult Edit(int id, [Bind("ID,ProblemTitle,ProblemDescription,ProblemStartDate,ProblemFileAttachments,ProblemSeverity,ProblemComplete")] Problem problem,
ProblemInputModel model)