无刷新跨域上传的回调处理解决方案
最近要做一个基于WebApi接口的图片无刷新上传,开始没在意,上传图片嘛,分分钟的事。
结果,图片传上去,的确是分分钟就解决了,但要回调页面的js给img标签赋值,却出现了问题。
原因很容易搞清楚:无刷新上传必须用一个隐藏的iframe来响应上传请求,而这个请求回来的结果,跟当前页不在一个域里面,也就是跨域了。
而总所周知的是,现在的浏览器,对安全规范的实现是越来越严格了,没有任何浏览器支持这种跨域的页面访问。
因此,api里返回的回调js根本访问不到iframe外面去。
经过近两个小时的艰苦卓绝的查证工作,阅读了许多国内外资料,没有一个好的方法,最终都是用专门的插件来完成,比如flash上传等,而我是特别讨厌用插件的。
偶然在国外一篇文章的评论里,有个小伙子随便回了一句,说可以考虑把跨域转为本站,他虽然是随便一说,也没说怎么个转法,转什么,
却给了我一个提示,那就是可以转iframe里的js为本站js。
稍微想下,就觉得这个可行,于是立即动手:
1>先在本站建立一个空的静态页,命名为UploadCallback.html,写入下面的内容
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>test1</title> <script type='text/javascript'> var url = location.href; //取得整个地址栏 var ps = url.split("?") var js = ps.length > 0 ? ps[1] : ''; //alert(decodeURI(js)); eval(decodeURI(js)); </script> </head> <body></body> </html>
2>上传表单代码如下:
<div class="edit-area"> <iframe id="uploadFrame" name="uploadFrame" style="display:none;"></iframe> <div class="edit-line" style="line-height:60px;"> <img class="edit-image left" alt="" src="" id="img_a" /> <div class="edit-title left"></div> <form id="form_a" class="edit-form right" method="post" target="uploadFrame" enctype="multipart/form-data" action="http://192.168.2.112:89/Files/Post"> <input type="hidden" name="ReturnUrl" value="http://192.168.2.111:81/UploadCallback.html" /> <input type="file" name="file_a" onchange="this.parentNode.submit();" /> </form> </div> </div> <script type="text/javascript"> function UploadCallback(result) { console.log(result); $('.edit-image').attr('src', result.Url); } </script>
3>api接收方法代码:(考虑到返回内容的管理,这里没有用标准WebApi 协议,而是用的MVC的Action来模拟)
public class FilesController : Controller { //要存放的文件虚拟路径 private string virtualPath = ConfigurationManager.AppSettings["VirtualImagePath"].ToString(); private string physicPath = ConfigurationManager.AppSettings["PhysicImagePath"].ToString(); [HttpPost] public ContentResult Upload() { //要返回的json对象 List<FilesModels> list = new List<FilesModels>(); var re = "<script type = 'text/javascript'>location.href=\"" + Request["ReturnUrl"] + "?\\\"window.parent.UploadCallback(["; try { //循环遍历上传文件 for (int i = 0; i < Request.Files.Count; i++) { //获取当前时间戳作为文件名 string fileName = Tools.GetTimeStamp(); //获取文件后缀名 var file = Request.Files[i]; var name = file.FileName; string fileSsuffix = name.Substring(name.LastIndexOf('.')); //上传 file.SaveAs(physicsPath + fileName + fileSsuffix); re += "{'key': '" + Request.Files.AllKeys[i] + "','url': '" + virtualPath + fileName + fileSsuffix + "','len': '" + file.ContentLength.ToString() + "','status': " + Response.StatusCode + ",'message': 'Upload Success'},"; } } catch { re += "{'status': '" + Response.StatusCode + "', 'message': 'Upload Failed'}"; } re += "]);\\\"\"</script>"; return Content(re); } }
代码完毕,现在解释下运行原理:
其实很简单,关键在于api返回的js是一个跳转语句,
把当前iframe的页面从api的域跳转到表单指定的ReturnUrl,
也就是前面定义的UploadCallback.html,
并且把真正要执行的回调方法作为参数带过去,
然后这个页面用js对当前url进行截取,获取后面追加的js代码,并用eval执行。
几个注意点:
1>表单提交时要把指定的回调页面的url作为提交项,提交上去
2>api返回的内容要特别注意拼写,因为现在的浏览器都直接禁止了url含有可执行js脚本,因此一定是转成字符串拼接
3>回调页面js截取需要解码并用eval方法执行,因为url取下来的是经过转码的字符串
至此,这个困扰世界的麻烦事,就这么简单的解决了^_^