无法获取未定义或空引用的属性“值”

时间:2013-05-15 14:46:04

标签: asp.net ajax asp.net-ajax

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

<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();
}

1 个答案:

答案 0 :(得分:5)

关于使用&#34;自定义&#34;的客户端 - 服务器通信的简短书籍AJAX请求。

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

我找到解决此问题的最佳方法是使用我所说的&#34;自定义AJAX请求。&#34;基本上这意味着我们编写一个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但我可以轻松地将它们切换到不使用{{1如果需要,可以使用属性
  4. 拼图的第二部分是JavaScript。我们可以通过几种方式实现我们的需求。第一种是在没有任何插件或外部库的帮助下使用vanilla JS。这节省了非常少的处理时间,边际加载时间,并且可以完成我们所要求的一切。但是,如果我们包含一个外部库,JQuery和插件,JQuery Validation,那么在编程阶段我们可以通过减少大约10倍的代码来减少代码量,从而使我们的生活变得更加容易。如果我们真的关心加载时间,那么我们可以使用客户端缓存来存储外部库,这样他们只需要下载一次。因此,您是否决定使用任何外部JavaScript库取决于您的项目需要,但因为您只关心验证邮政编码不是空的我不会使用任何JQuery但我只是认为它值得一提,因为它是如何简化流程的。

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

    ClientIDMode

    所以,直到这整个情况的肉和土豆。 AJAX请求。我已经提出了一个可重用的函数来处理整个AJAX请求,如下所示:

    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...
        }
    }
    

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

    此功能的第二件事是它假设所有处理程序都位于名为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); } 的网站根目录旁边的文件夹中。我使用这个设置只是为了整合我的所有处理程序文件,但你可以真正改变它在任何你想要的地方。

    函数本身包含一个字符串和四个函数指针。该字符串表示将等待解释请求的处理程序的名称,这四个函数指针都具有非常特定的作业。

    第一个函数指针是RequestHandlers,它表示您必须编写的函数,它将您认为必要的任何变量附加到要发回的URL的末尾。 This site为查询字符串的用途提供了非常准确的解释。对我来说,常见的GetQueryString函数类似于:

    GetQueryString

    第二个函数指针function GetPaymentQueryString() { var varString = ''; varString = "?CCPayment=True"; return varString; } 用于创建XML字符串(转换图...),该字符串将发送到我们发回的处理程序页面。此字符串将代表请求的大部分内容。任何窥探你的请求都不应该显示的内容应该作为XML字符串发送,如果你真的是偏执狂,你可以将它作为加密的XML字符串发送,但严格来说,这不是必需的。这一切都取决于你发送的是什么,如果它完整的信用卡信息那么,是的,也许你会想要考虑它,但如果它的名字和姓氏然后加密它将是矫枉过正。

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

    GetXMLString

    该功能的重要部分是使您的XML正确。第一个标签是非常通用的,在大多数情况下应该可以使用,然后在它之后它只是匹配标签。我遗漏了很多你的田地以节省空间。

    最后两个函数指针是你想要调用的东西,如果一切按计划进行,并且分别失败了。我通常处理成功请求的方式是将输入整体隐藏(通常将它们放在自己的function GetPaymentXmlString() { var xmlString = ''; xmlString = '<?xml version="1.0" encoding="UTF-8"?><Address><ZipCode>' + document.getElementById('txtbxZipCode').value + '</ZipCode></Address>'; return xmlString; } 部分内)并显示某种确认消息。失败的请求可能有点棘手,因为您必须告诉用户失败的原因。我这样做的方法是在页面上的其他所有部分上面放置一个虚拟div部分,并附加某种特殊的CSS,使div以某种方式脱颖而出,如果请求失败然后我从服务器发送一串文本,我最好猜测它失败的原因并将其分配给div部分中显示的内容。您决定如何向用户显示结果显然都是由项目本身决定的。因为你在成功或失败时所做的事情基本上是逐个项目的,所以我无法给出你应该做的一个很好的通用例子,因为这部分你自己就是这样。

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

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

    .ashx

    就是这样。实际的<%@ WebHandler Language="VB" CodeBehind="YourHandler.ashx.cs" Class="YourHandler" %> 文件中不应该有其他标记。显然,处理程序的名称将根据您的操作而改变。

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

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

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

    ProcessRequest

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

    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;
    

    在您的班级定义下:

    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); 函数的开头。这六行告诉客户它将接收XML而不是缓存任何响应,这将确保您的客户始终看到最新的内容。

    因此。它就是。你现在应该有框架来验证用户输入,创建一个AJAX请求,将请求发送到自定义处理程序,从客户端接受XML,将XML写入客户端并显示res -...我知道我忘了一些东西..

    客户端应该对从服务器获取的XML做什么?扔在墙上,看看有什么棒?不,这不会起作用。您需要一种方法来解释客户端上的XML。幸运的是,ProcessRequest对象的编写使得这项任务比听起来容易得多。

    您可能已经注意到我设置了成功和失败处理程序来获取发件人对象和XML对象。发件人真的有点过分,可以忽略(或删除)这个例子,以便正常工作。 XML对象是我们现在关注的。在我们进入客户端之前,我必须提到您必须在服务器端执行与客户端相同的过程,并手动编写XML字符串,包括您希望客户端了解的所有值。对于此示例,我假设您要向用户显示XMLHttpRequest。要将响应写入客户端,您将执行以下操作:

    FriendlyMessage

    在客户端,您需要从XML获取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.getElementsByTagName("FriendlyMessage")[0].childNodes[0].nodeValue 在尝试评估它们之前确实有孩子。这些检查由您自行决定。

    这次我想我实际上已经涵盖了所有步骤。我希望我的&#34;小&#34;指南可以帮助你,而我并没有给你太多烦恼。我为长度道歉,但它是一个过程,所以正确的步骤需要几步。一旦你获得了基线和工作,它真的适合任何情况。这种布局还使您的用户体验比让他们每次等待完全访问服务器更好。

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