Обратный вызов от клиента с примером реализации проверки
Обновлен: Ноябрь 2007
При обратном вызове функция сценария на стороне клиента отправляет запрос на веб-страницу ASP.NET, которая для его обработки выполняет сокращенную версию обычного жизненного цикла обработки запроса. Чтобы убедиться, что события обратного вызова исходят от ожидаемых элементов пользовательского интерфейса, можно проверить обратные вызовы. Во время отрисовки веб-страницы (в методе Render) событие регистрируется для проверки, затем во время обратного вызова выполняется проверка события.
Примечание. |
---|
Проверка события помогает защитить веб-приложение от поддельных обратных вызовов, но не защищает от атак повторной передачей пакетов. Более совершенные схемы проверки событий должны учитывать особенности конкретного веб-приложения и разрешения на доступ пользователей к его ресурсам. Дополнительные сведения см. в разделе Безопасность веб-приложений 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. Обратите внимание, что аргументы, передаваемые между клиентским сценарием и серверным кодом, могут быть только строками. Чтобы передать или получить несколько значений, можно объединить входные или выходные строки соответственно.
Примечание о безопасности. |
---|
Если веб-страницы и клиентские обратные вызовы работают с конфиденциальными данными, либо осуществляют операции вставки, обновления или удаления в базах данных, рекомендуется выполнять проверки обратных вызовов, чтобы убедиться, что обратный вызов выполняется предназначенным для этого элементом пользовательского интерфейса. |
См. также
Задачи
Практическое руководство. Реализация обратных вызовов на веб-страницах ASP.NET
Основные понятия
Программная реализация обратных вызовов клиента без обратной передачи в веб-страницы ASP.NET
Пример реализации обратного вызова клиента (C#)
Пример реализации обратного вызова от клиента (Visual Basic)