Reputation: 7725
[Apologies if my question title does not accurately describe my problem- if you can think of a better title and have the permissions to change this then please feel free to change it!].
I think that I have stumbled upon a minor breaking change between ASP.Net 3.5 and 4.0.
[Edit: I have confirmed that there is a change in behaviour twix 3.5 and 4.0 - see my answer]
Here is the scenario: -
I have a ASP.Net 3.5 web application. I have a trivial user control {appRoot}/Controls/Widgets/MyPictureAndTextWidget.ascx that essentially contains some text and another user control ({appRoot}/Controls/Widgets/MyPicture.ascx).
For the most part, this control is used in the normal fashion - i.e. including it in the mark up of other pages but I have one instance where I need to obtain the HTML to render on the client using Ajax.
The way I achieved this was to write an asmx web service that programmatically created a new Page and dynamically `LoadControl' the user controls and then captured the output from the rendering of the page in a string builder - particulary inelegant but it worked! See bottom for the source.
However, after upgrading the project to Asp.Net 4.0, the above code no longer works as it used to; the image, when rendered has src="../images/xxx.png
(note the '../' which is not wanted).
I have created a little demo app http://cid-916198839f3e806c.office.live.com/self.aspx/Public/TestingImageWTF.zip if you want to run it for yourselves. When you compile the app using 3.5, it works (i.e. you see 2 pictures of a spider on the test page) but when you compile and run under 4.0, you only see 1 spider (the other image has the wrong URL).
The only explanation that I can come up with is that the ResolveClientUrl
method (which the Image control will use in order to work out what is the relative path to the image from the currently executing page) is behaving differently. The fact that the image url is coming out as "../images/xxx.png" means that the Image control 'thinks' it is executing in a page that has a path like '{appRoot}/folder/handler' when running under 4.0 but it thinks it is running in a context '{appRoot}/handler' under 3.5.
I hope this is making sense to you - sorry if I am not describing the problem very clearly or concisely.
Can anyone either tell us how: -
to restore the 3.5 behaviour (without reverting to the 3.5 framework obviously!)
or a better way of generating the HTML in the web service in the first place?
A full test application can be downloaded from here http://cid-916198839f3e806c.office.live.com/self.aspx/Public/TestingImageWTF.zip
[WebMethod]
[ScriptMethod]
public string GetWidgetHtml(int number)
{
var pageHolder = new Page
{
//AppRelativeVirtualPath = "~/" // I tried playing with this but it made no difference!
};
for (int i = 0; i < number; i++)
{
var viewControl = (MyPictureAndTextWidget) pageHolder.LoadControl(@"~/Controls/Widgets/MyPictureAndTextWidget.ascx");
pageHolder.Controls.Add(viewControl);
}
var output = new StringWriter();
HttpContext.Current.Server.Execute(pageHolder, output, false);
StringBuilder sb = output.GetStringBuilder();
string fulloutput = sb.ToString();
return fulloutput;
}
Here are the contents of my user controls
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="MyPictureAndTextWidget.ascx.cs" Inherits="TestingImageWTF.Controls.Widgets.MyPictureAndTextWidget" %>
<%@ Register TagName="Picture" TagPrefix="widget" Src="~/Controls/Widgets/MyPictureWidget.ascx" %>
<div style="background:#EEEEEE; border:1px dashed;">
<h4>My control</h4>
Some text from the widget ....:
<br /><widget:Picture runat="server" />
</div>
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="MyPictureWidget.ascx.cs" Inherits="TestingImageWTF.Controls.Widgets.MyWidget" %>
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
image.ImageUrl = "~/images/spider.png";
}
</script>
<asp:Image ID="image" runat="server" />
Upvotes: 4
Views: 4269
Reputation: 7725
oSo here is at least part if the answer.
Question: Does ResolveClientUrl working differently in ASP.Net 4 and 3.5?
Answer: Yes.
And the change in behaviour (that I know of) is that it treats PathInfo differently.
To demonstrate, make the following page.
<%@ Page Language="C#" AutoEventWireup="true" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<form id="form1" runat="server">
DateTime.Now.Ticks: <%= DateTime.Now.Ticks %>
<br />
<asp:HyperLink runat="server" NavigateUrl="~/PathInfoLinkTest.aspx">This links to ~/PathInfoLinkTest.aspx</asp:HyperLink>
<br />
<asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl="~/PathInfoLinkTest.aspx/foo/bar">This links to ~/PathInfoLinkTest.aspx/foo/bar</asp:HyperLink>
<br />
ResolveClientUrl("~/PathInfoLinkTest.aspx/foo/bar") = <%= ResolveClientUrl("~/PathInfoLinkTest.aspx/foo/bar") %>
</form>
</body>
</html>
And run under .Net4 and .Net 3.5.
You will see that under 3.5:
ResolveClientUrl("~/PathInfoLinkTest.aspx/foo/bar") = 'PathInfoLinkTest.aspx/foo/bar'
whereas under 4.0 you get
ResolveClientUrl("~/PathInfoLinkTest.aspx/foo/bar") = 'bar'
The change seems to be a bug fix in response to the problems that these folk were having.
In essence, the bug in 3.5 is that if you are currently browsing the url http://host/app/page.aspx/foo/bar
and you want to link to http://host/app/page2.aspx
, then the URL as rendered on the client should be ../../page2.aspx
.
Asp.Net 4 gets this correct!
Asp.Net 3.5 doesn't - it outputs the link's url as 'page2.aspx
' (so when clicked, the browser will request the page 'http://host/app/page.aspx/foo/bar/page2.aspx
'. You can see a manifestation of this bug if you run the above page in .Net 3.5 and click on the 2nd hyperlink several times - then have a look in your browser's address bar!
Unfortunately the bug fix broke my code - because my code was relying on the (incorrect) behaviour of .Net 3.5: The web service request always has Pathinfo (the web service method name) and so when the controls render themselves, calls to ResolveClientUrl("~/xxx") (correctly) puts returns "../xxx".
Upvotes: 5
Reputation: 716
I'm not sure about updates to the ResolveClientUrl method, but I do know that they made updates to how controls are rendered between .NET 4.0 and 3.5. You may want to try updating your web.config to include:
<pages controlRenderingCompatibilityVersion="3.5" />
Check out: http://www.asp.net/learn/whitepapers/aspnet4/breaking-changes#0.1__Toc256770141
Also, you might try using RenderControl as follows in your web service:
StringBuilder sb = new StringBuilder();
StringWriter tw = new StringWriter(sb);
HtmlTextWriter hw = new HtmlTextWriter(tw);
control.RenderControl(hw);
return sb.ToString();
Rick Strahl has an article that might be useful: http://www.west-wind.com/weblog/posts/2004/Jun/08/Capturing-Output-from-ASPNet-Pages (may be somewhat dated however...)
Hope this helps!
Upvotes: 2