ارسال فایل و تصویر به همراه داده‌های دیگر از طریق jQuery Ajax
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: چهار دقیقه

یکی از سوالات رایجی که زیاد پرسیده می‌شود این است که چطور باید یک تصویر را به همراه فیلدهای دیگر به سمت سرور ارسال کرد. اکثر مثال‌های آپلود عکس بدین صورت هستند که از یک کتابخانه پویا استفاده می‌کنند که همان لحظه عکس را به سمت سرور ارسال میکنند. ولی یک مثال و کاربرد بسیار ساده این است که من میخواهم اطلاعات فرم و عکس Input File را به همراه یکدیگر همزمان ارسال نمایم که اتفاقا این مسئله به شدت هم اتفاق می‌افتد. برای مثال شما اطلاعات یک دانش آموز را وارد سیستم میکنید که به صورت ایجکسی به سمت سرور ارسال می‌شوند و حالا نیاز است که تصویر دانش آموز هم وارد سیستم شود که ارسال این تصویر نیز از طریق یک input File رخ می‌دهد. نحوه کار با یک input File در سمت سرور در سایت جاری پرداخته شده است که برای پرهیز از تکرار از آن خودداری میکنم.

XMLHttpRequest رابطی است که به شما امکان نقل و انتقالات را از سمت کاربر، به سمت سرور و سپس دریافت پاسخ آن را می‌دهد. این رابط طوری طراحی شده‌است که دیگر برای این جابجایی نیازی به بارگزاری مجدد کل صفحه نباشد و قسمتی از اطلاعات صفحات به روز شوند، مزاحمتی برای کاربر ایجاد نشود. به همین دلیل از این رابط، در پشت صحنه‌های عملیات ایجکسی استفاده زیادی می‌شود. در این مقاله با استفاده از خصوصیتی به نام request.IsAjax بررسی می‌شود که آیا درخواست رسیده به سرور از نوع ایجکسی است یا خیر. اگر به سورس نوشته شده این متد نگاه دقیق‌تری بیندازیم، متوجه می‌شویم کاری که این متد انجام می‌دهد، در واقع در یک خط خلاصه می‌شود و آن بررسی هدری برای وجود درخواست از نوع XMLHttpRequest است:
return request.Headers["X-Requested-With"] == "XMLHttpRequest";
برای اطلاعات بیشتر در مورد این رابط، خصوصیت‌ها، متدهایش و پشتیبانی سایر مرورگرها از این خواص، بهتر است به صفحه مستندات موزیلا نگاهی بیندازید.

یکی از متدهای این رابط، متد ارسال آن (send) می‌باشد که میتواند رابطی به نام formData را انتقال دهد و این رابط از نوع مجموعه‌ای از کلید و مقدارهاست. این رابط زمانی به کار گرفته می‌شود که انکدینگ فرم خود را بر روی multipart/form-data قرار داده باشید. این ساختار می‌تواند توسط دستور for of بررسی گردد. برای آشنایی بیشتر با متدهای آن این صفحه را مطالعه فرمایید.
هنگام ارسال فایل در حالت postback، ما فرم را بر روی multipart قرار می‌دهیم تا امکان ارسال آن توسط formData مهیا شود. ولی از آنجاکه ما از ایجکس استفاده می‌کنیم، بهتر است که خودمان مستقیما از این ساختار استفاده کنیم.

بخشی از فرم Html
<div>
  <label>تصویر</label>
  <div>
    <input id="picture" type="file" data-buttonText="انتخاب تصویر">
  </div>
</div>

<div>
  <label>کد ملی</label>
  <div>
    <input id="txtNationalCode" required="" maxlength="10" type="text">
  </div>
</div>

<div>
  <label>نام</label>
  <div>
    <input id="txtName" type="text" maxlength="50" required="">
  </div>
</div>

<div class="form-group">
  <div class="col-sm-4 col-sm-offset-2">
    <button class="btn btn-primary" id="btnSubmit" type="submit">ذخیره</button>
    <button class="btn btn-white" id="btnClear" type="submit">لغو</button>
  </div>
</div>
برای زیباسازی  کار، برای المان input File در بالا، از کتابخانه Bootstrap FileStyle استفاده شده‌است.
سپس کد جی کوئری زیر را می‌نویسیم:
 var formData = new FormData();
        formData.append('FirstName', $("#txtName").val());
        formData.append('NationalCode', $("#txtNationalCode").val());

        jQuery.each($('#picture')[0].files, function (i, file) {
            formData.append('picture-'+i, file);
        });

 $.ajax({
            type: "POST",
            dataType: "json",
            url: address,
            data: formData,
            success:
                function (data) {
                //.....

                },
            error:
                function (data) {
                  //......
                }
        });
