1. 程式人生 > >轉:MVC中的文件上傳

轉:MVC中的文件上傳

repl dex Enctype req 通配符 不能 getitem item cookie

上傳文件與與上傳數據區別

上傳數據主要指json等簡單字符串,上傳文件指的是上傳word、excel圖片等。在上傳數據的時候enctype默認為第一個application/x-www-form-urlencoded,而上傳數據包含文件的時候要用第二種multipart/form-data

描述
application/x-www-form-urlencoded 在發送前編碼所有字符(默認)url編碼
multipart/form-data

不對字符編碼。

在使用包含文件上傳控件的表單時,必須使用該值。

text/plain 空格轉換為 "+" 加號,但不對特殊字符編碼。

文件上傳在WEB開發中應用很廣泛,我們經常發微博、發微信朋友圈都用到了圖片上傳功能。

文件上傳是指將本地圖片、視頻、音頻等文件上傳到服務器上,可以供其他用戶瀏覽或下載的過程。

今天我給大家聊聊常見的文件(圖片)上傳的方式和要點處理。

表單上傳

這是傳統的form表單上傳,使用form表單的input[type=”file”]控件,可以打開系統的文件選擇對話框,從而達到選擇文件並上傳的目的,它的好處是多瀏覽器兼容,它是web開發者最常用的一種文件上傳方式。

表單的代碼如下:

<formmethod="post"action="http://uploadUrl"enctype="multipart/form-data">

<inputname="file"type="file"accept="image/gif,image.jpg"/>

<inputname="token"type="hidden"/>

<inputtype="submit"value="提交"/>

</form>

以下是表單上傳幾個關鍵點:

  • method=”post”: 采用post方式提交數據
  • enctype=”multipart/form- data”:采用multipart格式上傳文件,此時request頭會顯示 Content-Type:multipart/form-data; boundary=—-WebKitFormBoundaryzr34cwJ67R95KQC9
  • action:標明上傳的服務端處理地址
  • type=”file”:使用input的file控件上傳
  • 如果是多文件批量上傳,可以將input[type=”file”]的name屬性設置為如:name=”file[]”
  • accept屬性是HTML5的新屬性,它規定了可通過文件上傳提交的文件類型
  • 上傳的觸發事件可以是:input[type=”file”]的onChange觸發,也可以由一個獨立的按鈕的onClick使整個表單提交,此時還可以用input[type=”hidden”]帶一些其它的參數,比如Token來源驗證等等。

Ajax無刷新上傳

Ajax無刷新上傳的方式,本質上與表單上傳無異,只是把表單裏的內容提出來采用ajax提交,並且由前端決定請求結果回傳後的展示結果,不用像直接表單上傳那樣刷新和跳轉頁面。在這裏,我們采用jQuery來作為操作DOM和創建ajax提交的js基礎庫。

html代碼片段如下:

<form>

<inputid="file"name="file"type="file"/>

<inputid="token"name="token"type="hidden"/>

</form>

JavaScript代碼片段如下:

$("#file").on("change",function(){

var formData =newFormData();

formData.append("file", $("#file")[0].files);

formData.append("token", $("#token").val());

$.ajax({

url:"http://uploadUrl",

type:"POST",

data: formData,

processData:false,

contentType:false,

success:function(response){

//根據返回結果指定界面操作

}

});

});

我們使用了file控件的change來觸發上傳事件,當然你也可以使用某個按鈕來觸發表單提交。提交數據時,我 用到了FormData對象來發送二進制文件,FormData構造函數提供的append()方法,除了直接添加二進制文件還可以附帶一些其它的參數, 作為XMLHttpRequest實例的參數提交給服務端。

使用jquery提供的ajax方法來發送二進制文件,還需要附加兩個參數:

  • processData: false // 不要對data參數進行序列化處理,默認為true
  • contentType: false // 不要設置Content-Type請求頭,因為文件數據是以 multipart/form-data 來編碼

表單上傳和ajax上傳實質是一樣的,只不過表單上傳已經封裝好了,比較淺顯,ajax比較容易控制

上傳與安全

上傳文件時必須做好文件的安全性,除了前端必要的驗證,如文件類型、後綴、大小等驗證,重要的還是要在後臺做安全策略。

這裏我列舉幾個註意點:

    • 後臺需要進行文件類型、大小、來源等驗證
    • 定義一個.htaccess文件,只允許訪問指定擴展名的文件。
    • 將上傳後的文件生成一個隨機的文件名,並且加上此前生成的文件擴展名。
    • 設置上傳目錄執行權限,避免不懷好意的人繞過如圖片擴展名進行惡意攻擊,拒絕腳本執行的可能性。

轉:http://blog.csdn.net/xcymorningsun/article/details/52949848

http://blog.sina.com.cn/s/blog_75a555e40101q8i7.html

https://docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/sending-html-form-data-part-2

WEBAPI獲取數據文件等

