n-Tier Architecture Introduction – Part 2

July 29, 2011 02:19 by wjchristenson2

In my previous article (part 1), I shared what an n-Tier architected application is and why you should familiarize yourself with the concepts behind them.  In this article, I will take this a step further and show you how you can create a simple n-Tier architected ASP.NET application using Visual Studio 2010.  I won’t create all the CRUD (create, read, update, delete) operations for the business objects, but it should give you a strong start to creating a simple multi-tiered ASP.NET application.

When we are finished, your Visual Studio solution will have the following layers:
1) SalesSystem.Web - ASP.NET website
2) SalesSystem.Presentation - Presentation Controllers
3) SalesSystem.BusinessObjects – Business Logic Layer (Objects)
4) SalesSystem.Core – Core Utility Objects

We will start by creating an empty Visual Studio solution and then adding projects to it.  Each project acts as a logical layer (tier).  Projects can reference each other via the use of project references.  When you build the Visual Studio solution, it will build each project for you automatically.  Before I show how to create an empty solution and add projects (logical layers/tiers) to it, here’s a brief explanation of what each project will do.

SalesSystem.Core – I used this project to contain generic utilities, functions, extensions, helpers, etc which can be used across any new application you may develop.  Think big.  You don’t want to reinvent the wheel if you don’t have to.  I thought I may be developing more web applications in the future, so a common task is reading connection strings from the web.config.  So I wrote a utility object for the web.config and put a base class to be inherited on all web pages in there too.  This project/assembly is used by each of our other tiers.

SalesSystem.BusinessObjects – I made this project to house all of my business objects.  I didn’t take the time to create an official DataAccess Layer… but business objects often are made to interact with one particular data source type.  In this case I am interacting with a SQL Server and the connection string to run the queries on is passed in via a connection string to still maintain a dynamic and flexible approach.  Regardless of merging the business objects in with data access routines, I am able to use these business objects going forward easily as long as the application (whatever type it is) supports CRUD operations on a SQL Server.

SalesSystem.Presentation – Okay, this project may be a bit confusing for most.  I made another tier/layer to act as the liason between the UI and business objects.   I have a couple of reasons for this.  First, it makes calls simpler from ASP.NET page code behinds.  An ASP.NET page can have one controller which does a myriad of tasks (page centric) on many types of objects.  Second, it will help with automated unit testing in the future if we decided to take it to that level.  Your presentation layer should essentially be presenting the UI and capturing data from the user.  The less “clutter” you can put in the presentation the better.  So using presentation controllers can assist here.

SalesSystem.Web – This project is the presentation layer and we are using an ASP.NET website to do this.  Our objective in this layer is to simplify the logic down to presenting and capturing data from the user.  Any business object acquisition or saving should be done through the controller.  This makes the UI code much cleaner and simpler.

Here’s what the final solution will look like:

Step 1: Create an Empty Solution – First we want to create an empty Visual Studio solution so we can add our projects (layers/tiers) to it.  Below is a screenshot on how to do this.

Step 2: Add Projects to the Solution – Next we want to add projects for each tier we want to our solution.  We have 4 layers that we want to develop, so we’ll add 4 projects to the solution which will facilitate logical layers.  Each layer may be a different project type.  For instance, the presentation tier (SalesSystem.Web) will be an ASP.NET web project.  However our core, presentation controller, and business projects will all be windows class libraries as they simply have .NET classes in them.  Below are screenshots to help facilitate how to add the projects to our empty solution.

Step 3: Add Project References – The final step to getting your ASP.NET n-Tier architected solution to work is to setup project references to each tier can communicate, consume, and/or interact with each other.  Think of project references as “inheritance”.  If you need to use a tier’s (project’s) functions or data types, you need to add a project reference (or inherit) from it.

SalesSystem.Web References - We know that SalesSystem.Web will need to make calls to the SalesSystem.Presentation controllers which will return objects from SalesSystem.BusinessObjects.  So the SalesSystem.Web will need project references to both projects.  It will also need a project references to SalesSystem.Core because that is our core project which we have our core objects & utilities that we can use everywhere.

