最近要做一个基于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" οnchange="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取下来的是经过转码的字符串
至此,这个困扰世界的麻烦事,就这么简单的解决了^_^