Поделиться через


Обратный вызов от клиента с примером реализации проверки

Обновлен: Ноябрь 2007

При обратном вызове функция сценария на стороне клиента отправляет запрос на веб-страницу ASP.NET, которая для его обработки выполняет сокращенную версию обычного жизненного цикла обработки запроса. Чтобы убедиться, что события обратного вызова исходят от ожидаемых элементов пользовательского интерфейса, можно проверить обратные вызовы. Во время отрисовки веб-страницы (в методе Render) событие регистрируется для проверки, затем во время обратного вызова выполняется проверка события.

ms366515.alert_note(ru-ru,VS.90).gifПримечание.

Проверка события помогает защитить веб-приложение от поддельных обратных вызовов, но не защищает от атак повторной передачей пакетов. Более совершенные схемы проверки событий должны учитывать особенности конкретного веб-приложения и разрешения на доступ пользователей к его ресурсам. Дополнительные сведения см. в разделе Безопасность веб-приложений ASP.NET.

Пример, описанный здесь, расширяет Пример реализации обратного вызова клиента (C#) и Пример реализации обратного вызова от клиента (Visual Basic). В этих примерах элемент управления ListBox с именем ListBox1 является серверным элементом управления, отображающим список товаров. HTML-элемент <button> (не серверный элемент управления Button) выполняет обратный вызов для получения сведений о товарных запасах. Пример расширяется следующим образом: предоставляется дополнительная информация о том, есть ли товар в продаже, причем эти сведения доступны только пользователям, прошедшим проверку подлинности. Элемент управления LoginView используется со свойством LoggedInTemplate для отображения дополнительного содержимого. Анонимным пользователям веб-страницы разрешено выполнение обратного вызова для получения сведений о товарных запасах, тогда как пользователям, прошедшим проверку подлинности, также разрешено выполнение обратного вызова для получения сведений о продажах. Событие предоставления сведений о продажах регистрируется для проверки только в том случае, если пользователь прошел проверку подлинности. Это защищает от выполнения обратного вызова пользователями, не прошедшими проверку.

Пример

Описание

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

Код

<%@ Page Language="VB" AutoEventWireup="false" 
  CodeFile="ClientCallback.aspx.vb" Inherits="ClientCallback" %>

<!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 id="Head1" runat="server">
    <title>ASP.NET Example</title>
<script type="text/javascript">    
    function ReceiveServerData(rValue)
    {
        Results.innerText = rValue;
    }
  </script>
</head>
<body>
  <form id="form1" runat="server">
    <div>
      <asp:ListBox id="ListBox1" runat="server"></asp:ListBox>
      <br />
      <br />
      <button id="LookUpStockButton" onclick="LookUpStock()">Look Up Stock</button>
      <asp:LoginView id="LoginView1" runat="server">
      <LoggedInTemplate>
         <button id="LookUpSaleButton" onclick="LookUpSale()">Look Up Back Order</button>
      </LoggedInTemplate>
      </asp:LoginView>
      <br />
      Item status: <span id="Results"></span>
    </div>
  </form>
</body>
</html>
<%@ Page Language="C#" AutoEventWireup="true" 
  CodeFile="ClientCallback.aspx.cs" Inherits="ClientCallback" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 
  1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>ASP.NET Example</title>
<script type="text/javascript">    
    function ReceiveServerData(rValue)
    {
        Results.innerText = rValue;
    }
  </script>
</head>
<body>
  <form id="form1" runat="server">
    <div>
      <asp:ListBox id="ListBox1" runat="server"></asp:ListBox>
      <br />
      <br />
      <button id="LookUpStockButton" onclick="LookUpStock()">Look Up Stock</button>
      <asp:LoginView id="LoginView1" runat="server">
      <LoggedInTemplate>
         <button id="LookUpSaleButton" onclick="LookUpSale()">Look Up Back Order</button>
      </LoggedInTemplate>
      </asp:LoginView>
      <br />
      Item status: <span id="Results"></span>
    </div>
  </form>
</body>
</html>
Partial Class ClientCallback
    Inherits System.Web.UI.Page
    Implements System.Web.UI.ICallbackEventHandler

    Protected catalog As ListDictionary
    Protected saleitem As ListDictionary
    Protected returnValue As String
    Protected validationLookUpStock As String = "LookUpStock"
    Protected validationLookUpSale As String = "LookUpSale"
    Sub Page_Load(ByVal sender As Object, ByVal e As _
        System.EventArgs) Handles Me.Load

        Page.ClientScript.RegisterClientScriptBlock(Me.GetType(), _
            validationLookUpStock, "function LookUpStock() {  " & _
            "var lb = document.forms[0].ListBox1; " & _
            "var product = lb.options[lb.selectedIndex].text;  " & _
            "CallServer(product, ""LookUpStock"");}  ", True)
        If (User.Identity.IsAuthenticated) Then
            Page.ClientScript.RegisterClientScriptBlock(Me.GetType(), _
            validationLookUpSale, "function LookUpSale() {  " & _
            "var lb = document.forms[0].ListBox1; " & _
            "var product = lb.options[lb.selectedIndex].text;  " & _
            "CallServer(product, ""LookUpSale"");} ", True)
        End If

        Dim cbReference As String
        cbReference = "var param = arg + '|' + context;" & _
             Page.ClientScript.GetCallbackEventReference(Me, _
            "param", "ReceiveServerData", "context")
        Dim callbackScript As String = ""
        callbackScript &= "function CallServer(arg, context) { " & _
            cbReference & "} ;"
        Page.ClientScript.RegisterClientScriptBlock(Me.GetType(), _
            "CallServer", callbackScript, True)

        ' Populate List Dictionary with invented database data
        catalog = New ListDictionary()
        saleitem = New ListDictionary()
        catalog.Add("monitor", 12)
        catalog.Add("laptop", 10)
        catalog.Add("keyboard", 23)
        catalog.Add("mouse", 17)
        saleitem.Add("monitor", 1)
        saleitem.Add("laptop", 0)
        saleitem.Add("keyboard", 0)
        saleitem.Add("mouse", 1)

        ListBox1.DataSource = catalog
        ListBox1.DataTextField = "key"
        ListBox1.DataBind()
    End Sub

    Public Sub RaiseCallbackEvent(ByVal eventArgument As String) _
    Implements System.Web.UI.ICallbackEventHandler.RaiseCallbackEvent

        Dim argParts() As String = eventArgument.Split("|"c)
        If ((argParts Is Nothing) OrElse (argParts.Length <> 2)) Then
            returnValue = "A problem occurred trying to retrieve stock count."
            Return
        End If

        Dim product As String = argParts(0)
        Dim validationaction = argParts(1)
        Select Case validationaction
            Case "LookUpStock"
                Try
                    Page.ClientScript.ValidateEvent("LookUpStockButton", validationaction)
                    If (catalog(product) Is Nothing) Then
                        returnValue = "Item not found."
                    Else
                        returnValue = catalog(product).ToString() & " in stock."
                    End If
                Catch
                    returnValue = "Can not retrieve stock count."
                End Try
            Case "LookUpSale"
                Try
                    Page.ClientScript.ValidateEvent("LookUpSaleButton", validationaction)
                    If (saleitem(product) Is Nothing) Then
                        returnValue = "Item not found."
                    Else
                        If (Convert.ToBoolean(saleitem(product))) Then
                            returnValue = "Item is on sale."
                        Else
                            returnValue = "Item is not on sale."
                        End If
                    End If
                Catch
                    returnValue = "Can not retrieve sale status."
                End Try

        End Select

    End Sub

    Public Function GetCallbackResult() _
    As String Implements _
    System.Web.UI.ICallbackEventHandler.GetCallbackResult

        Return returnValue

    End Function

    Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter)
        Page.ClientScript.RegisterForEventValidation("LookUpStockButton", _
          validationLookUpStock)
        If (User.Identity.IsAuthenticated) Then
            Page.ClientScript.RegisterForEventValidation("LookUpSaleButton", _
             validationLookUpSale)
        End If
        MyBase.Render(writer)
    End Sub