SalesSystem.Presentation References – This tier doesn’t care about the UI.  It makes calls to the business object layer.  Therefore it only needs a reference to SalesSystem.BusinessObjects.  Again, add a reference to SalesSystem.Core as well.

SalesSystem.BusinessObjects References – This tier is only concerned with its own business objects and interacting with the database.  It doesn’t care about what presentation layer is employed to use them… so the only references you need is to SalesSystem.Core.

Here is how you add a project reference:

Step 4: Code – I’m not going to go into detail on how to code the tiers.  Instead I am attaching the solution which includes the code in each tier to acquire customer sales from the AdventureWorks database using our n-Tiered solution.  The end deliverable is an ASP.NET website which a page’s codebehind class makes a call to our presentation controller tier which makes calls to our business objects and back out again.

Summary - Imagine taking this application to the next level and offering a Windows WPF UI option.  We could reuse the same presentation controllers, core, and business object tiers.  We would simply replace the ASP.NET web presentation tier and most of our work would already be done.  If a call to the database was broke, we’d fix it in one business object and be done.  One place, one time.  We wouldn’t have to be searching through all code-behind files trying to locate every instance of the problem.  If you had a UI designer/coder, they could be designing the look of the web pages while you focused on the business objects and back-end tiers (parallelism).  There are many reasons why applications are constructed like this.

Hopefully this helps you get a good start to understanding n-Tier architected applications and how you can accomplish this in Visual Studio using logical layers with VS projects and project references.

n-Tier Architecture – Part 1
n-Tier Architecture – Part 2

SalesSystem_Soln.zip (792.10 kb)

Bookmark and Share

ASP.NET MVC Overview Part 2

February 14, 2010 08:36 by wjchristenson2

In Part1, I discussed the basics of what the MVC pattern is and how Microsoft has incorporated MVC into ASP.NET.  In this segment, I am going to discuss some general advantages of ASP.NET Web Forms and ASP.NET MVC.  Neither flavor of ASP.NET will be the superior choice in all circumstances.  Therefore knowing the advantages to each will help.

I’ve found through my development career that there’s never a right or wrong way to do something.  Scott Guthrie posted recently the following which is very true, “Great developers using bad tools/frameworks can make great apps.”  The same principal applies to bad developers using great tools/frameworks… inevitably they will create bad applications.  Just because one framework may shine better in certain situations may not mean its better.  If the developers using the superior technology cannot grasp or develop it properly, its full potential will not be realized.  So keep these principals in mind when evaluating the two.

Advantages of ASP.NET MVC
1) Separation of model, view, controller results in reduction of complexity, promotes parallel development, and easier to maintain.
2) Enables full control over rendered HTML (leaner html rendering).
3) Works much better with automated testing as all core contracts are interface based.  You can run unit-tests without having to run the controllers under an ASP.NET process.
4) Does not use view state or postbacks (no view stage = quicker load times).
5) Framework is very extensible and pluggable.
6) Supports existing markup (aspx, master pages, ascx, inline expressions).
7) Supports existing session, caching, authentication, etc.
8) Easy integration with javascript frameworks.
9) Follows the design of a stateless web.


Advantages of ASP.NET Web Forms
1) Commonality between windows forms development in that it uses the event model.
2) Usage of view state and server forms helps manage state more easily.
3) Promotes RAD more so than MVC as more controls/components are available.
4) Less complex than MVC and generally requires less code.
5) More mature as it’s been around since the 1990’s.

ASP.NET MVC Overview Part 1 (General ASP.NET MVC Overview)
ASP.NET MVC Overview Part 2 (Advantages of MVC and Web Forms)
ASP.NET MVC First Impressions

Bookmark and Share

GridView Column Sorting - Up/Down Arrows

December 7, 2008 14:32 by wjchristenson2

In this article, I will show you how to manually sort the GridView WebControl and display sort direction arrows.  The GridView has built-in sorting capabilities, however if we want visual feedback as to what column is being sorted and to what direction, we have to perform this ourselves.  While extending the GridView WebControl would be optimal, I'm going to show a quick way to get it done without creating a new GridView control.  Maybe in a future post I'll show how we can create a custom GridView control with sort arrows.  Here is a picture of what our final sorted GridView will look like.

