转:MVC中的文件上传

 

上传文件与与上传数据区别

上传数据主要指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

原文地址:https://www.cnblogs.com/love201314/p/7474943.html