IE9在使用MVC、IMG标签、Url.Action和TempData时出现了意外行为。

3
当我第一次遇到这个问题时,我把它作为一个附带说明提出来(之前的问题),但我无法确定我的应用程序是否存在问题,因为有太多的JavaScript、CSS和图像可能会加重问题。
现在,我做了一个非常简单的MVC应用程序,没有javascript、css和其他图像,似乎IE9调用了我的Url.Action两次(fiddler确认),但Chrome和Firefox都按照我期望的做了。
这个应用程序很简单,它包含一个具有一个属性的模型和一个返回Memorystream(MSChart图像)的方法。视图显示图像和颜色选择器,当视图被提交到控制器时,控制器设置图表的颜色并创建视图。通过调用控制器RenderChart操作来显示图表图像,MemoryStream通过TempData从视图传递到RenderImage操作。这对于GET来说很好,但是当它是POST时,IE9总是请求RenderChart两次,第二次,TempData已经被删除。可以通过在RenderChart操作中“重置”TempData(已注释的行)来解决这个问题,但这显然不是一个你可以信任的答案。
我不是在寻找替代方案,我已经有了替代方案,但...有人能够解释这种行为吗?
以下是模型:
   public class ChartModel
    {
        public ChartModel()
        {
            this.ChartColor = Color.Green;
        }
        public ChartModel(Color color)
        {
            this.ChartColor = color;
        }
        public Color ChartColor { get; set; }
        public MemoryStream Chart()
        {
            Chart chart = new Chart();
            chart.Height = 250;
            chart.Width = 450;
            chart.ImageType = ChartImageType.Jpeg;
            chart.RenderType = RenderType.BinaryStreaming;
            chart.BackColor=ChartColor;

            chart.BorderlineDashStyle = ChartDashStyle.Solid;
            chart.BackGradientStyle = GradientStyle.TopBottom;
            chart.BorderlineWidth = 2;
            chart.BorderlineColor = Color.Blue;
            chart.BorderSkin.SkinStyle = BorderSkinStyle.Emboss;
            ChartArea ca = chart.ChartAreas.Add("Default");
            ca.BackColor = Color.Transparent;
            ca.AxisX.IsMarginVisible = false;

            Series series = chart.Series.Add("Browser/Gets");
            series.ChartType = SeriesChartType.Bar;
            string[] browsers = new string[]{"IE9","Chrome","FireFox"};
            int[] gets = new int[]{2,1,1};
            series.Points.DataBindXY(browsers, gets);

            using (MemoryStream memStream = new MemoryStream())
            {
                chart.SaveImage(memStream, ChartImageFormat.Jpeg);
                return memStream;
            }
        }
    }

这里是视图

@model TestChart.Models.ChartModel          
@{
    ViewBag.Title = "Chart";
}
<h2>Chart</h2>
@using (Html.BeginForm("Index", "Chart"))
{
    @Html.DropDownListFor(m => m.ChartColor, new SelectList(Enum.GetNames(typeof(System.Drawing.KnownColor))))
    <br />
    <div>
        <div>
            <br />
            @{TempData["Chart"] = Model.Chart();
            }
            <img alt="Chart" src="@Url.Action("RenderChart", "Chart")" />
        </div>
    </div>
    <input type="submit" value="Post" />
}

以下是控制器代码:

public class ChartController : Controller
    {
        public ActionResult Index( string colorName = "White")
        {
            ChartModel model;
            model = new ChartModel(Color.FromName(colorName));
            return View(model);
        }

        [HttpPost]
        public ActionResult Index(ChartModel model)
        {
            return RedirectToAction("Index", new { colorName = model.ChartColor.Name });
        }

        public FileContentResult RenderChart()
        {
            MemoryStream ms = TempData["Chart"] as MemoryStream;
            // TempData["Chart"] = ms; //uncomment this line to get IE9 to work - odd indeed
            return File(ms.ToArray(), "image/jpeg");
        }
    }

