IE9 unexpected behaviour with MVC, IMG tag, Url.Ac

2019-02-24 10:52发布

问题:

I brought this up as an aside when I first ran into this problem Previous Question, I couldn't pin it down in my app because there were too many javascript, css and images kicking about that might have been aggravating the problem.

Now I've done a very simple MVC app with no javascipt, no css and no other images and it seems that IE9 calls my Url.Action twice (fiddler confirms) but Chrome and Firefox both do what I expect.

The app is simple, it contains a model that has one property and a method that returns a memorystream (of an MSChart image). The view displays the image and a color picker, when the view is posted to the controller, the controller sets the color for the chart and creates the view. The chart image is displayed by calling a controller RenderChart action via Url.Action, the MemoryStream is passed from the view to the RenderImage action via TempData. This works fine for a GET, but when it's a POST IE9 always requests RenderChart twice and the second time round, TempData has been removed. The problem can be resolved by 'resetting' the TempData in the RenderChart action (commented out line) but that is so hackariffic that it clearly isn't an answer that you'd trust.

I'm not looking for alternatives I already have alternatives but....can anyone explain this behaviour?

Here's the model

   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;
            }
        }
    }

Here's the view

@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" />
}

And here's the controller

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");
        }
    }

And the resulting HTML looks like...(colors list truncated)

<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>

And the Fiddler output looks like this Fiddler when it works (in compatibility mode)

Fiddler Header - Works OK

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 Header Failure

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

And here's some timings for fiddler (post / imageOK / image failed)

Notice that the failed one doesn't start until well after the post is completed. makes me wonder if there isn't some kind of extra thread somewhere that thinks the IMG tag hasn't been fulfilled and decides to go and do it. Mystery indeed.

== 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

回答1:

We had exactly the same problem coincidently using Charts, only difference was ours were custom, but based on MSChart. We now do the following, using a File Content Result instead of an Action Result

Controller:

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");
}

View:

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

Hope this helps

EDIT:

I've just realised you're calling the server in your TempData set!

remove this line from your view:

@{TempData["Chart"] = Model.Chart();

and change your RenderChart to do the following:

public FileContentResult RenderChart()
{         
    return File(new ChartModel().Chart().ToArray(), "image/jpeg");
}

The reason is, when you're actually rendering your view, you're calling the method in the Img tag, but you're also calling it when setting the temp data:

@{TempData["Chart"] = Model.Chart();

Thus invoking the chart method twice. Hope this helps :)



回答2:

If your view is copy/pasted from when you were having this problem, there is a mismatch of } characters in there.

The reason this is relevant is that the Html.BeginForm is being closed early and will in fact render the closing tag of form element in the wrong place (in the middle of your div). This in turn, creates an invalid HTML situation where the browser needs to determine the best course of action to parse and construct a DOM tree to show you the page.

Internet Explorer (at least older versions and compatibility modes) would in some cases resolve this by duplicating nodes in the DOM. Firefox, Chrome and Safari do things a little smarter/differently.

It could be that the result you are seeing is because IE is creating two <img> elements in its DOM and then requesting the image twice from your script.

This could be confirmed by opening the page in IE9, hitting F12 to open the Developer Tools and then browsing the DOM looking for an extra <img>.



回答3:

I'm not sure why IE9 is requesting the image twice, but I do have a tip which may help and feel less hackarific:

You are generating the Chart image when the HTML is rendered, rather than when the image is actually requested. If you changed your RenderChart action to accept the colour parameter, and render the image then instead, without using TempData, a number of things would become easier:

  • Your RenderAction method no longer relies on SessionState storage for the TempData image
  • This makes RenderAction easier to test
  • This removes the assumption that the call to /Chart/RenderChart from the img tag will definitely be from the correct page (it's an edge-case threading scenario, sure, but it's possible)
  • Applying a short OutputCache to the RenderAction would prevent multiple images being generated in duplicate for the same colour within the caching period, even if they are requested twice

Since the Url for the image is now more RESTful - i.e. the unique resource identifier identifies the unique resource, rather than relying on a hidden TempData value, you can also change your form to be a GET request, avoiding the "resubmit form" browser alert, or you could even just change the img src via javascript and do away with the form altogether.