在ASMX网络服务中使用Server.Execute()渲染UserControl时出现问题

15

请问有人能解释一下为什么Server.Execute()需要我的渲染后的UserControls包含<form>标签(或者说,我做错了什么导致Server.Execute()要求我的UserControls中必须有表单标签)吗?

我已经通过JQuery+JSON创建了一个ASMX服务来动态加载UserControls,如下所示:

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);
    }
}

我通过以下的JQuery代码将控件加载到页面中,它会用从服务返回的HTML替换id为ContentPlaceholder的div:

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">

<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>

这个方法有一个很大的限制条件。如果我在 .ascx 控件的标记中没有定义一个表单,那么 HttpContext.Current.Server.Execute() 就会抛出一个 HttpException 异常,并且错误信息如下:

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"/>

当我在ascx控件中添加一个表单标签以解决这个问题时,表单会被渲染,但是渲染器会重写控件中的表单标签,使其POST到ASMX服务而不是在aspx页面中定义的表单。
我进行了谷歌搜索,并发现了Scott Guthrie的出色ViewManager示例。我没有看到任何根本上不同于他所做的事情,这让我相信我所做的应该可以工作。
5个回答

21

看起来答案被埋在ViewManager的评论中。

你需要创建一个类,该类继承自Page并覆盖对于不在表单中的服务器控件的检查。

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

然后当你渲染控件时,使用

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

我假设您将失去以这种方式呈现的任何控件触发事件的能力。


我倾向于接受这个答案,因为它听起来很正确,但我想先试一下(这需要几天时间)。 - Jeff Leonard

1

不要在用户控件上使用asp.net隐藏控件,而是使用普通的HTML隐藏输入和代码标签<% %>来填充数据,如下所示:

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

"

"text"是代码后台文件中的公共变量。

这对我有用,而且不需要一个带有runat="server"的表单。

"

在这种情况下你的想法行不通。我的例子在浏览器中调用 Web 服务,并用客户端的 Javascript 替换隐藏字段为返回的内容。 <% %> 在服务器上进行处理。 - Jeff Leonard

1
<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

你动态添加表单


0
您可以按照以下方式更改您的GetControl()方法:
private string GetControl(String controlName, String ClassId)
{
    Page page = new Page();
    StringWriter writer = new StringWriter();
    page.Server.Execute(controlName, writer, false);
    return writer.ToString();
}

据我所知,System.Web.UI.Page类中没有任何Execute()方法。您是指page.Server.Execute(controlName, writer, false)吗? - Jeff Leonard
很遗憾,对于用户控件(.ascx),它不起作用。page.Server.Execute()仅适用于.aspx页面。 - Jeff Leonard

0
    <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

你动态添加表单


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接