Here is the HTML markup of our GridView:

<asp:GridView 
  ID="GridView1" 
  runat="server" 
  AutoGenerateColumns="False" 
  DataKeyNames="CustomerID" 
  CssClass="gridview" 
  RowStyle-CssClass="gridview_itm" 
  AlternatingRowStyle-CssClass="gridview_aitm" 
  HeaderStyle-CssClass="gridview_hdr" 
  PagerStyle-CssClass="gridview_pgr">
  <Columns>
    <asp:TemplateField>
      <HeaderTemplate>
        <asp:LinkButton ID="CustomerID_SortLnkBtn" runat="server" Text="Customer ID:" ToolTip="Click to Sort Column" CommandName="Sort" CommandArgument="CustomerID" CausesValidation="false" />
        <asp:ImageButton ID="CustomerID_SortImgBtn" runat="server" Visible="false" ToolTip="Click to Sort Column" CommandName="Sort" CommandArgument="CustomerID" CausesValidation="false" />
      </HeaderTemplate>
      <ItemTemplate><%#Eval("CustomerID")%></ItemTemplate>
    </asp:TemplateField>
                
    <asp:TemplateField>
      <HeaderTemplate>
        <asp:LinkButton ID="CompanyName_SortLnkBtn" runat="server" Text="Company Name:" ToolTip="Click to Sort Column" CommandName="Sort" CommandArgument="CompanyName" CausesValidation="false" />
        <asp:ImageButton ID="CompanyName_SortImgBtn" runat="server" Visible="false" ToolTip="Click to Sort Column" CommandName="Sort" CommandArgument="CompanyName" CausesValidation="false" />
      </HeaderTemplate>
      <ItemTemplate><%#Eval("CompanyName")%></ItemTemplate>
    </asp:TemplateField>
                
    <asp:TemplateField>
      <HeaderTemplate>
        <asp:LinkButton ID="ContactName_SortLnkBtn" runat="server" Text="Contact Name:" ToolTip="Click to Sort Column" CommandName="Sort" CommandArgument="ContactName" CausesValidation="false" />
        <asp:ImageButton ID="ContactName_SortImgBtn" runat="server" Visible="false" ToolTip="Click to Sort Column" CommandName="Sort" CommandArgument="ContactName" CausesValidation="false" />
      </HeaderTemplate>
      <ItemTemplate><%#Eval("ContactName")%></ItemTemplate>
    </asp:TemplateField>
  </Columns>
</asp:GridView>

The first step is to acquire customers from the Northwind database in the form of a DataTable.  We will then acquire a DataView object from our DataTable, and sort the view.  Once the data in our DataView has been sorted, we will then bind the GridView to the sorted DataView.  To accomplish the data retrieval, sorting, and data binding, I've created the following method:

Private Sub GridView1_DataBind()
    Dim dt As DataTable = New DataTable()

    'fill our datatable w/ customers from the DB
    Using conn As New SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings("NorthwindConnectionString").ConnectionString)
        Dim sql As String = "SELECT [CustomerID], [CompanyName], [ContactName] FROM [Customers] WITH (NOLOCK)"
        Dim cmd As SqlCommand = New SqlCommand(sql, conn)
        Dim reader As SqlDataReader = Nothing

        Try
            conn.Open()
            reader = cmd.ExecuteReader(CommandBehavior.CloseConnection)
            dt.Load(reader)
        Finally
            If Not reader Is Nothing AndAlso Not reader.IsClosed Then
                reader.Close()
            End If
        End Try
    End Using

    If dt.Rows.Count > 0 Then
        'get a dataView object from our dataTable of customers
        Dim dv As DataView = dt.DefaultView

        'if the user has elected to sort the gridview
        If Not String.IsNullOrEmpty(Me.SortBy("GridView1")) Then
            'get the sort expression and apply it to our dataView
            Dim sortExpr As String = Me.SortBy("GridView1") & " " & IIf(Me.SortDirection("GridView1") = WebControls.SortDirection.Ascending, "ASC", "DESC").ToString()
            dv.Sort = sortExpr
        End If

        'bind the dataView to our GridView
        Me.GridView1.DataSource = dv
        Me.GridView1.DataBind()
    End If
