0

我有注册表和按钮。OnClick - 我在服务器端调用函数,使用邮政编码在数据库中验证用户的邮政编码。如果验证成功 - 用户的数据存储在数据库中(这里我继续使用服务器功能)。但是如果 ZipCode 不匹配 - 我会调用 Javascript 函数来询问用户是否仍要将他的数据保存到数据库。如果是 - 我使用 Ajax 请求保存它。问题是当我调用 Javascript 函数时 - 首先它应该在客户端接收用户数据。但是当读取数据发生时 - 我收到错误“无法获取未定义或空引用的属性'值'”。但是用户的数据仍然存在于表单的字段中。似乎服务器从表单中读取的数据一次 - 在某处重置 - 并且无法在客户端上再次读取。

<body>
  <form id="frmZipValidation" runat="server">
     <div>
        <asp:Label runat="server">Registration Form</asp:Label>
        <asp:TextBox runat="server" ID="txtbxName"></asp:TextBox>
        <asp:TextBox runat="server" ID="txtbxZipCode"></asp:TextBox>            
        <asp:DropDownList runat="server" ID="DDLCountry">
            <asp:ListItem Text="Select country" Value="Select" Selected="True"></asp:ListItem>
            <asp:ListItem Text="USA" Value="USA"></asp:ListItem>
            <asp:ListItem Text="Canada" Value="Canada"></asp:ListItem>
        </asp:DropDownList>
        <asp:TextBox runat="server" ID="txtbxState"></asp:TextBox> 
        <asp:TextBox runat="server" ID="txtbxCity"></asp:TextBox> 
        <asp:Button runat="server" ID="btnSubmit" Text="Submit" OnClick="btnSubmit_Click"/>
    </div>
  </form>
</body>

这是我的服务器端

public partial class Default : System.Web.UI.Page
{
    string Name;
    string ZipCode;
    string Country;
    string State;
    string City;
    bool IsMatch;
    Addresses dbAddresses = new Addresses();
    User newUser;

    protected void Page_Load(object sender, EventArgs e)
    {
        if (Request["Action"] != null && Request["Action"].Trim() != "")
        {
            if (Request["Action"] == "AddUser")
            {
                AddUser(Request["Name"], Request["ZipCode"], Request["Country"], Request["State"], Request["City"]);
            }
        }
    }

    private void AddUser(string UserName, string UserZip, string UserCountry, string UserState, string UserCity)
    {
        newUser = new User(UserName, UserZip, UserCountry, UserState, UserCity);
        dbAddresses.Users.Add(newUser);
        dbAddresses.SaveChanges();
    }

    protected void btnSubmit_Click(object sender, EventArgs e)
    {
        if (IsValid)
        {
            ZipCode = txtbxZipCode.Text;
            Country = DDLCountry.Text;
            State = txtbxState.Text;
            City = txtbxCity.Text;
            Name = txtbxName.Text;
            IsMatch = false;

            List<ZipCode> ZipC = (from z in dbAddresses.Zips
                                  where z.Zip == ZipCode
                                  select z).ToList();

            //If ZipCode entered by client do not exists at Database return false
            if (!ZipC.Any())
            {
                IsMatch = false;
            }
            else
            {                    
                for (int i = 0; i < ZipC.Count; i++)
                {
                    if (ZipC[i].Country.ToString() == Country)
                    {
                        if (ZipC[i].State.ToString() == State)
                        {
                            if (ZipC[i].City.ToString() == City)
                            {
                                AddUser(Name, ZipCode, Country, State, City);

                                //Message to the user that all saved successfully
                                Page.ClientScript.RegisterClientScriptBlock(typeof(Page), "1", "<script>alert('Your data was saved successfully!');</script>");
                                IsMatch = true;
                                break;
                            }
                            else
                            {
                                IsMatch = false;
                                break;
                            }
                        }
                        else
                        {
                            IsMatch = false;
                            break;
                        }
                    }
                    else
                    {
                        IsMatch = false;
                        break;
                    }
                }
            }
            //If user's data are not match, then go to JS client code where - If user wants in any case to save data - make it using AJAX request 
            if (!IsMatch)
            {
                string clientScript = "AjaxRequestSaveToDB();";
                this.Page.ClientScript.RegisterStartupScript(this.GetType(), "MyClientScript", clientScript);
            }
        }
    }
}

这是Javascript:

function AjaxRequestSaveToDB()
{
var SaveData = confirm('Zip/Postal code doesn’t match region. Are you sure you want to save this data?');

if (SaveData)
   {
    var UserName = document.getElementById('txtbxName').value;
    var UserZipCode = document.getElementById('txtbxZipCode').value;
    var UserCountry = document.getElementById('DDLCountry').value;
    var USerState = document.getElementById('txtbxState').value;
    var UserCity = document.getElementById('txtbxCity').value;
    SendDataToServer('AddUser', UserName, UserZipCode, UserCountry, USerState, UserCity);
    alert("You data was saved successfully!");
    }
else { alert('Not saved');
    }
   }
}

