Beruflich Dokumente
Kultur Dokumente
<asp:ScriptManager ID="ScriptManager1" runat="server" /> <asp:UpdatePanel runat="server" ID="up1"> <ContentTemplate> <asp:Label runat="server" ID="Label1" Text="Update Me!" /><br /> <asp:Button runat="server" ID="Button1" Text="Postback Update" OnClick="Button1_Click" /> </ContentTemplate> </asp:UpdatePanel>
default.aspx.cs:
protected void Button1_Click(object sender, EventArgs e) { Label1.Text = DateTime.Now.ToLongDateString(); }
Simple enough. Button1 is clicked, an asynchronous request is made for the current date/time, and that is displayed as Label1s content. As simple as it sounds, take a look at the actual HTTP post and response necessary to accomplish the partial postback:
Thats a LOT of data sent back and forth to the web server just to display a 16 character string! This may be acceptable for infrequently used functionality, but, not in a heavily used production system. Luckily, Microsoft has given us a more efficient way to do this, as part of the ASP.NET AJAX framework.
Page Methods
Page methods allow ASP.NET AJAX pages to directly execute a pages static methods using JSON (JavaScript Object Notation). JSON is basically a minimalistic version of SOAP, which is perfectly suited for light weight communication between client and server. For more information about how to implement page methods and JSON, take a look at Microsofts Exposing Web Services to Client Script in ASP.NET AJAX. Instead of performing a partial postback and then receiving HTML markup to completely replace our UpdatePanels contents, we can use a web method to request only the information that were interested in: default.aspx: <asp:ScriptManager ID="ScriptManager1" runat="server"
EnablePageMethods="true" /> <script language="javascript"> function UpdateTime() { PageMethods.GetCurrentDate(OnSucceeded, OnFailed); } function OnSucceeded(result, userContext, methodName) { $get('Label1').innerHTML = result; } function OnFailed(error, userContext, methodName) { $get('Label1').innerHTML = "An error occured."; } </script> <asp:Label runat="server" ID="Label1" Text="Update Me!" /><br /> <input type="button" id="Button2" value="Web Method Update" onclick="UpdateTime();" />
default.aspx.cs:
[WebMethod] public static string GetCurrentDate() { return DateTime.Now.ToLongDateString(); }
Through this method, weve completely eliminated the extra data that was present in the UpdatePanels request, and reduced the response down to just the data were interested in requesting:
Using JSON, the entire HTTP round trip is 16 bytes, as compared to 872 bytes for the UpdatePanel. Thats roughly a 5,000% improvement!!, which will only continue to increase with the complexity of the page. Not only has this reduced our network footprint dramatically, but it eliminates the necessity for the server to instantiate the UpdatePanels controls and take them through their life cycles to render the HTML sent back to the browser. While Im a proponent of the simplicity inherent in the UpdatePanel, I think that it is crucial that we use them judiciously. In any heavy use situation, they are very rarely the best solution.
Ease of Development
As a seasoned WebForms developer, you may be thinking: "Wow, now I have to learn how to use these new fangled javascript libraries." Or, "Now we have to spend a lot of time redoing every page with these new low level javascript functions which we've never used!" But, wait, there is an easy to use alternative that has both high performance and ease of use. I've used JQuery, KendoUI, Knockout, and other high level javascript libraries to create some amazing applications such as a mapping application (allowing drag and drop between 2 maps on a page), Dashboard applications with full graphing capabilities, an Excel-like editable grid, and more, all in a matter of weeks. The power, speed and control you have with these high level javascript libraries are far and above that of standard WebForms development. Your code will be cleaner, and there will be no need to handle control from server side code to client side code, and vice versa, as all your UI coding will be in Javascript. As an example, I created a high performance Auditing 'History' page, which is entirely in Jquery and Javascript to replace an existing History Page in an ASP.NET WebForms application:
The History screen has all the features youd expect in a typical Auditing / History form. Implemented features are: Search / Filter, Sort, Number of entries per page, ToolTips for extra long field data, and a cool feature that allows the user to get rid of NULL-Blank transitions, eliminating unnecessary records being displayed. Your aspx / ascx page would consist of a div which the Jquery code will create the grid from, along with the appropriate Javascript function to call the display grid function:
function showHistory() { var contractkey = $("#<%= hdnCID.ClientID %>").val(); var type = 0; var checked = $("#chkFilterBlank").prop("checked"); displayHistory(type, contractkey, checked); } <div id="divHistory" style="width: 900px; height: 540px; background-color: #EDECEB; display: none;"> <table id="tblHistory" class="display" cellspacing="0" cellpadding="0" border="0" style=" margin: 5px 25px 0px 0px; color:#000000; border: 1px solid #222222;"> </table> <br /> <input type="checkbox" id="chkFilterBlank" checked="checked" name="filterblank" value="filter"/>Filter Blank transitions
<input id="btnRefresh" type="button" class="button-right" value="refresh" runat="server" onclick="showHistory(); return false;" /> </div>
The external history.js javascript file consists of the actual function to create the grid (I used the datatables plugin in this case, which allows fast server-side paging, allowing the history form to be extremely fast (< 1 second response time on any load, search, sort, page, or refresh operation.):
function displayHistory(type, id, filterblank) { try { if (filterblank == undefined || filterblank == null) filterblank = true; showHistoryDialog(); var oTable = $('#tblHistory').dataTable( { "bDestroy": true, "bJQueryUI": true, "bSort": true, "bAutoWidth": true, "bProcessing": true, "bServerSide": true, "sScrollX": "1500px", "sScrollY": "385px", "sPaginationType": "full_numbers", "iDisplayLength": 15, "aLengthMenu": [[10, 15, 25, 50, 100], [10, 15, 25, 50, 100]], "sAjaxSource": "AjaxPage.aspx", //Extra parameters "fnServerParams": function (aoData) { aoData.push({ "name": "type", "value": type }, { "name": "rowId", "value": id }, { "name": "filterBlank", "value": filterblank }, { "name": "CallRequest", "value": "ProcessHistory"}); }, "aoColumnDefs": [ { "sTitle": "Table", "sWidth": "160px", "type": "text", "aTargets": [0] }, { "sTitle": "Column", "sWidth": "110px", "type": "text", "aTargets": [1], "fnCreatedCell": function (nTd, sData, oData, iRow, iCol) { $(nTd).attr('title', oData[6]); } }, { "sTitle": "Old Value", "sWidth": "110px", "type": "text", "aTargets": [2], "fnCreatedCell": function (nTd, sData, oData, iRow, iCol) { $(nTd).attr('title', oData[7]); } }, { "sTitle": "New Value", "sWidth": "110px", "type": "text", "aTargets": [3], "fnCreatedCell": function (nTd, sData, oData, iRow, iCol) { $(nTd).attr('title', oData[8]); } },
"Changed", "sWidth": "195px", "sType": "date", }, "Changed By", "sWidth": "140px", "type": "text", }
Your Server side code could be an .asmx page, .ashx page (.NET Handler), or even an .aspx page. In this case, Ive used an .aspx page which acts the same as a typical codebehind aspx.vb page, except that I dont handle any postbacks, only HTTP Requests. Im using the GET method, as this particular Jquery plug-in (Datatables.net) implements this method when using the server-side paging functionality:
Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load Dim callRequest As String = If((Me.Request("CallRequest") Is Nothing), String.Empty, Me.Request("CallRequest")) Dim dataTable As DataTable = Nothing Dim returnValue As Boolean = False Dim strJson = Nothing If callRequest = "ProcessHistory" Then strJson = ProcessHistory() End If Me.Response.ClearHeaders() Me.Response.Clear() Me.Response.ContentType = "application/json" Me.Response.Write(strJson) Me.Response.[End]() End Sub Public Function ProcessHistory() As String 'Paging parameters: Try Dim iDisplayLength = Integer.Parse(HttpContext.Current.Request.QueryString("iDisplayLength")) Dim iDisplayStart = Integer.Parse(HttpContext.Current.Request.QueryString("iDisplayStart")) ' Sorting parameters Dim iSortCol = Integer.Parse(HttpContext.Current.Request.QueryString("iSortCol_0")) Dim iSortDir = HttpContext.Current.Request.QueryString("sSortDir_0") ' Search parameters
Dim sSearch = HttpContext.Current.Request.QueryString("sSearch") Dim type = Integer.Parse(HttpContext.Current.Request.QueryString("type")) Dim rowId = Integer.Parse(HttpContext.Current.Request.QueryString("rowId")) Dim filterBlank = Boolean.Parse(HttpContext.Current.Request.QueryString("filterBlank")) Dim sEcho = HttpContext.Current.Request.QueryString("sEcho") 'TableHistoryResult Dim history = TryCast(TableAudit.GetTableHistory(DirectCast(type, TableHistoryType), rowId), IEnumerable(Of TableHistoryResult)) If filterBlank = True Then Dim historylist As List(Of TableHistoryResult) = TryCast(history, List(Of TableHistoryResult)) historylist.RemoveAll(AddressOf IsBlankTransition) End If ' TableName ColumnName ' Define an order function based on the iSortCol parameter Dim order As Func(Of TableHistoryResult, Object) = Function(hist) Select Case iSortCol Case 0 Return DirectCast(hist.TableName, Object) Case 1 Return DirectCast(hist.ColumnName, Object) Case 2 Return DirectCast(hist.OldValue, Object) Case 3 Return DirectCast(hist.NewValue, Object) Case 4 Return DirectCast(hist.ChangedDateTime, Object) Case Else Return DirectCast(hist.UserChangedByLoginName, Object) End Select End Function ' Define the order direction based on the iSortDir parameter history = If("desc" = iSortDir, history.OrderByDescending(order), history.OrderBy(order)) sSearch = sSearch.ToLower() ' prepare an anonymous object for JSON serializationhistory = history.Where(Function(h) (h.TableName IsNot Nothing AndAlso h.TableName.ToLower().Contains(sSearch)) OrElse (h.ColumnName IsNot Nothing AndAlso h.ColumnName.ToLower().Contains(sSearch)) OrElse (h.OldValue IsNot Nothing AndAlso h.OldValue.ToLower().Contains(sSearch)) OrElse (h.NewValue IsNot Nothing AndAlso h.NewValue.ToLower().Contains(sSearch)) OrElse (h.UserChangedByLoginName IsNot Nothing AndAlso h.UserChangedByLoginName.ToLower().Contains(sSearch))) Dim aaData2 = history.[Select](Function(h) New With {h.TableName, .ColBlank = h.ColumnName.Substring(0, Math.Min(h.ColumnName.Length, 6)) + (If(h.ColumnName.Length <= 6, "", "...")), _ .OldBlank = If(h.OldValue Is Nothing, "", (h.OldValue.Substring(0, Math.Min(h.OldValue.Length, 6)) + (If(h.OldValue.Length <= 6, "", "...")))), _ .NewBlank = If(h.NewValue Is Nothing, "", (h.NewValue.Substring(0, Math.Min(h.NewValue.Length, 6)) + (If(h.NewValue.Length <= 6, "", "...")))), _
.ChangedDateTime = h.ChangedDateTime.ToString("yyyy-MM-ddTHH:mm:ssZ"), _ h.UserChangedByLoginName, _ h.ColumnName, h.OldValue, h.NewValue}).Skip(iDisplayStart).Take(iDisplayLength) Dim lsthistory As New List(Of String()) For Each historyitem In aaData2 Dim arrHistory As String() = New String(8) {historyitem.TableName, historyitem.ColBlank, historyitem.OldBlank, historyitem.NewBlank, historyitem.ChangedDateTime, historyitem.UserChangedByLoginName, historyitem.ColumnName, historyitem.OldValue, historyitem.NewValue} lsthistory.Add(arrHistory) Next Dim result = New With { _ Key .sEcho = sEcho, _ Key .iTotalRecords = history.Count(), _ Key .iTotalDisplayRecords = history.Count(), _ Key .aaData = lsthistory } Dim json = SerializeToJSON(result) Return json Catch ex As Exception End Try End Function
Note the use of VB.NET, LINQ and anonymous types (VB.NET handles anonymous type declaration differently than C#.). You can simply use a call to your existing data layer, instead of the use of LINQ as I did in this example.