[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?
The source
A full test application can be downloaded from here http://cid-916198839f3e806c.office.live.com/self.aspx/Public/TestingImageWTF.zip
Web Service
[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
Controls/Widgets/MyPictureAndTextWidget.ascx
<%# 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>
Controls/Widgets/MyPictureWidget.ascx
<%# 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" />
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.
http://channel9.msdn.com/Forums/TechOff/256519-Am-I-crazy-here-but-there-appears-to-be-an-oversight-in-ASPNET
http://forums.asp.net/t/1138135.aspx/1
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".
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!
Try to remove the leading tilde symbol from the ImageUrl value.
Related
i have this aspx code that used to work (on .net 2.0):
<Templates>
<EditForm>
<div>
<dxe:ASPxMemo runat="server" ID="Memo1" Text='<%#Bind("field1")%>' Width="100%" Height="60px" ReadOnly="true" BackColor="#E6E6E6"></dxe:ASPxMemo>
...
Now i'm trying to re-use it in the same context but using .net 3.5, but i got an error. I'll try to translate it:
Can't edit Controls because the control contains blocks of code (like
<% ... %>).
so i tried to do via vb.net code this:
Memo1.Text = #Bind("field1")
but looks like a can't access Memo1 because of protection level
how i can make this work again?
edit: found a tutorial here http://demos.devexpress.com/aspxgridviewdemos/GridEditing/EditFormTemplate.aspx
but the tutorial uses bracket insde Text too!
Solved!
Looks like that ASPxGridView doesn't like <% ... %> code inside the page header.
Some days ago i added in my master page's <head> a simple <% Response.Write() %> and the entire ASPxGridView failed because of that.
Moving that code outside the header resulted in a working ASPxGridView, that was not a problem of .NET framework version!
I am having a bootstrap problem I hope one of you might have a solution to.
I have placed all my JavaScript references in the bottom of my masterpage. This usually works fine, but now I have a ASCX control which needs to add some JavaScript too the footer (initialization of a module). The reason why I can't initialize the module from the master page is because I need some properties from my codebehind file.
In ASP.NET MVC I would have used sections to inject data from a usercontrol to a section in the masterpage, but is this even possible in ASP.NET Webforms 4?
No, that concept of sections in not available in WebForms.
One way to do what you've described is to use the <%= %> syntax and send the values of server properties to the client (HTML output). In your ASCX control you can have the following markup:
<script type="text/javascript">
var clientProperty = <%= MyServerProperty %>;
</script>
ASP.NET WebForms will substitute the value of MyServerProperty above when it renders the page, and then you can access clientProperty as a global variable from the script in the masterpage.
Another approach is to use a Hidden field and set its value on the server. It will be rendered as an <input type="hidden">, whose value you can then get from any script by ID.
A third option is to load the actual client script only from the ASCX control (when it makes sense), rather than put it in the masterpage (when it will be loaded everywhere in the site).
You could add something like this to the bottom of your master page:
<asp:PlaceHolder runat="server" ID="javascriptSection" />
Then in your page's code behind or in a <% %> tag:
var scriptTag = new HtmlGenericControl("script");
scriptTag.Attributes["type"] = "text/javascript";
scriptTag.InnerHtml = #"function () { ... }";
var javascriptSection = this.Page.Master.FindControl("javascriptSection");
if (javascriptSection != null)
javascriptSection.Controls.Add(scriptTag);
If I set Thread Culture and UICulture for one ASPX, after pass for that page, all my aspx that use the same thread(not same request) will have the same Culture?
Because I need to set Culture just for one ASMX
If the culture you set is not read from the browser settings (like it lives in a database) You need to set this on every request.
As described here:
http://msdn.microsoft.com/en-us/library/bz9tc508.aspx
Override the page's 'InitializeCulture' method on every page. A common base class for all your pages comes in really handy here.
I would suggest firing up .NET reflector and see what the default implementation does. It will help clarify what is going on by default.
As this is event is handled on the page level, and not in the Global.asax, I would expect this is re-set. also, as the article describes, this event is Called so early in the page life cycle that capturing User Input requires directly accessing 'Request.Form'.
EDIT: Please try this and See that this must be set in every request. Let me know if you see different results or if I misunderstand your question.
Default.aspx: prints '21.09.2010'
<%# Page Language="C#" %>
<%# Import Namespace="System.Threading" %>
<%# Import Namespace="System.Globalization" %>
<script runat="server">
protected override void InitializeCulture()
{
UICulture = "de-DE";
Culture = "de-DE";
//base.InitializeCulture();
}
</script>
<HTML>
<head>
</head>
<body>
<%= System.DateTime.Now.ToShortDateString()%>
</body>
</HTML>
Default2.aspx: prints '9/21/2010' (my default cluture is es-US)
<%# Page Language="C#" %>
<HTML>
<head>
</head>
<body>
<%= System.DateTime.Now.ToShortDateString()%>
</body>
</HTML>
The order in which you hit these pages does not matter. The results do not change.
One approach people is used is to store this info in a Session variable and use the Session Variables to set the culture ..so logic for this is centerlized.
I'm pretty sure that UICulture, once set, stays for the entire ASP session (which happens independently of whatever session you create for your own application).
Edit: looks like a straightforward summary here: http://quickstarts.asp.net/QuickStartv20/aspnet/doc/localization/localization.aspx
I have an asp.net web site where I'm trying to resolve what looks like a problem with ASP.NET AJAX:
Microsoft JScript runtime error:
Sys.ArgumentTypeException: Object of
type
'Sys.Extended.UI.AccordionBehavior'
cannot be converted to type
'Sys.UI.Behavior'. Parameter name:
instance
I've googled around a lot, and looked at the code presented (see below, no idea what it means) but no luck.
I've tried changing scriptmanger scriptmode to release, and a bunch of other things too. Anyone have any ideas?
Details:
Visual Studio 2010
ASP.NET 4.0
Ajax control toolkit 4.0
jQuery 1.4.2
jQuery UI 1.8.5
JS Code the error occurs in, inside ScriptResource.axd:
dispose:function()
{
var c=this;
b.UI.Behavior.callBaseMethod(c,eb); --------- this line
var d=c._element;
if(d)
{
var f=c.get_name();
if(f)
d[f]=a;
var e=d._behaviors;
Array.remove(e,c);
if(!e.length)
d._behaviors=a;
delete c._element
}
}
Declaration of accordion control:
<%# Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="atlas" %>
(snip)
<atlas:Accordion ID="menu" runat="server" SelectedIndex="0"
ContentCssClass="accordionContent" FadeTransitions="true" FramesPerSecond="30"
TransitionDuration="250" AutoSize="None" Width="270">
</atlas:Accordion>
*Update: *
Added accordion control declaration as requested, is there anything else I can add that could be useful?
Does anyone have any idea about this error at all - am I right in thinking it's MS AJAX not playing nicely with other javascript (probably jQuery UI) on the page?
Are you using update panels on your page at all? If so, remember that once you add controls inside\outside of an update panel, the "scope" of objects included in a postback changes dramatically, as only objects wrapped within an UpdatePanel are included.
If this doesnt help, I apologize, but my experiences with ASP.NET AJAX have added a layer of complexity to the postback model with sites Ive implemented it on.
I'm looking to get rid of the code-behind for a control in my WebForms 3.5 application. Again bitten by the bug of how it's done in MVC, I'd like to get a step closer to this methodology by doing:
<%# Control Language="C#" Inherits="Core.DataTemplate<Models.NewsArticle>" %>
This gives me the parser error you'd expect, so I remembered back to when this was an issue awaiting a fix in the MVC Preview, and changed it to:
<%# Control Language="C#" Inherits="Core.DataTemplate`1[[Models.NewsArticle]]" %>
But this doesn't work either! How is it that the MVC team were able to harness this ability? Was it something special about the MVC project type rather than the latest VS2008 Service Pack?
Short of giving up and requiring future templates to have code-behind files, what are my best options to get this as close to the generic user control method as possible?
Well, it appears like I've managed to do it. After looking at the PageParserFilter implemented by the MVC team for ViewUserControl<T>, I was able to construct something similar for my own DataTemplate<T> purposes. Sweet. I can now use the line:
<%# Control Language="C#" Inherits="Core.DataTemplate<Models.NewsArticle>" %>
And, without any code behind file, it parses! I'll report back if I find that I've broken something else in the process!
With WebForms you lose pretty much everything that makes them useful without a code behind page, because then VS can't auto generate the designer file that holds the actual definitions for all your runat="server" controls.
What you can do is have a common base page class, and make that generic:
public class DataTemplate<T> : Page {
public T Model {get;set;}
}
public partial class MyCodeBehindClass :
DataTemplate<Models.NewsArticle> {
...
}
This would allow all the drag-drop component stuff that WebForms does to work unhindered, while also allowing you to access a strongly typed model on the page:
<%# Control Language="C#" Inherits="MyCodeBehindClass" %>
<% foreach( var item in Model ) { %>
<!-- do stuff -->
<% } %>