End Sub

The logic is pretty straight forward.  Take note to line 28.  I am using 2 properties to store what column I am sorting by and what direction it is being sorted.  I persist the values in the ViewState and I also pass what GridView I either want to retrieve or store values for.  This allows me to have more that 1 sorting GridView on my page at a time using the same 2 properties.  Here's the code for the 2 properties to assist us with sorting.

''' <summary>
''' Gets or sets the column name to be sorted.
''' </summary>
''' <param name="GridViewID">The unique ID of the GridView.</param>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Private Property SortBy(ByVal GridViewID As String) As String
    Get
        Dim o As Object = ViewState(GridViewID & "_SortBy")
        If Not o Is Nothing Then
            Return o.ToString()
        Else
            Return String.Empty
        End If
    End Get
    Set(ByVal value As String)
        ViewState(GridViewID & "_SortBy") = value
    End Set
End Property

''' <summary>
''' Gets or sets the sort direction.
''' </summary>
''' <param name="GridViewID">The unique ID of the GridView.</param>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Private Property SortDirection(ByVal GridViewID As String) As SortDirection
    Get
        Dim o As Object = ViewState(GridViewID & "_SortDirection")
        If Not o Is Nothing Then
            Return DirectCast(o, SortDirection)
        Else
            Return WebControls.SortDirection.Ascending
        End If
    End Get
    Set(ByVal value As SortDirection)
        ViewState(GridViewID & "_SortDirection") = value
    End Set
End Property

We have the sort by and sort direction properties (storing/persistance mechanisms).  We have the data retrieval, sorting of the data, and data binding method in place.  Now what we have to do is think about what events we need to account for.  First, on initial page load we'll want to fetch customer data and bind it to our GridView.  We'll only want to bind our GridView the first time the page loads and not subsequent postbacks.

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    If Not Page.IsPostBack Then
        'bind the gridview on page first load
        GridView1_DataBind()
    End If
End Sub

Now we are ready to make the magic happen.  We want to handle the GridView's RowDataBound event and either show or hide our up/down arrows if the user has elected to sort a column.

Private Sub GridView1_RowDataBound(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs) Handles GridView1.RowDataBound
    'if the row being dataBound is the header row - toggle sort image visibility/directions
    If e.Row.RowType = DataControlRowType.Header Then
        ToggleSortArrows(e.Row, "GridView1")
    End If
End Sub

Private Sub ToggleSortArrows(ByVal headerRow As GridViewRow, ByVal gridViewID As String)
    Dim sortImgBtn As ImageButton = Nothing

    'loop through each cell in the header row
    For Each tc As TableCell In headerRow.Cells
        'loop through each control in the cell
        For Each c As Control In tc.Controls
            'if the control is an image button and is our sort image button
            If TypeOf c Is ImageButton AndAlso c.ID.EndsWith("SortImgBtn") Then
                sortImgBtn = DirectCast(c, ImageButton)

                'if the image button is in the column being sorted
                If Me.SortBy(gridViewID) = sortImgBtn.ID.Split(CChar("_"))(0) Then
                    'show the image button and set its image url (sorted column)
                    sortImgBtn.Visible = True
                    If Me.SortDirection(gridViewID) = WebControls.SortDirection.Ascending Then
                        sortImgBtn.ImageUrl = "~/img/uparrow.gif"
                    Else
                        sortImgBtn.ImageUrl = "~/img/dnarrow.gif"
                    End If
                Else
                    'hide the image button (not a sorted column)
                    sortImgBtn.Visible = False
                End If
            End If
        Next
    Next
End Sub

Basically what we are doing is detecting if the row being DataBound is the header row or not.  If it is, we want to loop through each cell in the header row and get a handle on the column's associated sort image.  We use the ID of the sort image to acquire what column it represents and compare it to our SortBy property.  If it matches, then we want to show the appropriate sort direction image.  We hide the other sort images in non-sorted columns.

