views:

266

answers:

7

I'm trying to implement something similar to this or this.

I've created a user control, a web service and a web method to return the rendered html of the control, executing the ajax calls via jQuery.

All works fine, but if I put something in the user control that uses a relative path (in my case an HyperLink with NavigateUrl="~/mypage.aspx") the resolution of relative path fails in my developing server.

I'm expecting: http://localhost:999/MyApp/mypage.aspx

But I get: http://localhost:999/mypage.aspx

Missing 'MyApp'...

I think the problem is on the creation of the Page used to load the control:

Page page = new Page();
Control control = page.LoadControl(userControlVirtualPath);
page.Controls.Add(control);
...

But I can't figure out why....

EDIT Just for clarity

My user control is located at ~/ascx/mycontrol.ascx and contains a really simple structure: by now just an hyperlink with NavigateUrl like "~/mypage.aspx". And "mypage.aspx" really resides on the root.

Then I've made up a web service to return to ajax the partial rendered control:

[ScriptService]
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class wsAsynch : System.Web.Services.WebService
{
    [WebMethod(EnableSession = true)]
    public string GetControl(int parma1, int param2)
    {
        /* ...do some stuff with params... */
        Page pageHolder = new Page();

        UserControl viewControl = (UserControl)pageHolder.LoadControl("~/ascx/mycontrol.ascx");
        Type viewControlType = viewControl.GetType();

        /* ...set control properties with reflection... */

        pageHolder.Controls.Add(viewControl);
        StringWriter output = new StringWriter();
        HttpContext.Current.Server.Execute(pageHolder, output, false);

        return output.ToString();
    }
}

The html is correctly rendered, but the relative path in the NavigateUrl of hyperlink is incorrectly resolved, because when I execute the project from developing server of VS2008, the root of my application is

http://localhost:999/MyApp/

and it's fine, but the NavigateUrl is resolved as

http://localhost:999/mypage.aspx

losing /MyApp/ . Of Course if I put my ascx in a real page, instead of the pageHolder instance used in the ws, all works fine.

Another strange thing is that if I set the hl.NavigateUrl = Page.ResolveUrl("~/mypage.aspx") I get the correct url of the page: http://localhost:999/MyApp/mypage.aspx

And by now I'll do that, but I would understand WHY it doesn't work in the normal way. Any idea?

A: 

The tildy pust the path in the root of the app, so its going to produce a the results you are seeing. You will want to use:

NavigateUrl="./whatever.aspx"

EDIT:
Here is a link that may also prove helpful...http://msdn.microsoft.com/en-us/library/ms178116.aspx

AGoodDisplayName
But I want the app path on my url, I need it for my local developing server. The url http://localhost:999/mypage.aspx simply doesn't exists. If I put the ascx on a page all the relative path are resolved as http://localhost:999/MyApp/something.something ... and it's correct.
tanathos
Can you show the mark up for the page registration for the ascx and maybe a simple explaination of your site structure (where do the pages reside, where do the controls reside)? I have a feeling this is becuase the controls/pages are in differnet directories.
AGoodDisplayName
A: 

I find the /MyApp/ root causes all sorts of issues. It doesn't really answer your question 'why is doesn't work the normal way', but do you realize you can get rid of the /MyApp/ and host your website at http:/localhost/...?

Just set Virtual Path in the website properties to '/'.

This clears everything up, unless of course you are trying to host multiple apps on the development PC at the same time.

James Gaunt
the problem is that I've a lot of applications configured for developing... by now I've bypassed this issue using Page.ResolveUrl("~/mypage.aspx") but I really would like to understand WHY :)
tanathos
A: 

It might be that the new page object does not have "MyApp" as root, so it is resolved to the server root as default.

My question is rather why it works with Page.ResolveUrl(...).
Maybe ResolveUrl does some more investigation about the location of the usercontrol, and resolves based on that.

awe
A: 

