Point MVC vs. web forms here in the effort to make ajax calls to the server for asynchronous postbacks. This experience made me realize how nice MVC is from this respect I'm about to highlight.
For years I used ASP.NET UpdatePanels in web forms development and overall they have done the job. However, anyone that has used them on mildly complex pages knows, you can't just have 1 UpdatePanel. Nope, you need 2 with the 2nd one having a trigger pointing to the 1st so partial updates will work properly. The end result: a tangled web of UpdatePanel goo.
Recently I decided to use jQuery making AJAX calls to ASP.NET page methods instead as an alternative to UpdatePanels. The jQuery in it of itself may not be 100% cleaner but it works really well and is straight forward. So I create my ASP.NET page method, write some jQuery to make a call to my new page method asynchronously, and walla I have bypassed the UpdatePanel, yeah!
Not so fast...
Caveat #1: Page Method Must Be Static. This will by the way not be ideal for anyone doing Dependency Injection as you will have to Publicly resolve dependencies through methods like: Global.GetInstance
Caveat #2: Can't see my server side controls from the static method called (because of #1 above and how it's called from the client).
Caveat #3: Normal postbacks later that access or post bound clientside elements complain of security and EnableEventValidation issue.
Caveat #4: Be prepared to query element values via Request.Form and not through control instances.
End result: A worse mess than using an UpdatePanel.
Recommendation: If you are doing ASP.NET web form development here are the (3) choices I recommend:
- Deal with full postbacks like it's 2002
- Use an UpdatePanel for asynchronous postbacks and don't mix client side data binding asynchronously with JavaScript.
- Ditch ASP.NET web forms and do MVC (hey no hater here, I stick up for web forms a lot - just know how the tool operates)
First a simple set of server controls:
<form id="form1" runat="server"> <div> <asp:DropDownList ID="DropDownList1" runat="server"> <asp:ListItem Text="Please Select..." Value="0"></asp:ListItem> <asp:ListItem Text="1st Period" Value="1"></asp:ListItem> <asp:ListItem Text="2nd Period" Value="2"></asp:ListItem> </asp:DropDownList> </div> <div> <asp:ListBox ID="ListBox1" runat="server" Width="400px" SelectionMode="Multiple"></asp:ListBox> </div> <div> <asp:Label ID="Label1" runat="server" Text=""></asp:Label> </div> <div> <asp:Button ID="Button1" runat="server" Text="Submit" OnClick="Button1_Click" /> </div> </form>
Next we need a Static server-side event with the [WebMethod] attribute added that our jQuery we will make can make an AJAX call to and will return a collection of 'School Classes' for the 'Period' selected:
public partial class Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } [WebMethod] public static List<SchoolClasses> DropDownList1_SelectedIndexChanged(string id) { if (id == "0") { return null; } //Generic code representing call probably to retrieve values from the database var classesPeriod1 = new List<SchoolClasses>() { new SchoolClasses(){ID = 1, Name = "Math 101"}, new SchoolClasses(){ID = 2, Name = "Science 101"}, new SchoolClasses(){ID = 3, Name = "Social Studies 101"}, new SchoolClasses(){ID = 4, Name = "Spanish 101"} }; var classesPeriod2 = new List<SchoolClasses>() { new SchoolClasses(){ID = 5, Name = "Art 101"}, new SchoolClasses(){ID = 6, Name = "Music 101"}, new SchoolClasses(){ID = 7, Name = "English 101"}, new SchoolClasses(){ID = 8, Name = "Global Studies 101"} }; List<SchoolClasses> results = null; if (id == "1") { results = classesPeriod1.ToList(); } if (id == "2") { results = classesPeriod2.ToList(); } //this.Label1.Text == "You selected a id = " + id; return results; } protected void Button1_Click(object sender, EventArgs e) { var selectedItems = this.ListBox1.Items.Cast<ListItem>().Where(li => li.Selected).ToList(); //The collection is empty?? There were selected items though... } } public class SchoolClasses { public int ID { get; set; } public string Name { get; set; } }
Lastly, we need the jQuery that is wired up to the 'change' event of the DropDownList that will call our new Static event server-side and populate the ListBox with the results.
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script> <script type="text/javascript"> $(document).ready(function () { DropDownList1_OnChangeHandler(); }); function DropDownList1_OnChangeHandler() { // Add the page method call as an change handler for DropDownList1. // This will call the static page method, and use the returned results to populate ListBox1. $('#<%=DropDownList1.ClientID %>').change(function () { var val = $(this).val(); var text = $(this).children("option:selected").text(); var $timePeriod = $('#<%=ListBox1.ClientID %>'); $timePeriod.attr('disabled', 'disabled'); $timePeriod.empty(); $timePeriod.append('<option value="0">< Loading Please Wait... ></option>'); $.ajax({ type: "POST", url: "Default.aspx/DropDownList1_SelectedIndexChanged", contentType: "application/json; charset=utf-8", dataType: "json", data: "{'id':'" + val + "'}", success: function (schoolClasses) { $timePeriod.removeAttr('disabled'); $timePeriod.empty(); if (schoolClasses.d == null) { return; } $.each(schoolClasses.d, function (i, schoolClass) { $timePeriod.append('<option value="' + schoolClass.ID + '">' + schoolClass.Name + '</option>'); }); }, error: function () { $timePeriod.empty(); $timePeriod.append('<option value="0">< Error Loading classes ></option>'); alert('Failed to retrieve classes.'); } }); }); } </script>
If you built this example and ran it, the ListBox would populate as we would like. In fact it's really fast too. Originally I thought, "Home Run!" So what's the problem? Well if we have a simple example this might work, but as we need to add to the page, we begin to run into hurdles.
Let's try to add in (2) more features to highlight what I'm talking about:
- Update a label to state which 'Period' we selected when calling server-side Static method.
- Iterate through the 'Selected Items' collection on submittal of the form
this.Label1.Text == "You selected a id = " + id;
protected void Button1_Click(object sender, EventArgs e) { var selectedItems = this.ListBox1.Items.Cast<listitem>().Where(li => li.Selected).ToList(); //The collection is empty?? There were selected items though... }
So in order to make my AJAX call work to interact with the server using asynchronous postbacks and avoiding the UpdatePanel I have to deal with the following:
- Reduce the page's security by setting EnableEventValidation="false"
- Not be able to see any of the control values I manipulated through their server-side properties.
- Have to dig through Request.Form to manually find updated client values
- Not be able to use Dependency Injection as intended
- Have a disorganized mismatch between the client and server state since the right-hand doesn't know what the left is doing.
<asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional"> <ContentTemplate> <asp:ListBox ID="ListBox1" runat="server" Width="300px" SelectionMode="Multiple" AutoPostBack="True" OnSelectedIndexChanged="ListBox1_SelectedIndexChanged" /> </ContentTemplate> </asp:UpdatePanel>
Hopefully you try all of this out and see the caveats of trying to do AJAX asynchronous postbacks to the server via jQuery before going to far down the road on a new project hoping this will work smoothly. There are as I've shown several 'pitfalls' to be aware of so just make sure you factor them in if going down this route.