The only task we have left to account for is how to fire our sorting event.  Take a quick look at our GridView HTML markup.  The header row has both a LinkButton and ImageButton that raise a GridView command event to which we pass the column name that the user wants to sort by.  We then handle the event in our code behind.

Private Sub GridView1_RowCommand(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewCommandEventArgs) Handles GridView1.RowCommand
    If e.CommandName.ToUpper = "SORT" Then
        InitializeSort(e.CommandArgument, "GridView1")
        GridView1_DataBind()
    End If
End Sub

Private Sub InitializeSort(ByVal sortBy As String, ByVal gridViewID As String)
    If Me.SortBy(gridViewID) = sortBy Then
        If Me.SortDirection(gridViewID) = WebControls.SortDirection.Ascending Then
            Me.SortDirection(gridViewID) = WebControls.SortDirection.Descending
        Else
            Me.SortDirection(gridViewID) = WebControls.SortDirection.Ascending
        End If
    Else
        Me.SortBy(gridViewID) = sortBy
        Me.SortDirection(gridViewID) = WebControls.SortDirection.Ascending
    End If
End Sub

Once we capture our sort row command, we initialize our SortBy and SortDirection properties.  We either toggle the direction of the sorted column or the sorted column is a new column to be sorted to which we default the column to be sorted Ascending.

I hope this article helped a bit.  It's a quick way to get a GridView sorted with visual indicators (sort arrows) without creating a new custom GridView control.

GridViewSorting_Soln.zip (90.61 kb)

Bookmark and Share

Save Scroll Position GridView Control

October 29, 2008 11:09 by wjchristenson2

I've seen a lot of articles on the web describing how to scroll a GridView.  Some of those articles may even go a little deeper and show you how to persist the scroll position across PostBacks.  What I haven't been able to find is how to encapsulate the scrolling and saving/persisting of the scroll position into a custom GridView control.  That is what I am going to show you how to do today.

There are many ways a developer may want to scroll their GridView (horizontal, vertical, freezing the header/footer, etc).  I'm not going to go into creating a bullet proof GridView control to accomplish those tasks.  My objective is to show you a technique to save/set the scroll position across PostBacks within a custom GridView control.

Here is what our end GridView control will look like.  First you can see a scrollable GridView.  When we scroll the GridView and then click the PostBack button, the page will perform a PostBack and the <div> will scroll itself back to where we left off.



So let's get to it.  First thing we do is create a new control and inherit from the GridView.  We then implement the IPostBackDataHandler to get and set our scroll position across PostBacks.  You'll see the LoadPostData function accomplishes this.  Take note that in line 3 I am referencing the name of my hidden field that I'll go over later.  We also tell the page that our new custom GridView (Me) has data to PostBack and we can do this in the GridView's Init.

Public Function LoadPostData(ByVal postDataKey As String, ByVal postCollection As System.Collections.Specialized.NameValueCollection) As Boolean Implements System.Web.UI.IPostBackDataHandler.LoadPostData
    'get and set the new scroll position
    Dim postedValue As String = postCollection(Me.ClientID & "_scroll")
    If Not postedValue Is Nothing Then
        Dim presentValue As Integer = Me.ScrollPosition
        Me.ScrollPosition = CType(postedValue, Integer)
        Return Not presentValue.Equals(Me.ScrollPosition)
    End If
End Function

Public Sub RaisePostDataChangedEvent() Implements System.Web.UI.IPostBackDataHandler.RaisePostDataChangedEvent
    'do nothing
End Sub

Private Sub ScrollingGridView_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Init
    If Not Me.Page Is Nothing Then
        'indicates that the control has data to postback
        Me.Page.RegisterRequiresPostBack(Me)
    End If
End Sub

