Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic UpdatePanels and UserControls issue

Have come across an interesting issue in ASP.Net WebForms when migrating a project from 3.5 up to 4.5.

The site in question is extremely dynamic - the page is built up based on configuration in a CMS fashion.

However in 4.5 we have a problem – when more content is added into the page via a button click, not all the markup for the content appears. (On testing the problem is reproduced in .net 4.0 as well).

To demonstrate here is a much stripped out example that just uses the default WebForms project template (in VB in this case).

In Default.aspx add the following markup:

<asp:UpdatePanel ID="udpTrigger" runat="server" UpdateMode="Always">
    <ContentTemplate>
        <asp:button id="btnGo" runat="server" Text ="Go" />
    </ContentTemplate>
</asp:UpdatePanel>
<asp:Panel ID="pnlContainer" runat="server">
</asp:Panel>

In Default.aspx.vb add the following code:

Dim _udp As UpdatePanel

Private Sub Page_Init(sender As Object, e As EventArgs) Handles Me.Init    
    _udp = New UpdatePanel()
    _udp.ID = "udpTarget"
    _udp.UpdateMode = UpdatePanelUpdateMode.Conditional

    pnlContainer.Controls.Add(_udp)        
End Sub

Private Sub btnGo_Click(sender As Object, e As EventArgs) Handles btnGo.Click    
    Dim ctrl = LoadControl("Control.ascx")        

    Dim pnlWrapper = New Panel With {.ID = "pnlWrapper"}
    pnlWrapper.Controls.Add(ctrl)

    _udp.ContentTemplateContainer.Controls.Add(pnlWrapper)        
    _udp.Update()            
End Sub

Note: There is a wrapping panel here between the user control and the update panel. This serves to demonstrate which markup is missing on output.

Create a Control.ascx and add the following:

<asp:Panel ID="pnlControl" runat="server"></asp:Panel>

Once we click the btnGo control, the wrapper panel and control.ascx should be added to the page.

In .Net 3.5 that is exactly what happens:

<div id="pnlContainer">                    
    <div id="udpTarget">
        <div id="pnlWrapper">
            <div id="ctl05_pnlControl">
            </div>
        </div>
    </div>
</div>

In .Net 4.5, the wrapping panel doesn't appear - just the user control:

<div id="MainContent_pnlContainer">          
    <div id="MainContent_udpTarget">
       <div id="MainContent_ctl02_pnlControl">
       </div>
    </div>
</div>

The problem doesn't occur if the update panel is in the markup of the webpage, however that's not possible in this case.

Switching which LoadControl overload is used (to LoadControl(type,params) produces the wrapper panel, but no markup - there appears to be a separate issue with this).

Checking the response body in fiddler2 shows that the wrapper panel is omitted at server side (ie we're not losing it in the client side ajax processing)

So is there any workaround, or even some sort of fix patch for this behaviour - it does seem to be a breakage in .Net 4 given that it was fine in 3.5.

Have also now posted this on my blog here to collect any attempts I make to get a fix or workaround.

Update

Following a pointer from @jadarnel27 that the below steps did not reproduce the problem on VS2010, I've tried the steps on a couple of machines away from work.

  • First off another VS2013 machine: Did recreate the issue.
  • Next, my old dev machine, running VS2012 Express For Web: Did not reproduce the problem

So it looks at this stage like the problem is restricted to VS2013. Next to try a few different settings in VS2013.

Have now posted this to Microsoft Connect here.

like image 484
Jon Egerton Avatar asked Nov 10 '22 16:11

Jon Egerton


1 Answers

Workaround

I have a work around for this now. I'm using a UserControlLoader server control to wrap the user control, and render it separately by overriding RenderContents

The code for UserControlLoader is:

''' <summary>
''' UserControl Loader - loads a user control
''' Works around a problem with ASP.Net Webforms in 4.0/4.5
''' </summary>
<ToolboxData("<{0}:UserControlLoader runat=server></{0}:UserControlLoader>")> _
Public Class UserControlLoader
    Inherits WebControl

    Public ReadOnly Property Control As Control
        Get
            Return _control
        End Get
    End Property
    Private _control As Control

    Public Sub LoadControl(page As Page, virtualPath As String)
        _control = page.LoadControl(virtualPath)
        Me.Controls.Add(_control)
    End Sub

    Public Overrides Sub RenderBeginTag(writer As HtmlTextWriter)
        'Don't render anything
    End Sub

    Public Overrides Sub RenderEndTag(writer As HtmlTextWriter)
        'Don't render anything
    End Sub

    ''' <summary>
    ''' Overrides RenderContent to write the content to a separate writer,
    ''' then adds the rendered markup into the main HtmlTextWriter instance.
    ''' </summary>
    Protected Overrides Sub RenderContents(ByVal writer As HtmlTextWriter)

        If _control Is Nothing Then Return

        Using sw = New StringWriter()
            Using hw = New HtmlTextWriter(sw)

                MyBase.RenderContents(hw)

                writer.Write(sw.ToString)

            End Using
        End Using

    End Sub

End Class

Usage:

To implement this into the recreation steps listed, the btnGo event handler becomes:

Private Sub btnGo_Click(sender As Object, e As EventArgs) Handles btnGo.Click

    Dim loader = new UserControlLoader()
    loader.LoadControl(Page,"Control.ascx")     

    Dim pnlWrapper = New Panel With {.ID = "pnlWrapper"}
    pnlWrapper.Controls.Add(loader)

    _udp.ContentTemplateContainer.Controls.Add(pnlWrapper)        
    _udp.Update() 

End Sub
like image 176
Jon Egerton Avatar answered Nov 29 '22 03:11

Jon Egerton