SqlDataSource + Disabled Viewstate = двойная привязка данных - PullRequest
1 голос
/ 02 марта 2012

Вот тестовая страница (.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>

Во-первых, я знаю, что в логике есть пробелы. Я построил эту страницу только для того, чтобы показать симптомы и быть как можно короче. Позвольте мне объяснить настройки ...

У меня есть страница с режимом просмотра ОТКЛЮЧЕНО. У меня есть базовый SqlDataSource с инструкциями Insert и Select, а SelectStatement имеет ControlParameter (который для целей тестирования просто захватывает пустое текстовое поле).

У меня есть FormView по умолчанию для режима вставки. [Для краткости я исключил ItemTemplate из моего примера, но на самом деле он есть. Есть моменты, когда мой FormView будет вставлять, и времена будут только для чтения. Проблема возникает в режиме вставки.]

Наконец, у меня есть внешняя кнопка, которая выполняет обратную передачу без вставки. Эта фиктивная кнопка просто представляет выполнение обратной передачи, которую вы не хотите вставлять. (На самом деле, я делаю некоторые проверки на стороне сервера.)

Проблема проста: когда вы нажимаете кнопку и выполняете обратную передачу, FormView привязывает данные дважды. Вы можете видеть это из события FormView1_DataBound, которое я добавил. Таким образом, если вы попытаетесь ввести значения в поля «Имя» или «Фамилия», а затем нажмите кнопку, введенная вами информация будет потеряна. Этого не должно быть, поскольку идея заключается в том, что если проверка на стороне сервера не удалась, вы можете исправить и повторить попытку, не вводя заново все поля.

Я точно знаю, что это вызвано ControlParameter, и я даже знаю, почему: он ожидает, что ViewState включен. Поскольку он отключен, когда он запускает свой метод OnEvaluate, он видит, что возвращаемое значение отличается от значения, «хранящегося в viewstate» (т. Е. NULL, поскольку значения нет). Это приводит к тому, что OnParameterChanged выбрасывает, а FormView привязывает данные во второй раз ... даже если он находится в режиме вставки и даже не касается оператора Select. (Если бы вы добавили прослушиватель события Selecting в SqlDataSource, он бы даже не выдавал. FormView снова связывается даром.)

Если вы просто сделаете ControlParameter параметром со значением по умолчанию, проблема исчезнет. FormView будет связываться только один раз, а ваши значения будут сохранены после обратной передачи.

Итак, мой вопрос: как вы можете обойти эту досадную проблему? Прямо сейчас, единственный способ исправить это:

  • Используйте только bland asp: Parameter, затем реализуйте событие SqlDataSource OnSelecting, чтобы установить значения, когда они действительно необходимы. Мне не нравится это решение, так как я вынужден присматривать за SqlDataSource и не позволяет мне использовать полезные параметры, такие как ControlParameter, SessionParameter, QueryStringParameter и т. Д.

  • Включите режим просмотра ... тьфу. Я знаю, что могу включить его только на SqlDataSource, но я действительно чувствую, что мне не нужно, когда его функциональность мне не нужна. Мне никогда не нужно запоминать состояние SqlDataSource и его параметры.

  • Откажитесь от проклятого SqlDataSource и вернитесь к SqlCommands для кровавого всего ... что я бы предпочел не делать, поскольку он так хорошо интегрируется во все другие элементы управления ASP, такие как FormView, GridView, DropDownList и т.д.

Я более чем готов создать собственный класс для чего угодно. Черт, я пытался создать свой собственный параметр, но корневой параметр настолько заблокирован, что я не вижу пути к чертовой функциональности ViewState & 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, чтобы дать мне решение. Это не совсем будущее.

Я все еще надеюсь, что есть более простое решение, чем это.

...