1

I just want to create a test application to dynamically parse controls. I added new Page().ParseControl. I am getting,

System.ArgumentNullException
Value cannot be null.
Parameter name: virtualPath

at System.Web.VirtualPath.Create(String virtualPath, VirtualPathOptions options) 
at System.Web.UI.TemplateControl.ParseControl(String content) 

Also tried BuildManager.CreateInstanceFromVirtualPath but it throws null exception.

10
  • I am doing new Page().ParseControl Commented Apr 3, 2015 at 17:01
  • You need to use this.Page.ParseControl instead. Commented Apr 3, 2015 at 17:03
  • @MichaelLiu, I am using this code out WebForms. Commented Apr 3, 2015 at 18:06
  • ParseControl has serious limitations. It cannot do exactly what ASP.NET does in an HTTP context. Plus, your stack is incomplete, there's some "distance" between control parsing and VirtualPath handling. What are you doing exactly? What do you pass to the ParseControl method? Try to eliminate things in there step by step to determine what fails. Commented Apr 4, 2015 at 7:22
  • @SimonMourier, I am doing new Page().ParseControl Nothing else Commented Apr 4, 2015 at 16:04

1 Answer 1

4
+50

The exception is actually coming from the internal class System.Web.VirtualPath:

// Default Create method
public static VirtualPath Create(string virtualPath) {
    return Create(virtualPath, VirtualPathOptions.AllowAllPath);
}

...

public static VirtualPath Create(string virtualPath, VirtualPathOptions options) {
    ...

    // If it's empty, check whether we allow it
    if (String.IsNullOrEmpty(virtualPath)) {
        if ((options & VirtualPathOptions.AllowNull) != 0) // <- nope
            return null;

        throw new ArgumentNullException("virtualPath"); // <- source of exception
    }

    ...
}

System.Web.UI.Page inherits ParseControl() from System.Web.UI.TemplateControl. So you are ultimately calling...

public Control ParseControl(string content) {
    return ParseControl(content, true);
}

public Control ParseControl(string content, bool ignoreParserFilter) {
    return TemplateParser.ParseControl(content, VirtualPath.Create(AppRelativeVirtualPath), ignoreParserFilter);
}

For reference (from VirtualPathOptions):

internal enum VirtualPathOptions
{
    AllowNull = 1,
    EnsureTrailingSlash = 2,
    AllowAbsolutePath = 4,
    AllowAppRelativePath = 8,
    AllowRelativePath = 16,
    FailIfMalformed = 32,
    AllowAllPath = AllowRelativePath | AllowAppRelativePath | AllowAbsolutePath,
}

Since VirtualPathOptions.AllowAllPath is passed to VirtualPath.Create()...

return Create(virtualPath, VirtualPathOptions.AllowAllPath);

this...

options & VirtualPathOptions.AllowNull

... evaluates to 0, and an ArgumentNullException will be thrown


Please consider the following example.

Default.aspx:

<%@ Page Title="Home Page" Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebFormsTestBed._Default" %>

<html>
<head>
    <title></title>
</head>
<body>
    <form id="formMain" runat="server">
        <asp:Label ID="lblResults" runat="server"></asp:Label>
    </form>
</body>
</html>

Default.aspx.cs:

using System;
using System.Web;
using System.Web.UI;

namespace WebFormsTestBed {
    public partial class _Default : Page {
        protected void Page_Load(object sender, EventArgs e) {
            Control ctl;
            var page = HttpContext.Current.Handler as Page;

            // First, using `HttpContext.Current.Handler as Page`,
            // - already has `AppRelativeVirtualPath` set to `~\Default.aspx`
            if (page != null) {
                ctl = page.ParseControl(@"<asp:TextBox ID=""txtFromCurrentHandler"" runat=""server"" Text=""Generated from `HttpContext.Current.Handler`""></asp:TextBox>");

                if (ctl != null) lblResults.Text = "Successfully generated control from `HttpContext.Current.Handler`";
            }

            // Next, using `new Page()`, setting `AppRelativeVirtualPath`
            // - set `AppRelativeVirtualPath` to `~\`
            var tmpPage = new Page() {
                AppRelativeVirtualPath = "~\\"
            };

            ctl = tmpPage.ParseControl(@"<asp:TextBox ID=""txtFromNewPageWithAppRelativeVirtualPathSet"" runat=""server"" Text=""Generated from `new Page()` with `AppRelativeVirtualPath` set""></asp:TextBox>", true);

            if (ctl != null)
                lblResults.Text +=
                    string.Format("{0}Successfully generated control from `new Page()` with `AppRelativeVirtualPath` set",
                                  lblResults.Text.Length > 0 ? "<br/>" : "");

            // Last, using `new Page()`, without setting `AppRelativeVirtualPath`
            try {
                ctl = new Page().ParseControl(@"<asp:TextBox ID=""txtFromNewPageWithoutAppRelativeVirtualPathSet"" runat=""server"" Text=""Generated from `new Page()` without `AppRelativeVirtualPath` set""></asp:TextBox>", true);

                if (ctl != null)
                    lblResults.Text +=
                        string.Format("{0}Successfully generated control from `new Page()` without `AppRelativeVirtualPath` set",
                                      lblResults.Text.Length > 0 ? "<br/>" : "");
            } catch (ArgumentNullException) {
                lblResults.Text +=
                    string.Format("{0}Failed to generate control from `new Page()` without `AppRelativeVirtualPath` set",
                                  lblResults.Text.Length > 0 ? "<br/>" : "");
            }
        }
    }
}

