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

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值

您需要了解以下两点:

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

第二种情况调用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)