使用 JQuery 在 Asp.net mvc 中进行异步文件上传






4.95/5 (12投票s)
在 asp.net mvc 中上传文件而不重新加载整个页面
引言
在这篇文章中,我们将学习如何在 asp.net mvc 中上传文件,而无需重新加载整个页面,而是更新页面的特定部分,这在 webforms 中通常被称为异步回发。
背景
曾经遇到一个场景,需要在我的 asp.net mvc Web 应用程序中上传文件,但如果想要完全回发,那很简单,但我正在考虑使用 Ajax 进行局部回发来完成它。使用 JQuery 发布标准表单非常简单,但在发布多部分表单以上传文件时,并不那么直观。这是由于浏览器安全限制和沙箱。今天我将向您展示如何在不重新加载页面的情况下模拟异步上传并从服务器获取结果。
我首先想到的方法是使用 mvc 辅助方法 Ajax.BeginForm,我发现它不支持通过局部回发上传文件,它会进行完全回发。
我当时想到的第二个方法是使用 JQuery 和 AJAX 将文件发布到服务器,这样我就可以避免回发。我尝试了自己的方式,通过发布文件并通过 AJAX 返回部分视图,但出现的问题相同,部分视图独立渲染,而不是在同一个视图中。然后我在网上搜索,找到了一种方法。
此方法使用 IFrame 并执行模拟的异步回发,看起来就像通过 ajax 上传文件,因为我们的视图保持不变。
工作原理
为了使 AJAX 风格的文件上传工作,我们需要发布到一个 iFrame。我们将把一个 iFrame 定位在页面之外来隐藏它,然后发布到它。我们将向页面返回一个部分视图,并将其附加到某个 html 容器中。需要注意的是,由于安全限制,被发布的页面必须与您发布内容的页面在同一个域。此外,此解决方案将使用一个隐藏的 iFrame,但对于多个同时上传,您可以动态生成 iFrame 并发布到它们。
主视图
这是我的视图代码,我使用 mvc 辅助方法创建了一个表单,并在表单之后添加了一个 iframe,我们的表单将发布到其中。@{ ViewBag.Title = "Ajax File Uploading"; Layout = "~/Views/Shared/_Layout.cshtml"; } <h2>Ajax File Uploading</h2> <div> <div> <h1>Upload File</h1> </div> @using (Html.BeginForm("Upload", "Home", FormMethod.Post, new { enctype = "multipart/form-data", id = "ImgForm", name = "ImgForm", target = "UploadTarget" })) { @* DataBase Record ID as Hidden Field against which we are uplaoding file *@ @Html.Hidden("ID", 65512) <div style="width: 100px; margin: auto; float: left; line-height: 30px; font-weight: bold;">File</div> <div style="width: 200px; float: left; margin: auto;"> <input type="file" id="uploadFile" name="file" data-val="true" data-val-required="please select a file" /> <span id="validityMessages" style="color: Red;"></span> </div> <div style="clear: both;"></div> <div style="width: auto; margin-top: 3%;"> <div style="margin: 5% 18%;"> <input id="upload" type="button" value="Upload" name="Upload" onclick="UploadImage()" /> <span id="uploadLoader" style="display: none;"> <img id="searchLoader" src="@Url.Content("~/Content/Images/loader.gif")" />Uploading Please Wait</span> </div> </div> } <div id="uploadsContainer"></div> <iframe id="UploadTarget" name="UploadTarget" onload="UploadImage_Complete();" style="position: absolute; left: -999em; top: -999em;"></iframe> </div> @section Scripts{ <script type="text/javascript"> $(document).ready(function () { $('img.delUpload').live('click', function () { var Id = this.id; var url = '@Url.Action("DeleteFile","Home")'; url = url + "?id=" + Id $.get(url, function (response) { $('#uploadsContainer').html(response); }); }); }); </script> <script type="text/javascript"> var isFirstLoad = true; function UploadImage() { // check for size of file not greater than 1MB if ($("#uploadFile").val()) { var iSize = ($("#uploadFile")[0].files[0].size / 1024); iSize = (Math.round((iSize / 1024) * 100) / 100); if (iSize > 4) { alert("File must be less than 4MB"); $('span#validityMessages').html("File must be less than 4MB"); return; } else { // on form post showing Busy Indicator $('#uploadLoader').show(); $("#ImgForm").submit(); // post form console.log(iSize + "Mb"); } } // check for no file selected for upload else { $('span#validityMessages').html("Please select a File of size less than 4MB!"); return; } } function UploadImage_Complete() { //Check to see if this is the first load of the iFrame if (isFirstLoad == true) { isFirstLoad = false; } $('#uploadLoader').hide(); //Reset the image form so the file won't get uploaded again document.getElementById("ImgForm").reset(); // this call will get uploads if any exists on server against this id and after successfull upload refreshing partial view to get the latest uploads GetFiles(); } function GetFiles() { var url = '@Url.Action("GetFiles","Home")'; var Id = $('input#ID').val(); url = url + "?id=" + Id $.get(url, function (response) { $('#uploadsContainer').html(response); }); } </script> }
上传 ViewModel
这是我的上传视图模型,它是强类型化到上传的部分视图。
public class UploadsViewModel { public long ID { get; set; } public List<file> Uploads { get; set; } public UploadsViewModel() { this.Uploads = new List<file>(); } } public class File { public string FilePath { get; set; } public long FileID { get; set; } public string FileName { get; set; } }
上传控制器方法
这是我的控制器方法,我接收两个参数输入:第一个是发布表单,表单中我发布了一些值到隐藏字段,这些值对于数据库更新是必需的;第二个参数是发布的文件。我只是将文件保存在服务器上的 Uploads 目录中,并使用 Entity Framework 更新数据库中的表。最后,返回一个部分视图,该视图将显示上传的文件名、如果它是图片类型文件则显示缩略图以及删除文件的按钮。对于这篇帖子,我将状态保存在 Session 中。
[HttpPost] public ActionResult Upload(FormCollection form, HttpPostedFileBase file) { UploadsViewModel uploadsViewModel = Session["Uploads"] != null ? Session["Uploads"] as UploadsViewModel : new UploadsViewModel(); uploadsViewModel.ID = long.Parse(form["id"]); File upload = new File(); upload.FileID = uploadsViewModel.Uploads.Count + 1; upload.FileName = file.FileName; upload.FilePath = "~/Uploads/" + file.FileName; //if (file.ContentLength < 4048576) we can check file size before saving if we need to restrict file size or we can check it on client side as well //{ if (file != null) { file.SaveAs(Server.MapPath(upload.FilePath)); uploadsViewModel.Uploads.Add(upload); Session["Uploads"] = uploadsViewModel; } // Save FileName and Path to Database according to your business requirements //} return PartialView("~/Views/Shared/_UploadsPartial.cshtml", uploadsViewModel.Uploads); }
上传部分视图
这是我的部分视图代码。
@model List<AjaxFileUploading.ViewModels.File> @{ Layout = null; var IsAnyImage = Model.Any(x => new String[] { "jpg", "jpeg", "png", "gif" }.Contains(x.FileName.Split('.').Last())); } @if (Model.Any()) { <h3>Uploads</h3> <hr /> <table style="width: 500px;"> <tr> @if (IsAnyImage) { <th>Thumbnail</th> } <th>FileName</th> <th>Action</th> </tr> <tbody> @for(int i=0; i< Model.Count; i++) { <tr> <td style="text-align: center;"> <img width="60" src="@Url.Content(Model[i].FilePath)" /> </td> <td style="text-align: center;"> <a href="@Url.Content("~/Uploads/" + Model[i].FileName)" target="_blank">@Model[i].FileName</a></td> <td style="text-align: center;"> <img id="@Model[i].FileID" class="delUpload" width="20" src="@Url.Content("~/Content/Images/delete.png")" /></td> </tr> } </tbody> </table> } else { <h3>No Uploads Found!</h3> }
主视图的 Javascript 和 Jquery
我们需要做的第一件事是包含一个 jQuery 版本。由于我正在编写一个 MVC 应用程序,我选择使用框架提供的包,所以您将在主布局文件 _Layout.cshtml 中看到以下行,该文件位于 Views >> Shared 目录中。
@Scripts.Render("~/bundles/jquery")
现在我们可以开始创建我们的 JavaScript 来处理表单发布。UploadImage() 方法不是绝对必需的,因为我们示例中的所有内容只是发布表单,但在我的情况下,我显示一个繁忙指示器,以便用户知道文件正在上传。这里我们只创建一个全局变量来指示是否是 iframe 的首次加载,以及一个用于上传图像的函数。
现在表单已设置为发布到我们的 iFrame。这是检查是否已有与此记录关联的文件的代码,如果已存在则显示,并且也在此事件中应用了验证。实际上,当 iframe 加载时,此事件将被调用,在首次加载页面时也会调用 iframe 加载事件,这就是为什么我在这里也放置了验证代码。我只是通过一个标志来检查这是 iframe 的首次加载还是不是,如果不是首次加载,则意味着用户正在上传文件,我们可能需要验证文件大小。我不允许文件大于 4MB,您也可以在这里检查文件扩展名,也许您只想允许用户上传图像,那么您可以在此函数中检查这一点,如果您想仅对特定类型的文件进行上传。
<script type="text/javascript"> var isFirstLoad = true; function UploadImage() { // check for size of file not greater than 1MB if ($("#uploadFile").val()) { var iSize = ($("#uploadFile")[0].files[0].size / 1024); iSize = (Math.round((iSize / 1024) * 100) / 100); if (iSize > 4) { alert("File must be less than 4MB"); $('span#validityMessages').html("File must be less than 4MB"); return; } else { // on form post showing Busy Indicator $('#uploadLoader').show(); console.log(iSize + "Mb"); } } // check for no file selected for upload else { $('span#validityMessages').html("Please select a File of size less than 4MB!"); return; } } function UploadImage_Complete() { //Check to see if this is the first load of the iFrame if (isFirstLoad == true) { isFirstLoad = false; } $('#uploadLoader').hide(); //Reset the image form so the file won't get uploaded again document.getElementById("ImgForm").reset(); // this call will get uploads if any exists on server against this id and after successfull upload refreshing partial view to get the latest uploads GetFiles(); } </script>
最后,这是使用 ajax 删除文件。
$(document).ready(function () { $('img.delUpload').live('click', function () { var Id = this.id; var url = '@Url.Action("DeleteFile","Home")'; url = url + "?id=" + Id $.get(url, function (response) { $('#uploadsContainer').html(response); }); }); });
删除文件控制器方法
这是删除文件操作。
public ActionResult DeleteFile(long id) { /* Use input Id to delete the record from db logically by setting IsDeleted bit in your table or delete it physically whatever is suitable for you for DEMO purpose i am stroing it in Session */ UploadsViewModel viewModel = Session["Uploads"] as UploadsViewModel; File file = viewModel.Uploads.Single(x => x.FileID == id); try { System.IO.File.Delete(Server.MapPath(file.FilePath)); viewModel.Uploads.Remove(file); } catch (Exception) { return PartialView("~/Views/Shared/_UploadsPartial.cshtml", viewModel.Uploads); } return PartialView("~/Views/Shared/_UploadsPartial.cshtml", viewModel.Uploads); }
GetFiles 操作看起来像这样。
public ActionResult GetFiles(long Id) { UploadsViewModel viewModel = Session["Uploads"] as UploadsViewModel; return PartialView("~/Views/Shared/_UploadsPartial.cshtml", (viewModel == null ? new UploadsViewModel().Uploads : viewModel.Uploads)); }
我创建了一个示例项目来演示这一点。屏幕截图已附上。
您可以在此处下载示例源代码。
下载源代码 (AjaxFileUploading.zip)
希望您能理解它的工作原理。供参考,我从哪里获得帮助,您也可以访问此链接了解更多详情: 使用 jQuery 和 MVC 3 进行 AJAX 文件上传