Weird, I recreated the example. The hyperlink renders as <a id="ctl00_hlRawr" href="Default.aspx"></a> for a given navigation url of ~/Default.aspx. My guess is that it has something to do with the RequestMethod. On a regular page it is "GET" but on a webservice call it is a "POST".

I was unable to recreate your results with hl.NavigateUrl = Page.ResolveUrl("~/mypage.aspx") The control always rendered as <a id="ctl00_hlRawr" href="Default.aspx"></a> given a virtual path. (Page.ResolveUrl gives me "~/Default.aspx")

I would suggest doing something like this to avoid the trouble in the future.

protected void Page_Load(object sender, EventArgs e)
{
    hlRawr.NavigateUrl = FullyQualifiedApplicationPath + "/Default.aspx";
}

public static string FullyQualifiedApplicationPath
{
    get
    {
        //Return variable declaration
        string appPath = null;

        //Getting the current context of HTTP request
        HttpContext context = HttpContext.Current;

        //Checking the current context content
        if (context != null)
        {
            //Formatting the fully qualified website url/name
            appPath = string.Format("{0}://{1}{2}{3}",
            context.Request.Url.Scheme,
            context.Request.Url.Host,
            (context.Request.Url.Port == 80 ? string.Empty : ":" + context.Request.Url.Port),
            context.Request.ApplicationPath);
        }

        return appPath;
    }
}

Regards,

Biff MaGriff
A: 

It is hard to tell what you are trying to achieve without posting the line that actually sets the Url on of the HyperLink, but I think I understand your directory structure.

However, I have never run into a situation that couldn't be solved one way or another with the ResolveUrl() method. String parsing for a temporary path that won't be used in production is not recommended because it will add more complexity to your project.

This code will resolve in any object that inherits from page (including a usercontrol):

Page page = (Page)Context.Handler;
string Url = page.ResolveUrl("~/Anything.aspx");

Another thing you could try is something like this:

Me.Parent.ResolveUrl("~/Anything.aspx");

If these aren't working, you may want to check your IIS settings to make sure your site is configured as an application.

NightOwl888
A usercontrol don't interit from Page !? And calling Parent on a Page object returns itself so thats not needed.
BurningIce
@BurningIce - Thanks for spotting that. I meant to use the Parent of the usercontrol. I updated the code example with the correction.
NightOwl888
but still your above code with fail, since then handler for the request won't be of type Page but most likely WebServiceHandler, since the document tanathos is requesting most likely has the extension .asmx or .svc (not .aspx).
BurningIce
@BurningIce - Note that the OP specified it was a user control that is failing. You are correct that this code will not work in a web service.
NightOwl888
A: 

The problem is that the Page-class is not intented for instantiating just like that. If we fire up Reflector we'll quickly see that the Asp.Net internals sets an important property after instantiating a Page class an returning it as a IHttpHandler. You would have to set AppRelativeTemplateSourceDirectory. This is a property that exists on the Control class and internally it sets the TemplateControlVirtualDirectory property which is used by for instance HyperLink to resolve the correct url for "~" in a link.

Its important that you set this value before calling the LoadControl method, since the value of AppRelativeTemplateSourceDirectory is passed on to the controls created by your "master" control.

How to obtain the correct value to set on your property? Use the static AppDomainAppVirtualPath on the HttpRuntime class. Soo, to sum it up... this should work;

[WebMethod(EnableSession = true)]
public string GetControl(int parma1, int param2)
{
    /* ...do some stuff with params... */
    var pageHolder = new Page() { AppRelativeTemplateSourceDirectory = HttpRuntime.AppDomainAppVirtualPath };

    var viewControl = (UserControl)pageHolder.LoadControl("~/ascx/mycontrol.ascx");
    var viewControlType = viewControl.GetType();

    /* ...set control properties with reflection... */

    pageHolder.Controls.Add(viewControl);
    var output = new StringWriter();
    HttpContext.Current.Server.Execute(pageHolder, output, false);

    return output.ToString();
}
BurningIce
Great, I like this way!
tanathos