最终生成的HTML如下所示...(颜色列表已缩短)

<form action="/Chart" method="post">
<select data-val="true" data-val-required="The ChartColor field is required." id="ChartColor" name="ChartColor"> <option>ActiveBorder</option>
:
<option>MenuHighlight</option>
</select>    <br />
    <div>
        <div>
            <br />
            <img alt="Chart" src="/Chart/RenderChart" />
        </div>
    </div>
    <input type="submit" value="Post" />
</form>
</body>
</html>

以下是Fiddler的输出结果: Fiddler 在兼容模式下工作的Fiddler Fiddler working

Fiddler头部 - 正常运行

GET /Chart/Index/WindowFrame HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Referer: http://localhost:54307/Chart/Index/Menu
Accept-Language: zh,es;q=0.9,en-GB;q=0.7,de-DE;q=0.6,fr-FR;q=0.4,nl;q=0.3,fr-CA;q=0.1
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)
Accept-Encoding: gzip, deflate
Host: localhost:54307
Connection: Keep-Alive
Pragma: no-cache
Cookie: ASP.NET_SessionId=biv2pyhxucudsg3aqsvv3jbs

GET /Chart/RenderChart HTTP/1.1
Accept: image/png, image/svg+xml, image/*;q=0.8, */*;q=0.5
Referer: http://localhost:54307/Chart/Index/WindowFrame
Accept-Language: zh,es;q=0.9,en-GB;q=0.7,de-DE;q=0.6,fr-FR;q=0.4,nl;q=0.3,fr-CA;q=0.1
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)
Accept-Encoding: gzip, deflate
Host: localhost:54307
Connection: Keep-Alive
Cookie: ASP.NET_SessionId=biv2pyhxucudsg3aqsvv3jbs

Fiddler头部错误
GET /Chart/Index/Transparent HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Referer: http://localhost:54307/Chart/Index/WindowFrame
Accept-Language: zh,es;q=0.9,en-GB;q=0.7,de-DE;q=0.6,fr-FR;q=0.4,nl;q=0.3,fr-CA;q=0.1
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)
Accept-Encoding: gzip, deflate
Host: localhost:54307
Connection: Keep-Alive
Pragma: no-cache
Cookie: ASP.NET_SessionId=biv2pyhxucudsg3aqsvv3jbs

GET /Chart/RenderChart HTTP/1.1
Accept: image/png, image/svg+xml, image/*;q=0.8, */*;q=0.5
Referer: http://localhost:54307/Chart/Index/Transparent
Accept-Language: zh,es;q=0.9,en-GB;q=0.7,de-DE;q=0.6,fr-FR;q=0.4,nl;q=0.3,fr-CA;q=0.1
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)
Accept-Encoding: gzip, deflate
Host: localhost:54307
Connection: Keep-Alive
Cookie: ASP.NET_SessionId=biv2pyhxucudsg3aqsvv3jbs

以下是Fiddler的一些时间记录(发布/图片成功/图片失败)

请注意,失败的记录直到发布完成后很久才开始。这让我想知道是否有某种额外的线程认为IMG标签尚未实现并决定去实现它。确实是个谜。

== FLAGS ==================
BitFlags: [None] 0x0
X-RESPONSEBODYTRANSFERLENGTH: 5627
X-PROCESSINFO: iexplore:3100
X-CLIENTIP: 127.0.0.1
X-HOSTIP: ::1
X-EGRESSPORT: 62803
X-CLIENTPORT: 62801

== TIMING INFO ============
ClientConnected:    21:23:58.866
ClientBeginRequest: 21:23:58.866
ClientDoneRequest:  21:23:58.867
Determine Gateway:  0ms
DNS Lookup:         0ms
TCP/IP Connect: 1ms
HTTPS Handshake:    0ms
ServerConnected:    21:23:58.868
FiddlerBeginRequest:    21:23:58.868
ServerGotRequest:   21:23:58.868
ServerBeginResponse:    21:23:58.928
ServerDoneResponse: 21:23:58.928
ClientBeginResponse:    21:23:58.928
ClientDoneResponse: 21:23:58.928

