上传文件时如何处理服务器端的验证错误

How to handle validation error(s) Server Side when a file is uploaded

我有一个经过服务器端验证的表单,如果表单包含无效项目,模型将返回到视图,然后 JavaScript 突出显示无效字段。这是一个包含用于文件上传的输入类型文件的多部分表单。我正在使用 Visual Studio 2017、ASP.Net Core 2.1 和 ASP.Net MVC 设计模式。

问题:用户提交表单并附加了一个文件,但由于一个或多个输入字段,表单无效。当将模型传回带有验证错误的视图时,该文件不再附加,因为您无法从服务器填充输入类型文件(至少我不知道,认为这是一项安全功能)。

期望的行为:用户无需重新select输入中的文件进行第二次上传。

当前解决方案:我将上传的文件序列化并保存在服务器的会话中,然后在模型上设置一个属性,它告诉视图文件是保存在会话中并删除输入类型文件以进行上传,然后在重新提交时,如果表单有效,将文件从会话中反序列化,对文件执行我需要执行的操作,然后将其从会话中删除。

问题:有更好的处理方法吗?如果是这样,请提供示例文档或建议。

我不确定你正在做的验证,但它可以移动到前端吗?通常在前端进行验证更多是为了用户体验。 (例如,格式不正确、字段留空、字段中的数据类型不正确)。如果是这样我会把它移到前端,如果不是你可以把它留在后端。

您可以在离开焦点时使用 AJAX 验证表单中的字段。这可以保证当他们提交表单时,表单中的字段始终是正确的。我发现前端和服务器端验证的健康组合效果最好,而不是将所有内容都放在后端。

下面的粗略示例会在 onblur 方法触发时向控制器发送 ajax 请求以验证输入字段。如果通过验证,它将把边框设置为绿色或红色,如果有要显示的,则显示一条消息。只需确保您的模型与您将在请求中发送的内容相匹配:

JS:

<script>
    function validate() {
        var input = { input: $('#one').val() };

        $.ajax({
            url: 'ValidateTextBox/',
            type: 'POST',
            contentType: 'application/json',
            data: JSON.stringify(input),
            success: function (data) {
                if (JSON.parse(data.Result)) {
                    $('#one').css('border', '5px solid green');
                } else {
                    $('#one').css('border', '5px solid red');
                }
                $('#result').text(data.Message);
            }
        })
    }
</script>
<input id="one" type="text" onblur="validate()"/>
<label id="result"></label>

Controller.cs

[HttpPost]
public ActionResult ValidateTextBox(Dummy data)
{
    int result = 0;

    if (!int.TryParse(data.input, out result)) {
        return Json(new { Result = false, Message = "Please enter in numbers" });
    }
    return Json(new { Result = true, Message = "Success" });
}

Model.cs

public class Dummy
{
    public string input { get; set; }
}

如您所料,出于安全原因,没有主流浏览器允许您访问用户选择的文件的实际路径。您的服务器代码将不会收到真实路径,甚至 JavaScript 也不允许访问它。相反,浏览器会生成一个假路径,其中唯一的真实值是文件名。

因此,在提交表单后将文件保留在当前设计(即标准表单)中的唯一方法是按照您在问题中描述的方式进行操作。显然,这不是一个好方法,尤其是如果文件太大,那么每次到服务器的往返都会花费很长时间。

要以不同的方式解决它,您需要更改设计。您需要让浏览器保留真实的路径值。唯一的方法是不提交表格。因此,您需要在不提交表单的情况下提交表单值。我能想到的唯一方法是使用 Ajax 提交表单。这意味着 server-side 验证的结果必须作为 Ajax 响应返回(通常是 JSON,但如果你愿意,可以用其他方式序列化)然后 JavaScript 将接受该响应并根据需要在表单上显示错误。这样,文件每次到服务器还是re-submitted,但至少服务器不需要像问题中描述的那样将它发送回客户端。这节省了一半的带宽,但需要更多的编程。如果文件可以大,还是不推荐。在这种情况下,让用户重新选择文件可能会更好,而不是等待很长时间才能再次上传文件。

要将 Ajax 助手添加到 .Net Core,您只需从 project GitHub 下载 JavaScript 文件,然后将 link 下载到 [=43] =] 你的 HTML。但是,我更喜欢在我需要它的页面上只 link 它,因为大多数页面不需要它。我做的是,首先为它创建一个局部视图:

<environment names="Development">
    <script src="~/lib/jquery-ajax-unobtrusive/dist/jquery.unobtrusive-ajax.js"></script>
</environment>
<environment exclude="Development">
    <script src="~/lib/jquery-ajax-unobtrusive/dist/jquery.unobtrusive-ajax.min.js"></script>
</environment>

如果你喜欢,你可以使用CDN。我找不到 cdn.jsdelivr.net 以外的 CDN,也找不到好的后备测试,所以我将 asp-fallback-test 留空:

<environment names="Development">
    <script src="~/lib/jquery-ajax-unobtrusive/dist/jquery.unobtrusive-ajax.js"></script>
</environment>
<environment exclude="Development">
    <script src="https://cdn.jsdelivr.net/npm/jquery-ajax-unobtrusive@3.2.6/dist/jquery.unobtrusive-ajax.min.js"
        asp-fallback-src="~/lib/jquery-ajax-unobtrusive/dist/jquery.unobtrusive-ajax.min.js"
        asp-fallback-test=""
        crossorigin="anonymous"
        integrity="sha256-PAC000yuHt78nszJ2RO0OiDMu/uLzPLRlYTk8J3AO10="></script>
</environment>

无论哪种方式,都可以随心所欲地命名这个文件,比方说 _AjaxScriptsPartial.cshtml。现在在你需要它的视图中,像这样添加它:

@section Scripts {
    @{ await Html.RenderPartialAsync("_AjaxScriptsPartial"); }
}

这里有一个关于如何使用它的很好的解释:Using Unobtrusive Ajax In Razor Pages

或者,您可以保留当前的设计,但不是序列化文件并将其发送回浏览器,而是将其保存在服务器上并将 URL 发送到浏览器。现在,在 JavaScript 中,将文件输入字段替换为 "File uploaded successfully" 之类的简单消息或任何其他方式来向用户指示文件不需要 re-selected。当表单为 re-submitted 并且所有服务器验证成功时,获取服务器上的物理文件。您的代码必须知道是上传文件还是从服务器获取文件(通过检查文件输入字段是否有文件)。它还需要知道在服务器上的何处获取它。这种方式适用于所有文件大小,因为文件只上传一次,但需要一些工作。