技術分享
/// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        public async Task<HttpResponseMessage> PostFormData()
        {
            // 檢查該請求是否含有multipart/form-data 
            if (!Request.Content.IsMimeMultipartContent("form-data"))
            {
                throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
            }
            // 文件保存目錄路徑
            const string saveTempPath = "~/UploadFiles/";
            var dirTempPath = HttpContext.Current.Server.MapPath(saveTempPath);
            // 設置上傳目錄 
            var provider = new MultipartFormDataStreamProvider(dirTempPath);

            try
            {
                // 讀取表單數據 
                await Request.Content.ReadAsMultipartAsync(provider);
                // 顯示所有“鍵-值”對  
                foreach (var key in provider.FormData.AllKeys)
                {
                    foreach (var val in provider.FormData.GetValues(key))
                    {
                        System.Diagnostics.Trace.WriteLine(string.Format("{0}: {1}", key, val));
                    }
                }

                // 以下描述如何獲取文件名    
                //TODO:這樣做直接就將文件存到了指定目錄下,暫時不知道如何實現只接收文件數據流但並不保存至服務器的目錄下,由開發自行指定如何存儲,比如通過服務存到圖片服務器 
                foreach (MultipartFileData file in provider.FileData)
                {
                    System.Diagnostics.Trace.WriteLine(file.Headers.ContentDisposition.FileName);//獲取上傳文件實際的文件名
                    System.Diagnostics.Trace.WriteLine("Server file path: " + file.LocalFileName);//獲取上傳文件在服務上默認的文件名
                }
                return Request.CreateResponse(HttpStatusCode.OK);
            }
            catch (Exception ex)
            {
                return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex);
            }
        }
WEBAPI

ajax

技術分享
受瀏覽器的同源策略限制,JavaSript只能請求本域內的資源。跨域資源共享(Cross-Origin Resource Sharing, CORS)是為解決Ajax技術難實現跨域問題而提出的一個規範,這個規範試著從根本上解決安全的跨域資源共享問題。在此之前,解決此類問題的途徑往往是服務器代理、JSONP等,治標不治本。目前基本所有瀏覽器都已經支持該規範。
一個域是由schema、host、port三者共同組成,與路徑無關。所謂跨域,是指在http://example-foo.com/域上通過XMLHttpRequest對象調用http://example-bar.com/域上的資源。CORS約定服務器端和瀏覽器在HTTP協議之上,通過一些額外HTTP頭部信息,進行跨域資源共享的協商。服務器端和瀏覽器都必需遵循規範中的要求。
CORS把HTTP請求分成兩類,不同類別按不同的策略進行跨域資源共享協商。
1. 簡單跨域請求。
當HTTP請求出現以下兩種情況時,瀏覽器認為是簡單跨域請求:
1). 請求方法是GET、HEAD或者POST,並且當請求方法是POST時,Content-Type必須是application/x-www-form-urlencoded, multipart/form-data或著text/plain中的一個值。
2). 請求中沒有自定義HTTP頭部。
對於簡單跨域請求,瀏覽器要做的就是在HTTP請求中添加Origin Header,將JavaScript腳本所在域填充進去,向其他域的服務器請求資源。服務器端收到一個簡單跨域請求後,根據資源權限配置,在響應頭中添加Access-Control-Allow-Origin Header。瀏覽器收到響應後,查看Access-Control-Allow-Origin Header,如果當前域已經得到授權,則將結果返回給JavaScript。否則瀏覽器忽略此次響應。
2. 帶預檢(Preflighted)的跨域請求。
當HTTP請求出現以下兩種情況時,瀏覽器認為是帶預檢(Preflighted)的跨域請求:
1). 除GET、HEAD和POST(only with application/x-www-form-urlencoded, multipart/form-data, text/plain Content-Type)以外的其他HTTP方法。
2). 請求中出現自定義HTTP頭部。
帶預檢(Preflighted)的跨域請求需要瀏覽器在發送真實HTTP請求之前先發送一個OPTIONS的預檢請求,檢測服務器端是否支持真實請求進行跨域資源訪問,真實請求的信息在OPTIONS請求中通過Access-Control-Request-Method Header和Access-Control-Request-Headers Header描述,此外與簡單跨域請求一樣,瀏覽器也會添加Origin Header。服務器端接到預檢請求後,根據資源權限配置,在響應頭中放入Access-Control-Allow-Origin Header、Access-Control-Allow-Methods和Access-Control-Allow-Headers Header,分別表示允許跨域資源請求的域、請求方法和請求頭。此外,服務器端還可以加入Access-Control-Max-Age Header,允許瀏覽器在指定時間內,無需再發送預檢請求進行協商,直接用本次協商結果即可。瀏覽器根據OPTIONS請求返回的結果來決定是否繼續發送真實的請求進行跨域資源訪問。這個過程對真實請求的調用者來說是透明的。
XMLHttpRequest支持通過withCredentials屬性實現在跨域請求攜帶身份信息(Credential,例如Cookie或者HTTP認證信息)。瀏覽器將攜帶Cookie Header的請求發送到服務器端後,如果服務器沒有響應Access-Control-Allow-Credentials Header,那麽瀏覽器會忽略掉這次響應。
這裏討論的HTTP請求是指由Ajax XMLHttpRequest對象發起的,所有的CORS HTTP請求頭都可由瀏覽器填充,無需在XMLHttpRequest對象中設置。以下是CORS協議規定的HTTP頭,用來進行瀏覽器發起跨域資源請求時進行協商:
1. Origin。HTTP請求頭,任何涉及CORS的請求都必需攜帶。
2. Access-Control-Request-Method。HTTP請求頭,在帶預檢(Preflighted)的跨域請求中用來表示真實請求的方法。
3. Access-Control-Request-Headers。HTTP請求頭,在帶預檢(Preflighted)的跨域請求中用來表示真實請求的自定義Header列表。
4. Access-Control-Allow-Origin。HTTP響應頭,指定服務器端允許進行跨域資源訪問的來源域。可以用通配符*表示允許任何域的javascript訪問資源,但是在響應一個攜帶身份信息(Credential)的HTTP請求時,Access-Control-Allow-Origin必需指定具體的域,不能用通配符。
5. Access-Control-Allow-Methods。HTTP響應頭,指定服務器允許進行跨域資源訪問的請求方法列表,一般用在響應預檢請求上。
6. Access-Control-Allow-Headers。HTTP響應頭,指定服務器允許進行跨域資源訪問的請求頭列表,一般用在響應預檢請求上。
7. Access-Control-Max-Age。HTTP響應頭,用在響應預檢請求上,表示本次預檢響應的有效時間。在此時間內,瀏覽器都可以根據此次協商結果決定是否有必要直接發送真實請求,而無需再次發送預檢請求。
8. Access-Control-Allow-Credentials。HTTP響應頭,凡是瀏覽器請求中攜帶了身份信息,而響應頭中沒有返回Access-Control-Allow-Credentials: true的,瀏覽器都會忽略此次響應。

