[ASP.net MVC 4] ASP.net MVC + jQuery Uploadify 3.x版 完整解決方案

[ASP.net MVC 4] ASP.net MVC + jQuery Uploadify 3.x版 完整解決方案

前台

最近專案有個需求

1. 使用者可以在上傳檔案選檔時,一次選多個檔 => Html5 有支援:[HTML5] HTML5 File API by 小朱

2. 選擇檔案後,自動上傳,使用者不必再按Button去Post=>可以在<input type=”file” />加一個onchange的JS事件,讓表單POST,不過這招在IE會被擋

3. 剛好,客戶給我要上傳檔案的畫面又是包在頁籤div底下,所以我上傳檔案若POST頁面的話,User會回到第一個頁籤,降低使用者體驗

4. 客戶美工還給我一個上傳檔案的按鈕圖片,所以傳統<input type=”file” />得打造外觀了

經過以上考量,最後還是把以前寫WebForm用過的jQuery Uploadify套件拿出來用

官網:http://www.uploadify.com/

image

翻譯:

1. 支援選擇多檔案上傳

2. 選擇檔案後自動上傳

3. 上傳按鈕可以客製化外觀

4. 進度條顯示(進度不用自己算,套件做掉了)

5. 上傳檔案不會刷新頁面

6. 前端JS判斷上傳限制

 

官網文件說明可以看到有兩種套件:Uploadify(Flash版)和UploadFive(Html 5版)

本文介紹的是Flash版※注意3.x版和2.x版本的程式碼,參數名稱會不太一樣

 

官網Flash版Demo頁:http://www.uploadify.com/demos/

使用jQuery Uploadify的環境需求:

image

翻譯:

1. jQuery 1.4版以上

2. 用戶端的Flash Player 9版以上

3. 一種Server Side動態網頁技術,本文以ASP.net MVC為範例

 

 

實作

1.一開始不用進官網下載壓縮包,直接用Visual Studio 2012的NuGet套件管理員找「Uploadify」安裝就行

image

image

由於Uploadify在FireFox使用會有掉Session問題

所以先在Global.asax.cs 寫下


using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Security;

namespace MvcApplicationjQueryUploadify
{
    // Note: For instructions on enabling IIS6 or IIS7 classic mode, 
    // visit http://go.microsoft.com/?LinkId=9394801
    public class MvcApplication : System.Web.HttpApplication
    {
        #region Firefox用的jQuery Uploadify設定
        void Application_BeginRequest(object sender, EventArgs e)
        {
            
                string session_param_name = "ASPSESSID";
                string session_cookie_name = "ASP.NET_SessionId";
                if (HttpContext.Current.Request.Form[session_param_name] != null)
                {
                    UpdateCookie(session_cookie_name, HttpContext.Current.Request.Form[session_param_name]);
                }
                else if (HttpContext.Current.Request.QueryString[session_param_name] != null)
                {
                    UpdateCookie(session_cookie_name, HttpContext.Current.Request.QueryString[session_param_name]);
                }
            


            //身份驗證
            
                string auth_param_name = "AUTHID";
                string auth_cookie_name = FormsAuthentication.FormsCookieName;
                if (HttpContext.Current.Request.Form[auth_param_name] != null)
                {
                    UpdateCookie(auth_cookie_name, HttpContext.Current.Request.Form[auth_param_name]);
                }
                else if (HttpContext.Current.Request.QueryString[auth_param_name] != null)
                {
                    UpdateCookie(auth_cookie_name, HttpContext.Current.Request.QueryString[auth_param_name]);
                }
           
        }

        private void UpdateCookie(string cookie_name, string cookie_value)
        {
            HttpCookie cookie = HttpContext.Current.Request.Cookies.Get(cookie_name);
            if (null == cookie)
            {
                cookie = new HttpCookie(cookie_name);
            }
            cookie.Value = cookie_value;
            HttpContext.Current.Request.Cookies.Set(cookie);//重新設定cookie
        }

        #endregion 


        protected void Application_Start()
        {
            #region 建立資料夾(上傳檔案用)
            string fileDir = Server.MapPath("~/Uploads/");
            if (!Directory.Exists(fileDir))
            {
                Directory.CreateDirectory(fileDir);
            }
            #endregion 







            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
        }
    }
}

再來是上傳檔案的Controller Code


using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;