You can read about this line...

var page = HttpContext.Current.Handler as Page;

at this here.


Result:

    Successfully generated control from `HttpContext.Current.Handler` 
    Successfully generated control from `new Page()` with `AppRelativeVirtualPath`
    Failed to generate control from `new Page()` without `AppRelativeVirtualPath` set

Example usage from WebForms project

This hack is based on this SO answer, which is based on attaching a non-WebForms test harness to a WebForms application.

Starting with the WebForms project that would have been created for the example above, add a new WinForms project.

For the simplest case, we will just modifying Program.cs:

using System;
using System.IO;
using System.Linq;
using System.Web.Hosting;
using System.Windows.Forms;
using System.Web.UI;

namespace WinFormsTestBed {
    public class AppDomainUnveiler : MarshalByRefObject {
        public AppDomain GetAppDomain() {
            return AppDomain.CurrentDomain;
        }
    }

    internal static class Program {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        private static void Main() {
            var appDomain = ((AppDomainUnveiler)ApplicationHost.CreateApplicationHost(
                    typeof(AppDomainUnveiler), "/", Path.GetFullPath("../../../WebFormsTestBed")))
                .GetAppDomain();

            try {
                appDomain.DoCallBack(StartApp);
            } catch (ArgumentNullException ex) {
                MessageBox.Show(ex.Message);
            } finally {
                AppDomain.Unload(appDomain);
            }
        }

        private static void StartApp() {
            var tmpPage = new Page() {
                AppRelativeVirtualPath = "~/Default.aspx"
            };
            var ctl = tmpPage.ParseControl(@"<asp:TextBox ID=""txtFromNewPageWithAppRelativeVirtualPathSet"" runat=""server"" Text=""Generated from `new Page()` with `AppRelativeVirtualPath` set""></asp:TextBox>");

            ctl = ctl == null ||
                  (ctl = ctl.Controls.OfType<System.Web.UI.WebControls.TextBox>().FirstOrDefault()) == null
                ? null
                : ctl;

            MessageBox.Show(ctl == null ? "Failed to generate asp:TextBox"  : "Generated asp:TextBox with ID = " + ctl.ID);
        }
    }
}

You will need to add a reference to System.Web to the WinForms project and make the WebForms project dependent on the WinForms project (this dependency isn't technically necessary, I'll explain below).

You will end up with the following:

enter image description here enter image description here

Create a post-build event in the WinForms project, which will copy the the WinForms output to the WebForms /bin.

xcopy /y "$(ProjectDir)$(OutDir)*.*" "$(ProjectDir)..\WebFormsTestBed\bin\"

Set the WinForms project as the startup project and run it. If you set everything up correctly you should see:

enter image description here

What this does is creates an AppDomain, which is based on the WebForms project, but in the execution context of the WinForms project, and it provides a method for firing a callback method from the WinForms project within the scope of the newly created AppDomain. This will allow you to handle VirtualPath issues correctly within the WebForms project without worrying about the details of mocking up path variables and such.

When the AppDomain is created, it needs to be able to find all resources in its path, which is why the post-build event was created to copy compiled WinForms files to the WebForms /bin folder. This is why the "dependency" is set up from the WebForms project to the WinForms project in the image above.

In the end I don't know how helpful that will be for you. There might be a way to get this all into a single project, or two projects. I'm not going to spend any more time on this without more detail as to why or how you would be using this.

NOTE: the ctl returned from ParseControl() is now a wrapper, with a Controls collection that actually contains the asp:TextBox - I haven't bothered to figure out why yet


Another Alternative

Instead of keeping a dummy WebForms project around, you could try mocking up the AppDomain entirely, so that setting the AppRelativeVirtualPath on the new Page() does not result in...

System.Web.HttpException The application relative virtual path '~/' cannot be made absolute, because the path to the application is not known.

To go about doing this you'll probably want to start by referring back to the source used by the SO answer that I cited above. The SO answer I cited is actually a workaround for this method, which is why I suggested that first, but it requires a valid WebForms project on the same host as the WinForms project.

Sign up to request clarification or add additional context in comments.

6 Comments

Tried that before I am getting System.Web.HttpException The application relative virtual path '~/' cannot be made absolute, because the path to the application is not known. Note I am using this outside web-from.
@user960567 Please see the addition in regards to using ParseControl outside a WebForms project
Thanks will check when I get some time. Thanks anyway
I'm trying to do something similar: Using ParseControl "standalone" without an ASP.NET Page object. I want to parse some ASP.NET-markup und dump it back to an HTML string. The solution works, but events like "OnLoad" are NOT called for user controls. Is there a trick to invoke those with such a Page "dummy"?
@Tobias81 The control that is generated from ParseControl has no idea how it would be used/interpretted within the ASP.NET WebForm lifecycle. I haven't worked with WebForms for about a year, and don't plan on researching your question (sorry). If I was you, I would start by trying to add that generated control to the ControlCollection of a Page and allowing it to run its life cycle. If you can do that, then you extract the critical markup from the Page.Response. But that seems like it would require the entire ASP.NET engine, and would therefore not be very "standalone". Good luck!
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.