End Class
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

public partial class ClientCallback : System.Web.UI.Page,
     System.Web.UI.ICallbackEventHandler
{
    protected System.Collections.Specialized.ListDictionary catalog;
    protected System.Collections.Specialized.ListDictionary saleitem;
    protected String returnValue;
    protected String validationLookUpStock = "LookUpStock";
    protected String validationLookUpSale = "LookUpSale";
    protected void Page_Load(object sender, EventArgs e)
    {
        Page.ClientScript.RegisterClientScriptBlock(this.GetType(),
            validationLookUpStock, "function LookUpStock() {  " +
            "var lb = document.forms[0].ListBox1; " +
            "var product = lb.options[lb.selectedIndex].text;  " +
            @"CallServer(product, ""LookUpStock"");}  ", true);
        if (User.Identity.IsAuthenticated)
        {
            Page.ClientScript.RegisterClientScriptBlock(this.GetType(),
            validationLookUpSale, "function LookUpSale() {  " +
            "var lb = document.forms[0].ListBox1; " +
            "var product = lb.options[lb.selectedIndex].text;  " +
            @"CallServer(product, ""LookUpSale"");} ", true);
        }

        String cbReference = "var param = arg + '|' + context;" + 
            Page.ClientScript.GetCallbackEventReference(this,
            "param", "ReceiveServerData", "context");
        String callbackScript;
        callbackScript = "function CallServer(arg, context)" +
            "{ " + cbReference + "} ;";
        Page.ClientScript.RegisterClientScriptBlock(this.GetType(),
            "CallServer", callbackScript, true);

        catalog = new System.Collections.Specialized.ListDictionary();
        saleitem = new System.Collections.Specialized.ListDictionary();
        catalog.Add("monitor", 12);
        catalog.Add("laptop", 10);
        catalog.Add("keyboard", 23);
        catalog.Add("mouse", 17);
        saleitem.Add("monitor", 1);
        saleitem.Add("laptop", 0);
        saleitem.Add("keyboard", 0);
        saleitem.Add("mouse", 1);

        ListBox1.DataSource = catalog;
        ListBox1.DataTextField = "key";
        ListBox1.DataBind();
    }
    public void RaiseCallbackEvent(String eventArgument)
    {
        string[] argParts = eventArgument.Split('|');
        if ((argParts == null) || (argParts.Length != 2))
        {
            returnValue = "A problem occurred trying to retrieve stock count.";
            return;
        }
        string product = argParts[0];
        string validationaction = argParts[1];
        switch (validationaction)
        {
            case "LookUpStock":
                try
                {
                    Page.ClientScript.ValidateEvent("LookUpStockButton", validationaction);
                    if (catalog[product] == null)
                    {
                        returnValue = "Item not found.";
                    }
                    else
                    {
                        returnValue = catalog[product].ToString() + " in stock.";
                    }
                }
                catch
                {
                    returnValue = "Can not retrieve stock count.";
                } 
                break;
            case "LookUpSale":
                try
                {
                    Page.ClientScript.ValidateEvent("LookUpSaleButton", validationaction);
                    if (saleitem[product] == null)
                    {
                        returnValue = "Item not found.";
                    }
                    else
                    {
                        if (Convert.ToBoolean(saleitem[product]))
                            returnValue = "Item is on sale.";
                        else
                            returnValue = "Item is not on sale.";
                    }
                }
                catch
                {
                    returnValue = "Can not retrieve sale status.";
                }
                break;
        }

    }
    public String GetCallbackResult()
    {
        return returnValue;
    }
    protected override void Render(HtmlTextWriter writer)
    {
        Page.ClientScript.RegisterForEventValidation("LookUpStockButton",
            validationLookUpStock);
        if (User.Identity.IsAuthenticated)
        {
            Page.ClientScript.RegisterForEventValidation("LookUpSaleButton",
                validationLookUpSale);
        }
        base.Render(writer);
    }
}