In order to scroll my new custom GridView, I need to wrap my GridView with a <div> tag.  To do this, I can override the Render method like so:

Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter)
    If Not Me.Page Is Nothing Then
        Me.Page.VerifyRenderingInServerForm(Me)
    End If

    Me.PrepareControlHierarchy()

    If Not Me.DesignMode Then
        If String.IsNullOrEmpty(Me.ClientID) Then
            Throw New HttpException("ScrollingGridView must be parented!")
        End If

        writer.AddAttribute(HtmlTextWriterAttribute.Id, String.Format("{0}_div", Me.ClientID), True)
        writer.AddAttribute("onScroll", "saveScrollPos('" & String.Format("{0}_scroll", ClientID) & "', '" & String.Format("{0}_div", Me.ClientID) & "');")
        writer.AddStyleAttribute(HtmlTextWriterStyle.OverflowY, "auto")
        writer.AddStyleAttribute(HtmlTextWriterStyle.Height, "300px")
        writer.RenderBeginTag(HtmlTextWriterTag.Div)
    End If

    Me.RenderContents(writer)

    If Not Me.DesignMode Then
        writer.RenderEndTag()
    End If
End Sub

Notice that when I render my <div> that I also wire in a call to my JavaScript function to save the scroll position "onScroll".  At this point I have completed the PostBack data handling and I have shown you how to wrap a <div> around our GridView so we can scroll and it will call a JavaScript function "onScroll" that will save the position to a hidden field.

The next phase is to emit our JavaScripts.  I always emit JavaScript in the PreRender phase of a control's lifecycle as the ClientID should be set at that point.  We need a function to set the scroll position and a function to save the scroll position.  Here they are:

'generate & register javascript blocks
Dim key As String = "ScrollingGridView"
Dim script As StringBuilder = New StringBuilder()
With script
    .AppendLine("function saveScrollPos(whereID, whatID) {")
    .AppendLine("  document.getElementById(whereID).value = document.getElementById(whatID).scrollTop;")
    .AppendLine("}")
    .AppendLine("function setScrollPos(whereID, whatID) {")
    .AppendLine("  document.getElementById(whatID).scrollTop = (document.getElementById(whereID).value.length > 0) ? document.getElementById(whereID).value : 0;")
    .AppendLine("}")
End With

If Not sm Is Nothing Then
    ScriptManager.RegisterStartupScript(Me.Page, Me.GetType(), key, script.ToString(), True)
Else
    Me.Page.ClientScript.RegisterStartupScript(Me.GetType(), key, script.ToString(), True)
End If

Notice we either acquire or save the scroll position to our hidden field.  Now how do we emit a hidden field inside our GridView?  My first attempts were to override the Render of the GridView control and emit hidden field there.  It didn't work.  I ended up registering the hidden field via .NET's scripting objects instead.  Check it out:

'register our hidden field to save our scroll position
If Not sm Is Nothing Then
    ScriptManager.RegisterHiddenField(Me.Page, String.Format("{0}_scroll", Me.ClientID), Me.ScrollPosition.ToString())
Else
    Me.Page.ClientScript.RegisterHiddenField(String.Format("{0}_scroll", Me.ClientID), Me.ScrollPosition.ToString())
End If

Notice that I assign the scroll position to the hidden field's value.  Last thing we need to do is register a startup script to get the persisted scroll position from our hidden field and scroll our <div>.  If you remember we created a JavaScript function for this (setScrollPosition).

'generate & register startup javascripts
key = String.Format("{0}_setScrollPos", Me.ClientID)
script = New StringBuilder()
script.AppendLine("setScrollPos('" & String.Format("{0}_scroll", Me.ClientID) & "','" & String.Format("{0}_div", Me.ClientID) & "');")
If Not sm Is Nothing Then
    ScriptManager.RegisterStartupScript(Me.Page, Me.GetType(), key, script.ToString(), True)
Else
    Me.Page.ClientScript.RegisterStartupScript(Me.GetType(), key, script.ToString(), True)
End If

Putting it all Together:

In summary, we persist the scroll position of the div that wraps our GridView via a hidden field which is registered via the Page.ClientScript or ScriptManager object.  We wrap the GridView with a <div> by overriding the GridView's Render method.  The <div> saves the scroll position to the hidden field as the <div> is scrolled.  We acquire the value stored in the hidden field via the LoadPostData function.  Once a PostBack occurs, we set the scroll position of the <div> with a startup script.