function SendDataToServer(RequestType, Name, ZipCode, Country, State, City)
{
    var xmlHttp = getXmlHttp();

    var Url = "Default.aspx?Action=" + escape(RequestType)
    + "&Name=" + escape(Name)
    + "&ZipCode=" + escape(ZipCode)
    + "&Country=" + escape(Country)
    + "&State=" + escape(State)
    + "&City=" + escape(City);

    xmlHttp.open("GET", Url, true);
    xmlHttp.send();
}
4

1 回答 1

5

一本关于使用“自定义”AJAX 请求的客户端-服务器通信的短书。

在 ASP.net 编程中(几乎)每次客户端与服务器交互时,客户端都会将其所有信息发送到服务器,然后丢弃其旧内容并用客户端从服务器接收到的响应替换它。因此,您遇到的问题是您asp:button在客户端计算机上正在向服务器上的页面发送信息,.aspx而服务器正在解释信息,意识到出了点问题并告诉客户端它应该向用户询问更多信息但抛出之前输入的所有信息。

我发现解决此问题的最佳方法是使用我所说的“自定义 AJAX 请求”。基本上,这意味着我们编写一个 XML 字符串并将其发送到一个 ASP 处理程序页面,该页面设置为接受 XML 字符串并对其进行处理。在我的旅行中,我将其精简到基本上 3 个部分。第一个是包含所有标记和 CSS(和验证)的用户界面,第二个是包含所有数据收集和实际 AJAX 请求的 JavaScript 文件,最后是ashx处理来自客户端的请求的文件.

因此,首先您需要设置您的用户界面。类似于以下内容:

<body>
  <form id="frmZipValidation" runat="server">
     <div>
        <div class="label">Registration Form<div>
        <asp:TextBox ID="txtbxName" class="txtbxName" ClientIDMode="Static" runat="server"></asp:TextBox>
        <asp:TextBox ID="txtbxZipCode" class="txtbxZipCode" ClientIDMode="Static" runat="server" ></asp:TextBox>            
        <asp:DropDownList ID="DDLCountry" class="DDLCountry" ClientIDMode="Static" runat="server" >
            <asp:ListItem Text="Select country" Value="Select" Selected="True"></asp:ListItem>
            <asp:ListItem Text="USA" Value="USA"></asp:ListItem>
            <asp:ListItem Text="Canada" Value="Canada"></asp:ListItem>
        </asp:DropDownList>
        <asp:TextBox ID="txtbxState" class="txtbxState" ClientIDMode="Static" runat="server" ></asp:TextBox> 
        <asp:TextBox ID="txtbxCity" class="txtbxCity" ClientIDMode="Static" runat="server" ></asp:TextBox> 
        <input id="btnSubmit" class="btnSubmit" type="button" value="Save" onclick="SubmitForm()" />
    </div>
  </form>
</body>

有几点需要注意:

  1. 提交表单的按钮不是 ASP 按钮,而是 HTML 按钮。
  2. 所有输入控件都是 ASP 控件,但它们ClientIDMode设置为Static,这仅适用于 .NET 4.0 或更高版本。
  3. 如果我们不使用 .NET 4.0 或更高版本,我们将其设置class为相同的值。ID您还想添加到控件的任何 CSS 类都可以添加到虚拟 ID 类之后。(对于我的示例,我假设您使用的是 .NET 4.0,但ClientIDMode如果您需要,我可以轻松地将它们切换到不使用属性的情况下)

第二个难题是 JavaScript。有几种方法可以完成我们所需要的。第一种是在没有任何插件或外部库帮助的情况下使用 vanilla JS。这节省了非常少的处理时间、少量的加载时间,并且可以完成我们要求的一切。但是,如果我们包含一个外部库 JQuery 和插件 JQuery Validation,那么我们可以通过将我们必须编写的代码量减少大约 10 倍来让我们在编程阶段的生活变得更加轻松. 如果我们真的关心加载时间,那么我们可以使用客户端缓存来存储外部库,这样它们只需下载一次。

准备好提交表单后,第一步是验证邮政编码是否有效。您可以通过几种方式执行此操作,具体取决于您想要获得的深度。最快的检查就是验证单击按钮时邮政编码文本框是否为空。所以要做到这一点,我们只需要这样做:

function SubmitForm() { //This will be assigned as the click handler on your button in your HTML
    if (document.getElementById('txtbxZipCode').value != null && document.getElementById('txtbxZipCode').value != '') {
        Save('YourHandler', GetQueryString, GetXmlString, SuccessHandler, FailureHandler);
    } else {
        //Your user needs to know what went wrong...
    }
}