namespace MvcApplicationjQueryUploadify.Controllers
{
    public class HomeController : Controller
    {
      
        [HttpGet]
        public ActionResult Index()
        {
           
            
            return View();
        }

        /// <summary>
        /// 上傳一筆檔案(前端Uploadify會呼叫此Action)
        /// </summary>
        /// <param name="Filedata">此參數名稱可以在Uploadify設定</param>
        /// <returns></returns>
        [HttpPost]
        public ActionResult UploadFile(HttpPostedFileBase Filedata)
        {
            
            if (Filedata != null && Filedata.ContentLength>0)
            {
                try
                {
                    
                    // 檔案上傳的儲存資料夾
                    string fileDir = Server.MapPath("~/Uploads/");
                    
                    string ext = Path.GetExtension(Filedata.FileName); //副檔名
                    string newName = Guid.NewGuid().ToString() + ext; // 新檔名

                    Filedata.SaveAs(Path.Combine(fileDir,newName));

                    //成功就回傳該圖片的URL
                    return Content(Url.Content("~/Uploads/" + newName));
                    
                }
                catch (Exception ex)
                {
                    //寫Log
                    return Content("");
                }
            }
            return Content("");
          
        }


    }
}

前端View Uploadify叫用方法:



<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
    @*引用Uploadify樣式,Queue會用到*@
    <link href="~/Scripts/uploadify/uploadify.css" rel="stylesheet" />
    @*引用jQuery核心函式庫*@
    <script src="~/Scripts/jquery-1.4.4.min.js"></script>
    @*引用uploadify核心*@
    <script src="~/Scripts/uploadify/jquery.uploadify-3.2.min.js"></script>
    <script type="text/javascript">
        $(document).ready(init);
        function init()
        {
            //解決Firefox掉Session用的程式碼
            var ASPSESSID = "@Session.SessionID";
            var AUTHID = "@(Request.Cookies[FormsAuthentication.FormsCookieName] == null ? string.Empty : Request.Cookies[FormsAuthentication.FormsCookieName].Value)";

            var width = 814;
            var height = 269;
            var buttonImageUrl = "@Url.Content("~/Content/Images/GoogleImg.png")";//這是我自己準備的按鈕圖

            //js註冊使用jQuery Uploadify
            //Filedata為input的id
            //queue為div id,佇列div的 id
            UploadFileFunc("Filedata", "queue", ASPSESSID, AUTHID,width,height,buttonImageUrl);



            
           

        }
        //這個function再自行放到自己的.js檔
        function UploadFileFunc(FiledataID, QueueID, ASPSESSID, AUTHID,width,height,buttonImageUrl)
        {
            $('#' + FiledataID).uploadify({
                buttonImage: buttonImageUrl,//依美工給的圖置換上傳按鈕圖
                width: width,//按鈕寬度和圖片一樣
                height: height,//按鈕高度和圖片一樣

                swf: '/Scripts/uploadify/uploadify.swf',//必填
                uploader: '/Home/UploadFile',//使用哪個Action 做上傳
            

                multi: true,//true支援選擇多檔案上傳
                auto: true,//設置true,檔案選擇框,一按確定就上傳,false的話,要另外呼叫方法傳遞upload參數觸發上傳行為
                fileTypeExts: '*.jpg;*.gif;*.png',//限制可以選擇的檔案類型
                fileTypeDesc: 'Image Files (.jpg, .gif, .png)',//選擇檔案時的說明
                fileSizeLimit: '4MB',//在js端就限制檔案大小,User選擇超過大小的檔案時,就會跳出error
                queueID: QueueID,//上傳進度條呈現的地方
                queueSizeLimit: 4,//限制queue的數量
                simUploadLimit: 0,//同時上傳檔案數,0為無限
                removeCompleted: true,//檔案上傳完成時,畫面上的佇列是否消失
               
                fileObjName: 'Filedata',//Server端的Action,以什麼名稱接收HttpPostedFileBase物件

                onSelectError: function (file) {//當選擇檔案不符合條件時,觸發: http://www.uploadify.com/documentation/uploadify/onselecterror/

                    //alert('The file ' + file.name + ' returned an error and was not added to the queue.');

                },
                onUploadSuccess: function (file, data, response) {
                    //一個佇列上傳成功時
                    //alert('The file ' + file.name + ' was successfully uploaded with a response of ' + response + ':' + data);
                    //data參數是Controller回傳的字串,想要Json格式的話,要再另外找Plugin把字串轉成Json物件
                    //以本文範例data就是該圖片上傳後的Url字串
                    
                },
                onQueueComplete: function (queueData) {
                    //全部佇列執行完畢時
                    //alert(queueData.uploadsSuccessful + ' files were successfully uploaded.');
                    

                },
                //解決Firefox掉Session的程式碼,ASPSESSID和AUTHID命名要和Global.asax.cs裡寫的一樣
                formData: {
                    ASPSESSID: ASPSESSID, 
                    AUTHID: AUTHID
                }




            });

        }
       
