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
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:
View:
Hope this helps
EDIT:
I've just realised you're calling the server in your TempData set!
remove this line from your view:
and change your RenderChart to do the following:
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:
Thus invoking the chart method twice. Hope this helps :)
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:
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.
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 ofform
element in the wrong place (in the middle of yourdiv
). 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>
.