ScrollingGridView_Soln.zip (97.19 kb)
Bookmark and Share

Show UpdateProgress when using an UpdatePanel with Triggers

August 23, 2008 09:05 by wjchristenson2

with ASP.NET AJAX Extensions 1.0 for ASP .NET 2.0...

You may have come across a situation where an UpdatePanel performs a partial-page update and the associated UpdateProgress control does not display.  The culprit is UpdatePanel AsyncPostbackTriggers.  If an UpdatePanel's partial-page update was triggered by a control outside the UpdatePanel, the UpdateProgress control is oblivious to this fact.  The workaround that I've found is to get a handle to the instance of the PageRequestManager class and add our own events to show and hide the UpdateProgress ourselves via JavaScript before and after the triggered request.

We are going to create a page that will add 2 numbers and show the calculated result.  We will have a TextBox for number 1, a TextBox for number 2, and a label to show the result all within an UpdatePanel.  The calculate Button will be outside the UpdatePanel and will thus be our AsyncPostBackTrigger.  We then have our UpdateProgress control that we want shown while the webpage calculates our results.  Here is the HTML markup to accomplish our base page.

<asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional">
  <Triggers>
    <asp:AsyncPostBackTrigger ControlID="Button1" EventName="Click" />
  </Triggers>
  <ContentTemplate>
    <asp:TextBox ID="tbxValue1" runat="server" Text="1" Width="50" /> + <asp:TextBox ID="tbxValue2" runat="server" Text="1" Width="50" /> = <asp:Label ID="lblResult" runat="server" Text="2" />
  </ContentTemplate>
</asp:UpdatePanel>
         
<asp:Button ID="Button1" runat="server" Text="Calculate" />
          
<asp:UpdateProgress ID="UpdateProgress1" runat="server" AssociatedUpdatePanelID="UpdatePanel1" DynamicLayout="true" DisplayAfter="0">
  <ProgressTemplate>
    <div style="background-color: #ffffc9;">Calculating...<div>
  </ProgressTemplate>
</asp:UpdateProgress>

Now that we have our markup finished, let's handle the click event of our calculate button.  To see the UpdateProgress control for a longer duration, I went ahead and added a sleep timer of 3 seconds to delay the calculation.

Private Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click
    System.Threading.Thread.Sleep(3000)
    lblResult.Text = (CInt(tbxValue1.Text) + CInt(tbxValue2.Text)).ToString()
End Sub

Now for the actual meat of this post.  The following JavaScript calls the getInstance() method of the PageRequestManager object to get the instance of the PageRequestManager class.  We then add our functions to be called when the the request is initialized and ended.  This will allow us to show our UpdateProgress control when the request is initialized and then hide it again when the request is ended.  Here is the JavaScript to accomplish this.

  <script type="text/javascript" language="javascript">
  <!-- 
   
  var prm = Sys.WebForms.PageRequestManager.getInstance();
  var postBackElement;
   
  function CancelAsyncPostBack() {
    if (prm.get_isInAsyncPostBack()) {
      prm.abortPostBack();
    }
  }
   
  prm.add_initializeRequest(InitializeRequest);
  prm.add_endRequest(EndRequest);
   
  function InitializeRequest(sender, args) {
    if (prm.get_isInAsyncPostBack()) {
        args.set_cancel(true);
    }
    postBackElement = args.get_postBackElement();
    if (postBackElement.id == 'Button1') {
      $get('UpdateProgress1').style.display = 'block'; 
    }
  }
  function EndRequest(sender, args) {
    if (postBackElement.id == 'Button1') {
      $get('UpdateProgress1').style.display = 'none';
    }
  }
   
  // -->
  </script>

Note that we acquire what element fired the request.  We only want to show/hide our UpdateProgress control if the postBackElement was our calculate button.

 

TriggersUpdateProgress_Soln.zip (45.30 kb)

Bookmark and Share