SqlDataSource +已禁用Viewstate = Double DataBind

时间:2012-03-02 14:38:15

标签: asp.net viewstate sqldatasource

这是我为了显示我遇到的症状而构建的测试页面(.NET 4):

<%@ Page Language="C#" AutoEventWireup="true" ViewStateMode="Disabled" %>

<script runat="server">
    protected void FormView1_DataBound(object sender, EventArgs e) {
        System.Diagnostics.Debug.WriteLine("FormView1_DataBound");
    }
</script>
<!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">
</head>
<body>
    <form id="form1" runat="server">
    <asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="<%$ ConnectionStrings:MyConnectionString %>"
        InsertCommand="INSERT INTO [User] ([First_Name], [Last_Name]) VALUES (@First_Name, @Last_Name)"
        SelectCommand="SELECT * FROM [User] where [User_ID] = @User_ID">
        <SelectParameters>
            <asp:ControlParameter Name="User_ID" Type="Int32" ControlID="TextBox1" PropertyName="Text" />
        </SelectParameters>
        <InsertParameters>
            <asp:Parameter Name="First_Name" Type="String" />
            <asp:Parameter Name="Last_Name" Type="String" />
        </InsertParameters>
    </asp:SqlDataSource>
    <asp:TextBox ID="TextBox1" runat="server" Text="351"></asp:TextBox>
    <asp:FormView ID="FormView1" runat="server" DataKeyNames="User_ID" DataSourceID="SqlDataSource1"
        DefaultMode="Insert" OnDataBound="FormView1_DataBound">
        <InsertItemTemplate>
            First_Name:
            <asp:TextBox ID="First_NameTextBox" runat="server" Text='<%# Bind("First_Name") %>' />
            <br />
            Last_Name:
            <asp:TextBox ID="Last_NameTextBox" runat="server" Text='<%# Bind("Last_Name") %>' />
            <br />
            <asp:LinkButton ID="InsertButton" runat="server" CausesValidation="True" CommandName="Insert"
                Text="Insert" />
        </InsertItemTemplate>
    </asp:FormView>
    <asp:Button ID="Button1" runat="server" Text="Button" />
    </form>
</body>
</html>

首先,我知道逻辑上存在差距。我建立此页面只是为了显示症状,并尽可能短。让我解释一下设置......

我有一个viewstatemode DISABLED的页面。我有一个带有Insert和Select语句的基本SqlDataSource,而SelectStatement有一个ControlParameter(出于测试目的,只能从虚拟文本框中获取)。

我有一个FormView,它默认为Insert模式。 [为了简洁起见,我从我的例子中省略了一个ItemTemplate,但实际上有一个。有时候我的FormView会插入,而且它只是readonly的时间。在插入模式下会出现问题。]

最后,我有一个外接按钮,可以在不插入的情况下进行回发。这个虚拟按钮只表示在不想插入的情况下进行回发。 (实际上,我做了一些服务器端验证。)

问题很简单:当您单击按钮并回发时,FormView数据绑定两次。您可以从我添加的FormView1_DataBound事件中看到这一点。因此,如果您尝试在名字或姓氏字段中输入值,然后单击按钮,您输入的信息将丢失。它不应该是,因为想法是如果服务器端验证失败,您可以修复并重试,而无需重新输入所有字段。

我确信这是由ControlParameter引起的,我甚至知道原因:它希望ViewState打开。因为它关闭,当它运行其OnEvaluate方法时,它看到返回的值与“存储在viewstate中”的值不同(即NULL,因为没有值)。这导致OnParameterChanged抛出,而FormView再次进行数据绑定....即使它处于插入模式并且甚至没有触及Select语句。 (如果你要向SqlDataSource添加一个Selecting事件监听器,它甚至不会抛出.PormView再次无法绑定。)

如果您只是将ControlParameter设为带有DefaultValue的参数,问题就会消失。 FormView只会绑定一次,并且在回发后将保留您的表单值。

所以我的问题是,你怎么能解决这个恼人的问题? 现在,我能看到修复它的唯一方法是:

  • 仅使用bland asp:Parameter,然后实现SqlDataSource OnSelecting事件以在实际需要时设置值。我不喜欢这个解决方案,因为我必须照看SqlDataSource并阻止我使用ControlParameter,SessionParameter,QueryStringParameter等有用的参数。

  • 打开viewstate ......呃。我知道我可以在SqlDataSource上打开它,但我真的觉得我不需要它的功能我不想要它。我永远不需要记住SqlDataSource的状态及其参数。

  • 放弃该死的SqlDataSource并返回SqlCommands以获得血腥的一切......我也不想这样做,因为它很好地集成到所有ASP的其他控件中,如FormView,GridView,DropDownList,等

我非常愿意为任何事情建立一个自定义类。地狱我试图制作我自己的自定义参数,但根参数是如此锁定我无法看到该死的ViewState&amp; OnParameterChanged功能。

我真的希望我错过了一些明显的东西。


编辑1:我想出了最讨厌的黑客解决方案。我制作了自己的SqlDataSource。然后,OnLoad:

protected override void OnLoad(EventArgs e) {
    foreach (Parameter p in SelectParameters) {
        Type t = p.GetType();
        MethodInfo mi = t.GetMethod("Evaluate", BindingFlags.NonPublic | BindingFlags.Instance);
        if (mi == null) return; 

        while (t != null && !t.Equals(typeof(Parameter))) { t = t.BaseType; }
        if (t == null) return;

        FieldInfo fi = t.GetField("_viewState", BindingFlags.NonPublic | BindingFlags.Instance);
        if (fi == null) return;

        object o = fi.GetValue(p);
        if (o == null) return;

        System.Web.UI.StateBag stateBag = (System.Web.UI.StateBag)o;
        stateBag["ParameterValue"] = mi.Invoke(p, new object[] { Context, this });
    }

    Base.OnLoad(e);
}

基本上我破解它应该进入视图状态的价值,欺骗它认为价值没有改变。这有效,但它非常难看。我讨厌依靠Reflection给我一个解决方案。这不完全是面向未来的。

我仍然希望有一个比这更简单的解决方案。

0 个答案:

没有答案