无刷新跨域上传的回调处理解决方案

最近要做一个基于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取下来的是经过转码的字符串


至此,这个困扰世界的麻烦事,就这么简单的解决了^_^





原文地址:https://www.cnblogs.com/foren/p/6009080.html