== FLAGS ==================
BitFlags: [None] 0x0
X-RESPONSEBODYTRANSFERLENGTH: 17612
X-PROCESSINFO: iexplore:3100
X-CLIENTIP: 127.0.0.1
X-HOSTIP: ::1
X-EGRESSPORT: 62804
X-CLIENTPORT: 62802

== TIMING INFO ============
ClientConnected:    21:23:58.866
ClientBeginRequest: 21:23:59.001
ClientDoneRequest:  21:23:59.001
Determine Gateway:  0ms
DNS Lookup:         0ms
TCP/IP Connect: 0ms
HTTPS Handshake:    0ms
ServerConnected:    21:23:59.002
FiddlerBeginRequest:    21:23:59.002
ServerGotRequest:   21:23:59.002
ServerBeginResponse:    21:23:59.012
ServerDoneResponse: 21:23:59.012
ClientBeginResponse:    21:23:59.012
ClientDoneResponse: 21:23:59.012

== FLAGS ==================
BitFlags: [None] 0x0
X-RESPONSEBODYTRANSFERLENGTH: 7996
X-PROCESSINFO: iexplore:3100
X-CLIENTIP: 127.0.0.1
X-HOSTIP: ::1
X-EGRESSPORT: 62807
X-CLIENTPORT: 62805

== TIMING INFO ============
ClientConnected:    21:23:59.062
ClientBeginRequest: 21:23:59.063
ClientDoneRequest:  21:23:59.063
Determine Gateway:  0ms
DNS Lookup:         0ms
TCP/IP Connect: 0ms
HTTPS Handshake:    0ms
ServerConnected:    21:23:59.063
FiddlerBeginRequest:    21:23:59.063
ServerGotRequest:   21:23:59.064
ServerBeginResponse:    21:24:01.597
ServerDoneResponse: 21:24:01.597
ClientBeginResponse:    21:24:01.597
ClientDoneResponse: 21:24:01.598