所以,归根结底是整个情况的关键。AJAX 请求。我想出了一个可重用的函数来处理整个 AJAX 请求,如下所示:

function Save(handlerName, GetQueryString, GetXmlString, SuccessHandler, FailureHandler) {
    // Date.GetTime gets the number of milliseconds since 1 January 1970, so we divide by 1000 to get the seconds. 
    end = (new Date().getTime() / 1000) + 30;
    //This variable is the actual AJAX request. This object works for IE8+ but if you want backwards compatability for earlier versions you will need a different object which I can dig up for you if you need.
    var xmlhttp = new XMLHttpRequest();
    //This is the function that fires everytime the status of the request changes. 
    xmlhttp.onreadystatechange = function () {
        if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
            //Get all the headers to determine whether or not the request was successful. This is a header you will need to add to the response manually.
            var xx = xmlhttp.getResponseHeader("Success");
            //the object xx will be a string that you designate. I chose to use True as the indicator that it was successful because it was intuitive.
            var x1 = xx.trim();
            if (x1 != undefined && x1 == 'True' && (new Date().getTime() / 1000) < end) {
                //If the response was successful and the timeout hasn't elapsed then we get the XML from the response and call the success handler
                var xmlResponse = xmlhttp.responseXML;
                SuccessHandler(sender, xmlResponse);
            } else if ((new Date().getTime() / 1000) < end) {
                //If the response was not successful and the timeout hasn't elapsed then we get the XML from the response and call the failure handler
                var xmlResponse = xmlhttp.responseXML;
                FailureHandler(sender, xmlResponse);
            } //If the request was successful
        } //If the readystate is 4 and the status is 200
    }  //OnReadyStateChanged function

    //This gets the query string to be added to the url
    var varString = GetQueryString();

    //Build XML string to send to the server
    var xmlString = GetXmlString();

    //Open the request using the handler name passed in and the querystring we got from the function passed in
    xmlhttp.open("POST", "../RequestHandlers/" + handlerName + ".ashx" + varString, true);
    //This tells the handler that the content of the request is XML
    xmlhttp.setRequestHeader("Content-Type", "text/xml");
    //Send the request using the XML we got from the other function passed in.
    xmlhttp.send(xmlString);
} 

这个函数有一个内置的超时功能,如果服务器响应请求的时间超过 30 秒,那么客户端收到的任何响应都会被忽略。对于我的实现,这与另一个功能相结合,向用户显示一些内容,告诉他们网站正在处理他们的请求,如果超时过去,它会告诉他们发生了超时。

该函数所做的第二件事是假设所有处理程序都位于您网站根目录旁边的文件夹中,名为RequestHandlers. 我使用这个设置只是为了整合我所有的处理程序文件,但你可以真正改变它在哪里寻找你想要的任何地方。

函数本身接受一个字符串和四个函数指针。该字符串表示将等待解释请求的处理程序的名称,四个函数指针都有非常具体的工作。

第一个函数指针是GetQueryStringthis 表示您必须编写的函数,该函数会将您认为必要的任何变量附加到要回发的 URL 的末尾。该站点对查询字符串的用途给出了非常准确的解释。对我来说,一个常见的GetQueryString功能看起来像:

function GetPaymentQueryString() {
    var varString = '';
    varString = "?CCPayment=True";
    return varString; 
}

第二个函数指针GetXMLString用于创建 XML 字符串(如图...),该字符串将被发送到我们要回发到的处理程序页面。该字符串将代表请求的大部分。不应该向窥探您请求的任何人显示的所有内容都应作为 XML 字符串发送,如果您真的偏执,您可以将其作为加密的 XML 字符串发送,但严格来说,这不是必需的。这一切都取决于您发送的内容,如果它的信用卡信息完整,是的,也许您会想要考虑它,但如果它的名字和姓氏那么加密它会是矫枉过正。

一个常见的GetXMLString函数可能如下所示:

function GetPaymentXmlString() {
    var xmlString = '';
    xmlString = '<?xml version="1.0" encoding="UTF-8"?><Address><ZipCode>' + document.getElementById('txtbxZipCode').value + '</ZipCode></Address>';
    return xmlString;
}

该功能的重要部分是让您的 XML 正确。第一个标签是非常通用的,在大多数情况下都可以使用,然后所有标签都匹配起来。我遗漏了很多字段以节省空间。

如果一切都按计划进行并且如果出现故障,最后两个函数指针是您想要调用的。我通常处理成功请求的方式是将输入作为一个整体隐藏(通常是将它们放在自己的div部分中)并显示某种确认消息。失败的请求可能有点棘手,因为您必须告诉用户他们失败的原因。我这样做的方法是div在页面上的所有其他内容上方放置一个虚拟部分,并附加某种特殊的 CSS,div以某种方式突出显示,如果请求失败,则我从服务器发送一串文本用我最好的猜测为什么它失败并将其分配给显示在div部分。您决定如何向用户显示结果显然完全由项目本身决定。由于您在成功或失败时所做的工作基本上是在逐个项目的基础上进行的,因此我无法真正给出一个很好的通用示例来说明您应该独自完成这部分的工作。

