views:

2218

answers:

5

Can anyone explain to why Server.Execute() is requiring my rendered UserControls to contain <form> tags (or alternately, what I am doing wrong that is making Server.Execute() require form tags in my UserControls)?

I have created an ASMX service to dynamically load UserControls via JQuery+JSON as follows:

ControlService.asmx

<%@ WebService Language="C#" CodeBehind="ControlService.asmx.cs" Class="ManagementConcepts.WebServices.ControlService" %>

ControlService.cs

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.Web.Script.Services.ScriptService]
public class ControlService : System.Web.Services.WebService
{
    private string GetControl(String controlName, String ClassId)
    {
        Page page = new Page();
        UserControl ctl = (UserControl)page.LoadControl(controlName);

        page.Controls.Add(ctl);
        StringWriter writer = new StringWriter();
        HttpContext.Current.Server.Execute(page, writer, false);
        return writer.ToString();
    }
    [WebMethod]
    [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
    public string GetSimpleControl(string ClassId)
    {
        return GetControl("SimpleControl.ascx", ClassId);
    }
}

I load the control into a page via the following bit of JQuery which replaces a with the id ContentPlaceholder with the HTML returned from the service:

JQueryControlLoadExample.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="JQueryControlLoadExample.aspx.cs" Inherits="ControlService_Prototype._Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>ControlService Prototype</title>
</head>
<body>
    <form id="theForm" runat="server" action="JQueryControlLoadExample.aspx">
        <asp:ScriptManager ID="ScriptManager1" runat="server" EnablePageMethods="true" >
            <Scripts>
                <asp:ScriptReference NotifyScriptLoaded="true" Path="~/Scripts/jquery-1.3.2.js" />
            </Scripts>
        </asp:ScriptManager>
        <div>
        <asp:HiddenField runat="server" ID="hdncourse"/>
        <asp:HiddenField runat="server" ID="hdnTargetContent" Value="GetSimpleControl"/>
        <div runat="server" id="ContentPlaceholder" class="loading"></div>
        </div>
        <script type="text/javascript">
            $(document).ready(function() {
                var servicemethod = document.getElementById("hdnTargetContent").value;
                $.ajax({
                type: "POST",
                    url: "ControlService.asmx/" + servicemethod,
                    data: "{'ClassId':'"+document.getElementById("hdncourse").value+"'}",
                    contentType: "application/json; charset=utf-8",
                    dataType: "json",
                    success: function(msg) {
                        $('#ContentPlaceholder').html(msg.d);
                    }
                });
            });
        </script>
    </form>
</body>
</html>

This works with one huge caveat. If I don't define a form inside the .ascx control's markup then HttpContext.Current.Server.Execute() throws an HttpException with the following message:

Control 'hdnspecialoffer' of type 'HiddenField' must be placed inside a form tag with runat=server.

SimpleControl.ascx

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="SimpleControl.ascx.cs" Inherits="ControlService_Prototype.UserControls.SimpleControl" %>
    <asp:HiddenField runat="server" ID="hdnspecialoffer"/>

When I added a form tag to the ascx control to work around this, the form would render, but the renderer rewrites the form tag in the control so that it POSTs back to the ASMX service instead of the form defined in the aspx page.

I googled around and discovered Scott Guthrie's excellent ViewManager example. I don't see anything fundamentally different from what he did there, which leads me to believe that what I am doing ought to work.

A: 

You could change your GetControl() method as follows:

private string GetControl(String controlName, String ClassId)
{
    Page page = new Page();
    StringWriter writer = new StringWriter();
    page.Server.Execute(controlName, writer, false);
    return writer.ToString();
}
ichiban
System.Web.UI.Page doesn't have any Execute() methods as far as I can tell. Do you mean `page.Server.Execute(controlName, writer, false)`?
Jeff Leonard
Sorry. that's what I meant.
ichiban
Unfortunately, that doesn't work for UserControls (.ascx). page.Server.Execute() only works for .aspx pages.
Jeff Leonard
A: 

Instead of using an asp.net hidden control on your user control, just use a regular html hidden input with the code tags <% %> to fill the data like this:

<input id="Hidden1"  type="hidden" value="<%= text %>"/>

"text" is a public variable in the code behind file.

This worked for me and didn't require a form with runat="server".

orandov
Your idea won't work in this case. My example invokes the web service from the browser and replaces the hidden field with the returned content, in Javascript, at the client. <% %> is processed at the server.
Jeff Leonard
+4  A: 

Looks like the answer was buried in the comments for the ViewManager

You'll want a class that inherits from Page and overrides the check for server controls not in a form

public class FormlessPage : Page
{
 public override void VerifyRenderingInServerForm(Control control)
 {
 }
}

Then when you render the control, use

Page page = new FormlessPage();
UserControl ctl = (UserControl)page.LoadControl(controlName);
//etc

I'm assuming you'll lose the ability to have events firing from any controls rendered this way.

Bela
I'm leaning towards accepting this answer, as it rings true, but I want to try it out first (which won't be for a few days).
Jeff Leonard
A: 
    <System.Web.Services.WebMethod()> _
Public Shared Function GetDetails(ByVal filename As String) As String
 Dim page As Page = New Page()
 Dim ctl As recDetails = CType(page.LoadControl("~/Controles/recDetails.ascx"), recDetails)
 ctl.FileName = filename

 page.EnableEventValidation = False
 Dim _form As New HtmlForm()
 page.Controls.Add(_form)
 _form.Controls.Add(ctl)

 Dim writer As New System.IO.StringWriter()
 HttpContext.Current.Server.Execute(page, writer, False)
 Dim output As String = writer.ToString()
 writer.Close()
 Return output
End Function

You add the Form dinamically

Javier Sanchez
A: 
<System.Web.Services.WebMethod()> _
Public Shared Function GetDetails(ByVal filename As String) As String
 Dim page As Page = New Page()
 Dim ctl As recDetails = CType(page.LoadControl("~/Controles/recDetails.ascx"), recDetails)
 ctl.FileName = filename

 page.EnableEventValidation = False
 Dim _form As New HtmlForm()
 page.Controls.Add(_form)
 _form.Controls.Add(ctl)

 Dim writer As New System.IO.StringWriter()
 HttpContext.Current.Server.Execute(page, writer, False)
 Dim output As String = writer.ToString()
 writer.Close()
 Return output
End Function

You add the Form dinamically

Jfsanchez2k