如果您通过http://validator.w3.org/#validate_by_input运行生成的HTML,会发生什么情况?问题是在IIS还是Cassini中发生的?如果使用www.yourwebsite.name而不是localhost:61877,问题是否消失?它是否在兼容模式下运行(https://dev59.com/9nE85IYBdhLWcg3wgDvd#2745477)?您能否向我们展示2个请求(成功和失败)的Fiddler请求标头? - mjwills
另外,您是否正在运行IE9 RTM(http://www.sadev.co.za/content/internet-explorer-9-breaks-localhost)? - mjwills
@mjwills - HTML验证OK - 两个警告,一个是HTML5,另一个是假设UTF8字符编码。很抱歉我没有任何地方可以从真正的URL中尝试它。兼容模式没有开启,但当我打开它时一切都正常。IE9是RTM V.9.0.8113.16421(9.0.3)。成功的Fiddler马上就会被添加。 - K. Bob
如果您将对JPG的引用更改为不同的文件格式,会发生什么?我还对失败情况下的引荐者是/Chart/Index/Transparent感兴趣。http://expression.microsoft.com/en-us/dd835379#rendmodemetatags可以帮助您强制兼容模式。 - mjwills
@mjwills - 转换为PNG没有任何改变。我会更仔细地查看引荐者的事情,因为我通常只是随意选择颜色而不太注意。 - K. Bob
@mjwills - 推荐人看起来没问题。推荐人是发布的页面,正如您所期望的那样。 - K. Bob
3个回答

4
我们遇到了与使用图表有关的完全相同的问题,唯一的区别是我们的图表是基于MSChart自定义的。 现在我们采用以下方式,使用文件内容结果而不是动作结果
控制器:
public FileContentResult GetGraph(int id)
{
    var image = Resolve<CountryModel>().Load(id).Graph; //gets our Bitmap object
    image.Save(HttpContext.Response.OutputStream, ImageFormat.Jpeg);
    var converter = new ImageConverter();

    return new FileContentResult((byte[])converter.ConvertTo(image, typeof(byte[])), "image/jpeg");
}

查看:

<img src="@Url.Action("GetGraph", "Country", new {Id = Model.CountryId})" />

希望这能帮到你。
编辑:
我刚才意识到你在 TempData 中调用了服务器!
从你的视图中删除这行代码:
@{TempData["Chart"] = Model.Chart();

并将您的RenderChart更改为执行以下操作:
public FileContentResult RenderChart()
{         
    return File(new ChartModel().Chart().ToArray(), "image/jpeg");
}

原因是,当您实际呈现视图时,在Img标签中调用该方法,但是在设置临时数据时也会调用它。
@{TempData["Chart"] = Model.Chart();

这样两次调用图表方法。希望这有所帮助 :)

1
好主意(+1)返回真实类型,但可悲的是它并没有解决问题,我仍然会得到两个GET请求到RenderChart,第二个请求失败。我已经编辑了问题以反映FileContentResult并添加了一个Fiddler截图。 - K. Bob
那个解决方案解决了问题,但我已经从我的先前的问题中知道了这一点。在这个例子中至关重要的是,我没有两次调用Model.Chart(),它只在视图中被调用以填充TempData["Chart"]的内存流,在RenderChart中,流从TempData["Chart"]中提取并通过FileContentResult反馈回视图。我认为真正的问题是IE9出了问题,因为Chrome和FireFox以及IE8和IE7都按预期工作。我正在寻找IE9出现问题的原因,而不是寻找更好的显示图像的方法。 - K. Bob

2
如果您的代码是从遇到此问题时复制/粘贴来的,则其中的 }字符不匹配。
这很重要的原因是,Html.BeginForm被提前关闭了,实际上会在错误的位置呈现form元素的关闭标签(在您的div中间)。 这反过来又创建了一个无效的HTML情况,浏览器需要确定解析和构建DOM树以显示页面的最佳方法。
Internet Explorer(至少旧版本和兼容模式)在某些情况下通过复制DOM中的节点来解决此问题。 Firefox、Chrome和Safari做法略有不同且更智能。
可能是因为IE在其DOM中创建了两个<img>元素,然后从您的脚本中请求了该图像两次,所以才出现了您看到的结果。
可以在IE9中打开此页面,按F12打开开发人员工具,然后浏览DOM,找到额外的<img>以确认这一点。

花括号 {} 标签都匹配了,HTML 也看起来没问题。没有什么不对劲的地方。你是否错过了 TempData[] 之前的那个标签? - K. Bob

1

我不确定为什么IE9会请求两次图像,但我有一个提示可以帮助你避免使用hackarific的方法:

当HTML被渲染时,您正在生成Chart图像,而不是在实际请求图像时生成。如果您将RenderChart操作更改为接受颜色参数并在那时呈现图像,而不使用TempData,则许多事情将变得更容易:

  • 您的RenderAction方法不再依赖SessionState存储TempData图像
  • 这使得RenderAction更容易测试
  • 这消除了对/img标记中的/Chart/RenderChart调用一定来自正确页面的假设(这是边缘情况的线程场景,但确实可能发生)
  • 将短OutputCache应用于RenderAction将防止在缓存期内为相同颜色生成重复的多个图像,即使它们被请求两次

由于图像的URL现在更符合RESTful - 即唯一资源标识符标识唯一资源,而不是依赖于隐藏的TempData值,因此您还可以将表单更改为GET请求,避免“重新提交表单”浏览器警报,或者甚至可以通过JavaScript更改img src并完全放弃表单。


谢谢您的关注。根据我的原始问题,我遵循了与您建议类似的路线解决了问题,但这个问题特别想知道是否有人知道为什么IE9会执行两次GET,而Chrome和Firefox则表现出我所期望的行为。有趣的是,IE8模式(通过IE9)可以正常工作。 - K. Bob

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接