آپلود فایل با استفاده از JQuery AJAX در MVC
پنجشنبه 1 بهمن 1394در این مقاله می خواهیم آپلود فایل در MVC را بدون بار گذاری کل صفحه آموزش دهیم و به جای آن فقط قسمتی از صفحه که فایل در آن می خواهد آپلود شود را بارگذاری کنیم. اگر بخواهیم برای فایل آپلود تمام صفحه را post-back کنیم کار ساده ایست اما حرفه ای تر آن است که با استفاده از AJAX فقط قسمت مورد نظر خود را Post-back کنیم.
استفاده از AJAX برای پست کردن فرم های استاندارد بسیار ساده است اما زمانی که پست کردن برای چندین فرم جهت آپلود فایل ها انجام می شود، استفاده از AJAX کار ساده ای نیست. این مسئله به خاطر محدودیت های امنیتی مرورگر و sandboxing بوجود می آید. در این مقاله می خواهیم نشان دهیم که چگونه یک آپلود غیر همزمان را بدون بارگزاری مجدد صفحه انجام دهیم و نتیجه را از سرور بگیریم.
اولین ایده ای که به ذهن می رسد، استفاده از یک Helper به صورت Ajax.BeginFormمی باشد اما این helper آپلود فایل با post-back های جزئی را پشتیبانی نمی کند این Helper کل صفحه را post-back می کند.
ایده دوم استفاده از JQuery و AJAX برای پست کردن فایل روی سرور می باشد بنابراین با این کار می توانی از post-back جلوگیری کنیم. در این مقاله می خواهیم یک فایل را ارسال کرده و با برگرداندن یک partial view از طریق AJAX آن را آپلود کنیم اما در اینجا مشکلی که وجود دارد partial view به طور مستقل نمی تواند render شود
برای رفع این مشکل از متد Iframe استفاده کرده و یک postback غیر همزمان به صورت جعلی(Fake) انجام می دهیم که شبیه به آپلود فایل از طریق Ajax به نظر می رسد.
این روش چگونه کار می کند؟
به منظور ایجاد فایل آپلود به صورت AJAX ای نیاز داریم که فایل را به iFrame پست کنیم. ما iFrame را در صفحه قرار می دهیم تا فایل را مخفی کرده سپس به آن پست کند. ما یک partial view در صفحه برمی گردانیم و آن را در تعدادی html container اضافه می کنیم.
مهم است بدانیم که با توجه به محدویت های امنیتی، باید عمل پست شدن در همان دامنه ای انجام شود که عمل پست را انجام می دهیم به عبارت دیگر درخواست ارسال فایل از سایت دیگر نباشد. همچنین این راه حل از یک iFrame مخفی تکی استفاده می کند اما برای چندین آپلود همزمان باید به طور پویا iFrames را ایجاد کرده و به آن ها پست کنید.
View اصلی
در اینجا ما یک فرم با استفاده از mvc Helper ها ایجاد کرده ایم و بعد آن یک 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")'; urlurl = 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(); urlurl = url + "?id=" + Id $.get(url, function (response) { $('#uploadsContainer').html(response); }); } </script> }
UploadsViewModel
کد زیر ViewModel مورد نظر را نشان می دهد که به صورت strongly typed با uploads partial view می باشد.
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; } } </file></file>
کنترلر Upload
کد زیر کنترلر ما می باشد، ما در اینجا دو پارامتر ورودی در نظر گرفته ایم که یکی فرم پست شده است در این فرم تعدادی مقادیر را پست کرده ایم که در hidden fields قرار دارند. و برای به روز رسانی DB لازم است و دومین پارامتر فایل پست شده است ما در اینجا به سادگی فایل را در دایرکتوری Uploads ذخیره کرده ایم و یک جدول را در DB با استفاده از Entity Framework آپدیت می کنیم و در انتها یک partial view برمی گرداند که نام فایل آپلود شده، تصویر در اندازه بند انگشتی-thumbnail-(اگر که فایل از نوع image باشد) و یک دکمه برای حذف فایل را نمایش می دهد.
در این مقاله ما state را در 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); }
Uploads Partial View
در اینجا کد partial view قرار دارد:
@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 برای View اصلی
اولین کاری که باید انجام دهیم این است که صفحه شامل نسخه ای از JQuery باشد. از آنجایی که اپلیکیشن ما از نوع MVC مباشد ما از bundles ها استفاده کرده ایم که توسط framework ارائه شده است. و به صورت زیر می باشد که در Views >> Shared با نام _Layout.cshtml می باشد:
@Scripts.Render("~/bundles/jquery") Views
حالا می توانیم ایجاد JavaScript برای مدیریت ارسال فرم خود را بنویسیم. متد ( )UploadImage کاملا ضروری نیست، از آنجا که تمام مثال ما فقط عمل پست فایل را انجام می دهد اما ما در اینجا یک شاخص مشغول را نمایش می دهیم که به کاربر اطلاع داده شود که فایل در حال آپلود شدن است. در اینجا می خواهیم یک متغیر عمومی تعریف کنیم که می گوید که آیا این اولین بارگذاری ما در iFrame هست یا خیر.
حالا فرم ما برای ارسال iFrame راه اندازی شده است در اینجا کدی وجود دارد که چک می کند که آیا فایلی در مقابل این رکورد از قبل وجود دارد یا خیر. این نمایش داده خواهد شد و اعتبار سنجی نیز در این رویداد انجام می شود. در واقع این رویداد زمانی فراخوانی می شود که iframe بارگذاری شده باشد.در اولین pageload همچنین iframe رویدادی که فراخوانی شده باشد را بارگذاری می کند. بنابراین ما یک اعتبارسنجی در اینجا قرار داده ایم که توسط یک Flag بررسی می شود که آیا این اولین بارگذاری 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)); }
در نهایت خروجی به صورت زیر است:
اگر فایلی انتخاب نکنیم و Upload را بزنیم به صورت زیر می شود:
حالا عکسی انتخاب کرده و آن را آپلود می کنیم:
- ASP.net MVC
- 4k بازدید
- 8 تشکر