Clientrückruf und Beispiel für die Implementierung einer Validierung

Aktualisiert: November 2007

Bei einem Clientrückruf sendet eine Clientskriptfunktion eine Anforderung an die ASP.NET-Webseite. Diese führt dann eine Kurzversion des normalen Lebenszyklus aus, um den Rückruf zu bearbeiten. Um sicherzustellen, dass Rückrufereignisse von der erwarteten Benutzeroberfläche stammen, können Sie Rückrufe überprüfen. Bei einer Rückrufvalidierung wird ein Ereignis während der Wiedergabe der Webseite für die Validierung registriert und dann während des Rückrufs validiert.

Hinweis:

Mit der Ereignisvalidierung können Webanwendungen vor falschen Postbacks, jedoch nicht vor wiederholten Angriffen geschützt werden. Ein etwas umfassenderes Ereignisvalidierungsschema sollte auch die Besonderheiten der Webanwendung und die Berechtigungen der Benutzer berücksichtigen, die auf die Ressourcen zurückgreifen. Weitere Informationen finden Sie unter Sicherheit für ASP.NET-Webanwendungen.

Im hier erläuterten Beispiel werden Beispielimplementierung eines Clientrückrufs (C#) und Beispiel für das Implementieren von Clientrückrufen (Visual Basic) erweitert. In diesen Beispielen handelt es sich bei dem ListBox-Steuerelement ListBox1 um ein Steuerelement des Servers, mit dem eine Produktliste angezeigt wird. Ein HTML <button>-Element (nicht ein Button-Serversteuerelement) führt einen Rückruf aus, um Produktbestandsdaten abzurufen. Das Beispiel wird so erweitert, dass zusätzliche Informationen zum Verkaufstatus eines Produkts bereitgestellt werden und diese Informationen nur von authentifizierten Benutzern angezeigt werden dürfen. Ein LoginView-Steuerelement wird verwendet, bei dem die LoggedInTemplate-Eigenschaft auf die Anzeige weiterer Inhalte festgelegt ist. Anonyme Benutzer der Webseite können einen Rückruf ausführen, um Bestandsdaten abzurufen. Angemeldete Benutzer dagegen können über einen Rückruf auch Verkaufsinformationen abrufen. Der Rückruf für die Verkaufsinformationen wird nur dann für die Ereignisvalidierung registriert, wenn der Benutzer authentifiziert ist. So wird verhindert, dass nicht authentifizierte Benutzer einen Rückruf ausführen können.

Beispiel

Beschreibung

Im folgenden Beispiel emuliert eine Webseite eine Datenbanksuche, um die Anzahl der verfügbaren Artikel sowie die Verfügbarkeit eines Artikels zu ermitteln. Um das Beispiel zu vereinfachen, wird der Datenspeicher durch zwei Wörterbuchlisten dargestellt. In einer Betriebsanwendung würde stattdessen eine Datenbank verwendet werden. In diesem Beispiel wird ein Szenario dargestellt, bei dem durch Überprüfen von Clientrückrufen verhindert wird, dass ein anonymer Benutzer einen Rückruf ausführt, der nur für authentifizierte Benutzer vorgesehen ist.

Code

<%@ 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" >
    <title>ASP.NET Example</title>
<script type="text/javascript">    
    function ReceiveServerData(rValue)
    {
        Results.innerText = rValue;
    }
  </script>
</head>
<body>
  <form id="form1" >
    <div>
      <asp:ListBox id="ListBox1" ></asp:ListBox>
      <br />
      <br />
      <button id="LookUpStockButton" onclick="LookUpStock()">Look Up Stock</button>
      <asp:LoginView id="LoginView1" >
      <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" >
    <title>ASP.NET Example</title>
<script type="text/javascript">    
    function ReceiveServerData(rValue)
    {
        Results.innerText = rValue;
    }
  </script>
</head>
<body>
  <form id="form1" >
    <div>
      <asp:ListBox id="ListBox1" ></asp:ListBox>
      <br />
      <br />
      <button id="LookUpStockButton" onclick="LookUpStock()">Look Up Stock</button>
      <asp:LoginView id="LoginView1" >
      <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);
    }
}

Kommentare

Die Webseite emuliert eine Datenbanksuche, um für einige Produkte (Monitore, Tastaturen usw.) die Anzahl der verfügbaren oder vorrätigen Artikel zu bestimmen. Um das Codebeispiel zu vereinfachen, wird die Datenbank als Wörterbuchliste dargestellt, die nur einige wenige Elemente enthält. Für jeden Artikel in der Tabelle ist der Artikelname der Schlüssel (z. B. Monitor), und der Wert beschreibt die Anzahl der vorrätigen Artikel. In einer Betriebsanwendung würde stattdessen eine Datenbank verwendet werden.

Beim Ausführen der Seite wird ein ListBox-Steuerelement an die Hashtabelle gebunden, damit das ListBox-Steuerelement die Produktliste anzeigen kann. Für authentifizierte Benutzer wird die Seite mit zwei HTML <button>-Elementen gerendert, deren onclick-Ereignisse an eine Clientfunktion mit dem Namen LookUpStock bzw. an eine Clientfunktion mit dem Namen LookUpSale gebunden sind. Für anonyme Benutzer wird die Seite mit nur einem HTML <button>-Element gerendert, dessen onclick-Ereignis an LookUpStock gebunden ist. Ein LoginView-Steuerelement wird verwendet, um anzugeben, welche Schaltflächen angezeigt werden. In einem überschriebenen Render-Ereignis für die Seite werden die Schaltflächen für die Validierung registriert. Wenn der Benutzer nicht authentifiziert ist, wird die Schaltfläche, die den Rückruf für LookUpSale auslöst, nicht registriert und ein Rückrufversuch schlägt fehl.

Die Code-Behind-Seite fügt der Seite über die RegisterClientScriptBlock-Methode Clientskripts hinzu. Das hinzugefügte Skript enthält die CallServer-Funktion, die von der GetCallbackEventReference-Methode den Namen der Methode abruft, die das Postback an den Server ausführt.

Der Clientrückruf ruft die RaiseCallbackEvent-Methode auf, die den verfügbaren Bestand für den an sie übergebenen Artikel ermittelt. Die GetCallbackResult-Methode gibt den Wert zurück. Beachten Sie, dass es sich bei den Argumenten, die zwischen Clientskript und Servercode hin und her gesendet werden, nur um Zeichenfolgen handeln darf. Sie können mehrere Werte gleichzeitig eingeben oder empfangen, wenn Sie in der Eingabezeichenfolge bzw. in der Ausgabezeichenfolge jeweils Werte verknüpfen.

Sicherheitshinweis:

Wenn die Webseite und die Clientrückrufe vertrauliche Daten anzeigen oder mit Operationen arbeiten, die Daten einfügen, aktualisieren oder löschen, sollten Sie die Rückrufe unbedingt überprüfen, um sicherzustellen, dass das dafür vorgesehene Element der Benutzeroberfläche den Rückruf ausführt.

Siehe auch

Aufgaben

Gewusst wie: Implementieren von Rückrufen in ASP.NET-Webseiten

Konzepte

Programmgesteuertes Implementieren von Clientrückrufen ohne Postbacks in ASP.NET-Webseiten

Beispielimplementierung eines Clientrückrufs (C#)

Beispiel für das Implementieren von Clientrückrufen (Visual Basic)

Referenz

ClientScriptManager

RegisterForEventValidation

ValidateEvent