现在我们已经有了这些部分,最后要做的部分是处理程序。

基本上出于所有意图和目的,处理程序基本上是一个ASPX没有任何内容的网页。因此HTML,构成您的处理程序页面(具有扩展名.ashx)将如下所示:

<%@ WebHandler Language="VB" CodeBehind="YourHandler.ashx.cs" Class="YourHandler" %>

就是这样。.ashx您的实际文件中不应有其他标记。显然,处理程序的名称将根据您的操作而改变。

默认情况下创建ashx文件时的代码将是一个包含单个函数的类,名为ProcessRequest. 基本上,您可以将此功能视为一种“收到请求”事件。因此,在您的情况下,您会将btnSubmit_Click函数的内容移动到文件中的ProcessRequest函数ashx。您可以添加所需的任何属性或其他功能,但ProcessRequest据我所知,该功能必须存在,处理程序才能工作。

您需要做的一个额外步骤是从发送到处理程序的 XML 中获取信息,并告诉响应您将把 XML 发送回客户端。

因此,要从请求中获取 XML,您需要执行以下操作:

IO.StreamReader textReader = New IO.StreamReader(context.Request.InputStream);
context.Request.InputStream.Seek(0, IO.SeekOrigin.Begin);
textReader.DiscardBufferedData();
XDocument xml = XDocument.Load(textReader);
String zip = xml.Elements("Address").Elements("ZipCode").FirstOrDefault().Value;

为了将 XML 发送回客户端,您需要在响应中添加几个标头,然后通过添加来完成(我认为这是在 C# 中实现接口的正确方法,但在这一点上并不积极):

class YourHandler : System.Web.IHttpHandler, System.Web.SessionState.IReadOnlySessionState

在您的班级定义下,并且:

context.Response.ContentType = "text/xml";
context.Response.ContentEncoding = System.Text.Encoding.UTF8;
context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
context.Response.Cache.SetAllowResponseInBrowserHistory(True);

到你的ProcessRequest功能的开始。这六行告诉客户端它将接收 XML,而不是缓存任何响应,这将确保您的客户端始终看到最新的内容。

所以。它在那里。您现在应该拥有验证用户输入、创建 AJAX 请求、将​​请求发送到自定义处理程序、从客户端接受 XML、将 XML 写入客户端并显示资源的框架-...我知道我忘记了一些东西.. .

客户端应该如何处理从服务器获取的 XML?把它扔在墙上,看看有什么棒?不,那是行不通的。您需要一种在客户端解释 XML 的方法。幸运的是,该XMLHttpRequest对象已被编写为使这项任务比听起来容易得多。

您可能已经注意到,我将成功和失败处理程序设置为接收一个发送者对象和一个 XML 对象。发件人真的是矫枉过正,可以忽略(或删除)此示例正常工作。XML 对象是我们现在所关心的。在我们进入客户端之前,我必须提到,您必须在服务器端经历与在客户端相同的过程,并手动编写您的 XML 字符串,包括您希望客户端知道的所有值。对于此示例,我将假设您想向FriendlyMessage用户显示 a 。要将响应写入客户端,您将执行以下操作:

using (System.Xml.XmlTextWriter writer = new System.Xml.XmlTextWriter(context.Response.Output)) {
    context.Response.AddHeader("Success", true);
    System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
    doc.LoadXml("<?xml version='1.0' encoding='UTF-8'?><Response><FriendlyMessage>" + Message + "</FriendlyMessage></Response>");
    doc.WriteTo(writer);
    writer.Flush();
    writer.Close();
}

在客户端FriendlyMessage要从 XML 中获取,您需要执行以下操作:

xml.getElementsByTagName("FriendlyMessage")[0].childNodes[0].nodeValue

现在这条线做了一些假设。例如,您可能需要添加一些检查以确保xml.getElementsByTagName("FriendlyMessage")在尝试评估它们之前确实有孩子。这些类型的检查由您自行决定。

这次我想我实际上已经涵盖了所有步骤。我希望我的“小”指南对您有所帮助,而且我并没有让您感到厌烦。我为篇幅道歉,但它是一个过程,因此正确处理需要几个步骤。一旦你确定了基线并开始工作,它真的适用于任何情况。这种布局还使您的用户体验比让他们每次都等待完全访问服务器要好得多。

我真诚地希望这可以帮助你完成你的项目,并且我没有跳过一个步骤或同样令人尴尬的事情......

于 2013-05-16T02:35:22.763 回答