در کد بالا ابتدا یک شیء FormData ایجاد می‌شود و سپس کلیدو مقدارهایی نیز به آن انتساب داده می‌شوند. در صورتیکه Input File شما، امکان آپلود چندین فایل همزمان را می‌دهد، می‌توانید با استفاده از حلقه‌ی مورد نظر، یکی یکی آن‌ها را به این شیء اضافه کنید و به ترتیب اسامی pictue-0 ,picture-1 و ... به آن‌ها انتساب داده می‌شود. در نهایت تنها کاری که لازم است انجام دهید این است که روال همیشگی را طی کنید و این شیء را به عنوان data، در اختیار متد ajax قرار دهید تا ارسال شود.

توجه به این نکته ضروری است و با توجه کدهایی که در نت دیدم و بسیاری از آن حتی به عنوان پاسخ صحیح در نظر گرفته شده بودند این است که شیء FormData شامل هیچ سازنده‌ای نیست و باید با استفاده از متد append آن‌ها را اضافه کنید.
  • #
    ‫۸ سال قبل، دوشنبه ۲۲ شهریور ۱۳۹۵، ساعت ۲۱:۲۹
    اگر ممکنه این مثال رو برای سمت کلاینت دات نت هم بنویسید.
  • #
    ‫۸ سال قبل، چهارشنبه ۲۴ شهریور ۱۳۹۵، ساعت ۱۶:۱۱
    چطور در سمت سرور یا کنترلر، تعداد عکس‌های آپلود شده را تشخیص دهیم؟
    • #
      ‫۸ سال قبل، چهارشنبه ۲۴ شهریور ۱۳۹۵، ساعت ۱۶:۴۳
      برای این View که از ویژگی multiple مربوط به HTML5 استفاده می‌کند و با نام کنترل files
      @using (Html.BeginForm("Multiple", "Home", FormMethod.Post, new { enctype = "multipart/form-data" })) { 
          <input type="file" name="files" multiple />
          <button class="btn btn-default">Upload</button>   
      }
      و یا این View که سه کنترل هم نام (با نام files) ارسال فایل را تعریف کرده‌است:
      @using (Html.BeginForm("Multiple", "Home", FormMethod.Post, new { enctype = "multipart/form-data" })) {  
          <input type="file" name="files" /><br />
          <input type="file" name="files" /><br />
          <input type="file" name="files" /><br />
          <button class="btn btn-default">Upload</button>   
      }
      هر دو یک اکشن متد سمت سرور را خواهند داشت (اگر نام کنترل‌های سمت کلاینت یکسان باشند، در سمت سرور، یک آرایه را تشکیل می‌دهند):
      [HttpPost]
      public ActionResult Multiple(IEnumerable<HttpPostedFileBase> files)
      {
          foreach (var file in files)
          {
              if (file != null && file.ContentLength > 0)
              {
                  file.SaveAs(Path.Combine(Server.MapPath("/uploads"), Guid.NewGuid() + Path.GetExtension(file.FileName)));
              }
          }
          return View();
      }
    • #
      ‫۸ سال قبل، چهارشنبه ۲۴ شهریور ۱۳۹۵، ساعت ۱۶:۵۹
      اگر نام کنترل‌های ارسال فایل سمت کلاینت یکسان نباشند، هر کدام را باید به عنوان یک پارامتر جدید اکشن متد تعریف کرد و یا می‌توان از طریق آرایه‌ی Request.Files، بدون ذکر پارامتری در اکشن متد، به تمام آن‌ها دسترسی پیدا کرد:
      foreach (string file in Request.Files)
      {
         var hpf = Request.Files[file] as HttpPostedFileBase;
        // todo: save it
      }
  • #
    ‫۷ سال و ۱۱ ماه قبل، پنجشنبه ۱۵ مهر ۱۳۹۵، ساعت ۰۵:۲۵
    نکته تکمیلی :
    این حالت رو می‌توان به صورت ترکیبی با Ajax.BeginForm هم انجام داد تا از امکان بایندیگ و ... محروم نشیم:
    سمت Html:
    @using (Ajax.BeginForm("Upload", "Attachment", FormMethod.Post, 
    new AjaxOptions
                                {
                                    HttpMethod = "POST",
                                },
                                new
                                {
                                    encType = "multipart/form-data",
                                    id = "attach-form"
                                }))
    {
        @Html.AntiForgeryToken()
    
        @Html.TextBoxFor(m => m.FirstName })
        
        <input type="file" name="Files" data-buttonText="انتخاب تصویر">
       
        <button type="submit">ارسال</button>
    }
    کد‌های Javascript :
            var formData = new FormData();
    
            $('form').submit(function() {
                
                var action = $(this).attr('action');
                var formData = new FormData($(this).get(0));
    
                $.ajax({
                    type: "POST",
                    dataType: "json",
                    url: action,
                    data: formData,
                    processData: false,
                    contentType: false,
                    success: function(data) {
                        //...
                    }
                    success: function(data) {
                         //...
                    }
                });
    
                return false;
            });
    کد سمت سرور #C:
    public class MyModel
        {
            public string FirstName{ get; set; }
    
            public IEnumerable<HttpPostedFileBase> Files { get; set; }
        }  
    [AjaxOnly]
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Upload(MyModel model)
    {
         if (!ModelState.IsValid)
             return //....
    
         if(model.Files != null)
             foreach (var file in model.Files)
                 if (file  != null && file.ContentLength > 0)
                 {
                     // ....
                 }
    }

  • #
    ‫۷ سال و ۹ ماه قبل، پنجشنبه ۱۸ آذر ۱۳۹۵، ساعت ۱۶:۲۶
    من از این روش  استفاده کردم. توی این روش البته از پلاگین jquery form  هم استفاده شده. ویژگی این روش اینه که از بایندینگ محروم نمیشیم و مجبور نیستیم که تمامی کنترلهای داخل صفحه رو دونه به دونه و دستی به FormData اضافه کنیم و همچنین علاوه بر اون از ولیدیشن هایی که توی ویومدل تعریف کردیم هم بهره مند میشیم. و البته توی این مثال یه progress bar هم به کاربر نمایش داده میشه تا درصد پیشرفت فرآیند آپلود رو ببینه.
  • #
    ‫۶ سال و ۱۰ ماه قبل، یکشنبه ۷ آبان ۱۳۹۶، ساعت ۲۰:۳۱
    با سلام
    در صورتی که بخواهیم به همراه هر فایل یک داده ارسال کنیم در سمت سرور چطور میشه داده مرتبط با فایل مربوطه رو واکشی کرد؟
    مثلا به ازای هر فایل بخواهیم عنوان فایل و توضیحاتی راجع به فایل رو هم ارسال کنیم و در سمت سرور واکشی کرده و در دیتابیس ذخیره کنیم.
    • #
      ‫۶ سال و ۱۰ ماه قبل، یکشنبه ۷ آبان ۱۳۹۶، ساعت ۲۱:۵۰
      نمونه‌اش MyModel هست که کمی بالاتر ارسال شده. یا نمونه‌ی پویای اون بررسی Request.Files هست.
  • #
    ‫۵ سال و ۴ ماه قبل، یکشنبه ۱۵ اردیبهشت ۱۳۹۸، ساعت ۱۷:۵۲
    برای ارسال آرایه توسط FormData، نباید آرایه را مستقیما Append کرد؛ بدین شکل:
    var formData = new FormData();
    var splitedKeywords = $('#keywords').val().split(',');
    formData.append('Keywords', JSON.stringify(splitedKeywords));
    در این صورت خروجی در سمت سرور به این شکل خواهد بود؛ آرایه ایی تک عضوی با این مقدار:
    "\"کلمه اول\",\"کلمه دوم\""

    برای ارسال یک آرایه (لیست) باید به صورت زیر عمل شود :
    var formData = new FormData();
    var splitedKeywords = $('#keywords').val().split(',');
    for (var counter = 0; counter < splitedKeywords.length; counter++) {
        formData.append('Keywords', JSON.stringify(splitedKeywords[counter]));
    }
    تمام آرایه تک به تک اضافه شود.
  • #
    ‫۵ سال و ۲ ماه قبل، چهارشنبه ۱۲ تیر ۱۳۹۸، ساعت ۲۳:۰۷
    سلام و تشکر از مطلب مفیدتون. میشه از این مدل در ASP.NET Core هم استفاده کرد؟ قبلا در ASP.NET MVC اطلاعات پارامترهای Request رو به صورت Request.Params["value"] میشد دریافت کرد ولی Params در ASP.NET MVC از فضای نام System.Collections.Specialized  استفاده میکرد. در حال حاضر برای متدهای ajax که به صورت post و مدلی که شما اشاره فرمودید به سمت سرور ارسال میشه خصوصیت پارامترها وجود نداره که بتوانیم از سمت کلاینت اطلاعات را دریافت کنیم. توضیح واضح‌تر اینکه مثلا در متد زیر از سمت کلاینت چطوری میشه سمت سرور در ASP.NET Core پارامترهای بخش data رو دریافت کرد؟
    $.post( "manage/test", { name: "John", time: "2pm" })
      .done(function( data ) {
        alert( "Data Loaded: " + data );
      });
    در ASP.NET MVC به راحتی با کد زیر در سمت سرور میشد اطلاعات را دریافت کرد
    var name = Request["name"].ToString();
    var time = Request["time"].ToString();
    ممنون می‌شم در این مورد راهنمایی بفرمایید
    • #
      ‫۵ سال و ۲ ماه قبل، چهارشنبه ۱۲ تیر ۱۳۹۸، ساعت ۲۳:۴۰
      - در ASP.NET Core، یکسری key/value pair مانند HttpContext.Request.Query و HttpContext.Request.Form و ... به صورت مجزایی وجود دارند. البته روش بهتر استفاده از Model Binding است.
      + سمت سرور مطلب جاری در ASP.NET Core، با سمت سرور مطلب «بررسی روش آپلود فایل‌ها از طریق یک برنامه‌ی Angular به یک برنامه‌ی ASP.NET Core» یکی هست. آن مطلب را از قسمت «دریافت فرم درخواست پشتیبانی در سمت سرور و ذخیره‌ی فایل‌های آن‌» به بعد مطالعه کنید.
      +  نکته‌ی تکمیلی «در نسخه‌ی اخیر افزونه jquery-ajax-unobtrusive، ارسال فایل با استفاده از FormData نیز به صورت توکار پشتیبانی می‌شود» را هم مدنظر داشته باشید. یعنی برای سمت کلاینت نیازی به نوشتن کدهای خاصی برای ارسال فایل به سمت سرور نیست (نیازی نیست کد جی‌کوئری بنویسید). همینکه به صورت معمولی از افزونه‌ی jquery-ajax-unobtrusive استفاده کنید، تمام نکات آن مانند قبل است.
  • #
    ‫۴ سال و ۴ ماه قبل، سه‌شنبه ۹ اردیبهشت ۱۳۹۹، ساعت ۰۶:۵۱
    سلام، من وقتی میخوام دیتا رو به سمت سرور ارسال کنم براساس مثال بالا مشکلی نداره فقط مشکلی که وجود داره اینه دیتای بازگشتی Response رو بصورت جیسون پاس نمیده بهم! من از Webmethod‌ها در asp.net استفاده میکنم و [ScriptMethod(ResponseFormat = ResponseFormat.Json)]  رو هم اضافه کردم البته در کدهای دیگه که دارم مشکلی نیست اما این کد دیتا رو بصورت جیسون پاس نمیده
    var formData = new FormData();
                formData.append('Organizations', $('#Organizations').val());
                formData.append('OrgNameInReports', $('#OrgNameInReports').val());
                formData.append('CenterTypes', $('#CenterTypes').val());
                formData.append('Users', $('#Users').val());
                formData.append('Comments', $('#Comments').val());
                formData.append('UserTypes', $('#UserTypes').val());
    
                jQuery.each($('#profile-img')[0].files, function (i, file) {
                    formData.append('picture-' + i, file);
                });
                $.ajax({                
                    url: 'SettingPages/OrgManagmentService.asmx/SaveNewOrgManager',
                    type: 'POST',
                    data: formData,
                     dataType: 'json',
                    contentType: false,
                    processData: false,
    
                    beforeSend: function () {
                        Preloader();
                    },
                    complete: function (data) {
                        
                        RemovePreloader();
                    },
                    success: function (data, status) { 
                        data = JSON.stringify(data);
                        console.log(data);
                       
    
                    },
                    error: function (data, status,e) {
                        alert(status);
                    }
                });

    • #
      ‫۴ سال و ۴ ماه قبل، سه‌شنبه ۹ اردیبهشت ۱۳۹۹، ساعت ۱۲:۰۳
      developer tools مرورگر رو باز کن. برگه‌ی network اون رو برای مشاهده‌ی خروجی واقعی بررسی کن (امکان مشاهده‌ی محتوای کامل response در این برگه وجود داره). شاید خطایی دریافت کردی.
      • #
        ‫۴ سال و ۴ ماه قبل، سه‌شنبه ۹ اردیبهشت ۱۳۹۹، ساعت ۱۶:۲۶
        خطایی وجود نداره یعنی کد 200 برمیگردونه اما وضعیتی که از ریسپانس میاد میره تو بلوک error و چک کردم errorparse میده ! من از دات نت کور استفاده نمیکنم یا mvc که return json داشته باشه ها!  
        • #
          ‫۴ سال و ۴ ماه قبل، سه‌شنبه ۹ اردیبهشت ۱۳۹۹، ساعت ۲۲:۵۲
          errorParse کاملا واضح هست که خروجی مورد نظر مشکلی در فرمت جیسون دارد. خروجی بازگشتی از تب network و همچنین لاگ‌های موجود در کنسول را با دقت بررسی نمایید.