用户单击“编辑”视图中的“保存”按钮后,如何将文件上传到文件系统并将路径保存到数据库?

发布于 2025-01-10 10:32:31 字数 10331 浏览 0 评论 0原文

我想在编辑视图中按下“保存”按钮后将文件上传到文件系统。为此,我尝试在编辑 (POST) 操作中调用 UploadToFileSystem 方法。

我在这里学习了如何做到这一点,但本教程显示了您可以在索引操作网格视图中执行此操作。我一直在尝试对该逻辑进行逆向工程,以便在编辑操作中做到这一点。

这就是我在仅使用该方法的按钮中调用 UploadToFileSystem 之前的样子,它可以将路径保存到文件系统而不是数据库。

我会给你一个该应用程序的 Github 链接,但它有很多依赖项,你需要安装和访问。

之前:

在 VS Code 中调试,显示 UploadToFileSystem 正在被自身调用。

之后:(没有的地方)不起作用。)

在 VS Code 中调试,显示 UploadToFileSystem 被调用因此,

在第二张图像中显示没有检索到文件的错误之后,我尝试使用名为 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;}
    }
}

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

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

暖伴 2025-01-17 10:32:31

对于这两种情况,都得到空值都是由于模型绑定失败引起的。

您需要了解以下两件事:

  1. ASP.NET Core Tag Helper asp-for 将生成 idname 属性。

  2. 模型绑定系统在源中查找名称模式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");
}

对于第二种情况调用Edit操作中的UploadToFileSystem方法:

[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 将生成与 ProblemFileAttachments 匹配的 name="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)

For the two situations, both of them get null value are caused by model binding failure.

You need know two things below:

  1. ASP.NET Core Tag Helper asp-for will generate the id and name attribute.

  2. Model Binding system looks through the sources for the name pattern prefix.property_name. If nothing is found, it looks for just property_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)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文