</script>
  
</head>
<body>
   
     @*※為div queue加上style="display:none;",就可以隱藏queue了*@
     <div id="queue" ></div>
     <input id="Filedata" type="file" name="Filedata"   />
       

 

 


</body>
</html>

如果上傳中Queue發生錯誤404 Error

image

或 500 Error

image

這表示ASP.net預設的上傳限制4MB是不夠User上傳超大檔案

解決辦法參考他人文章:

win8下IIS8.0下uploadify-v3.1上传文件超过30M,报HTTP Error(404)

[IIS] IIS7.0上傳檔限制的解決方法

 

執行畫面:

image

本文範例檔下載,請用7-zip解壓,放到我的Azure沒錢

 

 

結語

雖然Uploadify能夠客製化很多東西,但事前配置是很麻煩的

一直想找個更簡單,程式碼寫更少的套件取代,但沒時間……Orz

 

2013.8.27 追記幾個實務上的經驗分享

1.畫面上不一定要有<input type=”file” name=”Filedata” id=”Filedata” />,因為最後會被套件Render成<object>使用.swf來上傳檔案

所以即使改成<div id=”Filedata”></div>也可以。

2.承上,因為只是專門呈現<object>的區域,所以名稱也不一定要叫Filedata,如果同個畫面有多個上傳按鈕,可以

<div id=”Filedata1”></div>

<br/>

<div id=”Filedata2”></div>

真正影響Action接收的HttpPostedFileBase名稱的是在uploadify的設定參數:fileObjName

3.queueSizeLimit參數為當前畫面上Queue的限制數量,可以指定數字,當做限制使用者選檔數量

4.如果發生

typeerror object  has no method 'uploadify'

找不到uploadify方法的錯誤

image

那是因為uploadify這個function是註冊於jQuery之上,要記得引用順序

先jQuery核心函式庫→uploadify核心函式庫

然後注意頁面footer不要再引用一次jQuery核心函式庫,它會蓋掉剛剛註冊過的uploadify function

我的話會統一把jQuery核心函式庫放在head標籤,就解決了

5.如果在送檔案到Action時也要連同其他資訊一塊送的話,可以使用formData參數設定,本文有使用,只是沒在Action中寫出來而已,用法請參考官網

6.如果網站有使用bootstrap在IE9、IE10,當滑鼠游標移到上傳按鈕圖片上或點擊時,發生以下錯誤

JavaScript 執行階段錯誤:必須要有物件

image

是因為uploadify套件和bootstrap的某個功能的相衝,解法:

1.在頁面的<head>加入<meta http-equiv="X-UA-Compatible" content="IE=8" />強制IE使用IE8文件模式瀏覽,只是這樣會有破版可能

2.將uploadify嵌入iframe,該iframe是一個乾淨的頁面,沒有bootstrap的attribute綴詞
※如果用在編輯資料頁面,為了把原本圖片資料抓出來,可以利用iframe的src傳遞QueryString帶檔案名稱

 

2013.10.28

再分享幾個經驗,客戶的表單上有uploadify上傳檔案、ckeditor輸入欄位,當表單提交時

如果為了firefox掉Sesson問題而在Global.asax.cs寫下

if (HttpContext.Current.Request.Form[session_param_name] != null)

這段文字的話,ckeditor輸入的文字就會過不了ASP.net的輸入驗證

後來我的解決辦法:從Global.asax.cs拿掉那堆程式碼,firefox要掉Session就讓它掉,在上傳檔案的Action方法裡注意Filter和裡頭的程式碼不要存取Session,單純做上傳動作就皆大歡喜了

 

 

其他參考文章

jQuery Uploadify在ASP.NET MVC3中的使用

基于jquery的上传插件Uploadify 3.1.1在MVC3中的使用