總結:只要是帶自定義header的跨域請求,在發送真實請求前都會先發送OPTIONS請求,瀏覽器根據OPTIONS請求返回的結果來決定是否繼續發送真實的請求進行跨域資源訪問。所以復雜請求肯定會兩次請求服務端。
View Code

response.setHeader("Access-Control-Allow-Origin", "*");
“Access-Control-Allow-Origin”表示允許跨域訪問,“*”表示允許所有來源進行跨域訪問,這裏也可以替換為特定的域名或ip。
很顯然,這種方式對非網站擁有人員來說是不能做到的。而且此種方式很容易受到CSRF攻擊

beforeSend: function (request) {
request.AddHeader("Access-Control-Allow-Origin", "*");

request.setRequestHeader("Test", "ceshi01");

}

action:

//服務端允許跨域訪問

HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "*");

HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS,DELETE,PUT");

HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "*");

JS 上傳文件和參數

xhr.setRequestHeader("POWERED-BY-MENGXIANHUI", "Approve");

xhr.setRequestHeader("Content-Type", "application/xml");

xhr.onreadystatechange = handler;

技術分享
 /*發送文件 文件以數組的方式傳入 支持多個*/
    importFiles: function (options) {
        var host = webInit.url;
        var xhr = new XMLHttpRequest();
        var iUrl = host + options.url;

        if (true) {
            xhr.open("POST", iUrl, true);
            layerIndexIm = layer.load(2);
            var formData = new FormData();
            formData.append("time", (new Date()).valueOf());
            formData.append("uid", window.sessionStorage.getItem("uid"));
            formData.append("data", JSON.stringify(options.data));

            for (var i = 0, len = options.file.length; i < len; i++) {
                formData.append("file" + i, options.file[i]);
            }

            xhr.onload = function (result) {
                if (result.currentTarget.status == 200) {
                    var resultData = JSON.parse(result.currentTarget.response);
                    if (options.success) {
                        options.success.call(this,resultData);
                    }
                    layer.close(layerIndexIm);
                    if (resultData.code == 0) {
                        // layer.msg( "導入成功。");
                    } else if (resultData.code == -1) {
                        var mes = resultData.msg || "請求失敗,請聯系管理員。";
                        layer.msg(mes);
                    } else {
                        layer.msg(resultData.msg);
                    }
                } else {
                    layer.close(layerIndexIm);
                    layer.msg("請求失敗,請檢查文件後重新導入。");
                }
            };
            xhr.send(formData);
        }
    },
    /*獲取鏈接中的關鍵字的值*/
    getUrlParam: function (name) {
        var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
        var r = window.location.search.substr(1).match(reg);
        if (r != null) return decodeURI(r[2]);
        return null;
    },
    formatDateTime: function (date) {
        if (typeof (date) == object) {
            var y = date.getFullYear();
            var m = date.getMonth() + 1;
            m = m < 10 ? 0 + m : m;
            var d = date.getDate();
            d = d < 10 ? (0 + d) : d;
            return y + - + m + - + d;
        } else {
            return date;
        }

    }
View Code

http://blog.csdn.net/net_lover/article/details/5172522

轉:MVC中的文件上傳