Примечания

Веб-страница имитирует поиск в базе данных, содержащей группы товаров (мониторы, клавиатуры и т.д.), для определения количества товаров, имеющихся в наличии или на складе. Чтобы упростить данный пример, база данных представляется в виде словаря-списка из небольшого набора элементов. Для каждого товара в таблице ключом является имя элемента, например «monitor», а значением — количество товаров, имеющихся на складе. В реальных приложениях следует использовать базу данных.

При запуске страницы элемент управления ListBox привязывается к хэш-таблице, чтобы отображать список товаров. Для прошедших проверку подлинности пользователей страница отображается с двумя элементами HTML <button>, события которых onclick привязаны к методам клиента LookUpStock и LookUpSale соответственно. Для анонимных пользователей страница отображается с единственным элементом HTML <button>, событие которого onclick привязано к методу LookUpStock. Элемент управления LoginView используется для указания, какие кнопки будут показаны. В переопределенном для страницы событии Render регистрируются кнопки для выполнения проверки. Если пользователь не прошел проверку подлинности, кнопка, которая инициирует обратный вызов для LookUpSale, не регистрируется, и обратный вызов, если он будет предпринят, завершится ошибкой.

Фоновый код добавляет сценарий к клиентской странице с помощью метода RegisterClientScriptBlock. Добавляемый на страницу сценарий содержит функцию с именем CallServer, которая получает имя метода. Это имя будет передано серверу из метода GetCallbackEventReference.

Обратный вызов клиента приводит к вызову метода RaiseCallbackEvent, который определяет объем запасов переданного ему наименования товара. Это значение возвращает метод GetCallbackResult. Обратите внимание, что аргументы, передаваемые между клиентским сценарием и серверным кодом, могут быть только строками. Чтобы передать или получить несколько значений, можно объединить входные или выходные строки соответственно.

ms366515.alert_security(ru-ru,VS.90).gifПримечание о безопасности.

Если веб-страницы и клиентские обратные вызовы работают с конфиденциальными данными, либо осуществляют операции вставки, обновления или удаления в базах данных, рекомендуется выполнять проверки обратных вызовов, чтобы убедиться, что обратный вызов выполняется предназначенным для этого элементом пользовательского интерфейса.

См. также

Задачи

Практическое руководство. Реализация обратных вызовов на веб-страницах ASP.NET

Основные понятия

Программная реализация обратных вызовов клиента без обратной передачи в веб-страницы ASP.NET

Пример реализации обратного вызова клиента (C#)

Пример реализации обратного вызова от клиента (Visual Basic)

Ссылки

ClientScriptManager

RegisterForEventValidation

ValidateEvent