asp.net UpdatePanel with UserControls and Page Level Async Method - asp.net

I have an asp.net page which has amoungst some basic labels etc, and an updatepanel containing a dynamic list of UserControls. Each control has its own UpdatePanel, and basic controls. I have hatched together a collaps/expand feature for these controls and has been working fine to date.
To make the page more efficient I have been working to 'Async-ify' the heavy data processing that happens during load, to display in these fields and user controls.
After all of these changes everything still works fine (including a massive performance boost), except one thing - the collapse/expand no longer works. The limited data viewable in collapsed mode' has updated fine.
Reverting back to a non async call for the data, and everything works fine again, but slow again.
It is like they are constantly forced back to their original default state (collapsed), on each postback, triggered by the collapse/expand button. But nothing has changed from this perspective. I have literally only changed the data call to await async instead of non-async, which as i overstate, is fine for everything except this issue.
I can post any code, but not sure what is relevant. The only change I have made is as follows:
Protected Async Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Update()
End Sub
Private Async Function Update() As Task
Dim AController As New CIP_WS.ARFController
ARF = AController.GetARFWithResults(Client.ClientID, PRN, Nothing, StartTime, EndTime, CIP_WS.LabResultController.InequalityModes.AsIs)
PrintBasic()
PrintSPC()
PrintPhotos()
PrintResults()
PrintHistoricEdits()
End Function
Above works fine - non async
Protected Async Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
RegisterAsyncTask(New PageAsyncTask(Function() Update()))
End Sub
Private Async Function Update() As Task
Dim AController As New CIP_WS.ARFController
Dim ARFTask As Task(Of CIP_WS.ARF) = AController.GetARFWithResultsAsync(Client.ClientID, PRN, Nothing, StartTime, EndTime, CIP_WS.LabResultController.InequalityModes.AsIs)
ARF = Await ARFTask
PrintBasic()
PrintSPC()
PrintPhotos()
PrintResults()
PrintHistoricEdits()
End Function
Above, everything works fine, except usercontrol's not persisting their updated collapsed/expanded state.
Any help most appreciated
EDIT _____
Expand/Collaps method, as requested:
Partial Class wucLabResultPack
Inherits System.Web.UI.UserControl
Private _Expanded As Boolean
Public Property Expanded() As Boolean
Get
Return _Expanded
End Get
Set(ByVal value As Boolean)
_Expanded = value
ViewState("Expanded") = _Expanded
UpdateExpandPanel()
End Set
End Property
Protected Sub imgExpand_Click(ByVal sender As Object, ByVal e As System.Web.UI.ImageClickEventArgs) Handles imgExpand.Click
ToggleExpanded()
End Sub
Private Sub ToggleExpanded()
Expanded = Not Expanded
End Sub
Private Sub UpdateExpandPanel()
If Expanded Then
pnlDetail.Visible = True
imgExpand.ImageUrl = "Images/Shrink.png"
Else
pnlDetail.Visible = False
imgExpand.ImageUrl = "Images/Expand.png"
End If
End Sub
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If IsPostBack Then
Expanded = CInt(ViewState("Expanded"))
End If
End Sub

Related

Run after Init, before Page_Load

After a PostBack caused by ddlPlant_SelectedIndexChanged, I need to set set HttpContext.Current.Session("PlantNumber"). This needs to happen after ddlPlant loads in Site.Master, but before the code in Default.aspx needs the value.
Public Class SiteMaster Inherits MasterPage
Protected Sub Page_Init(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Init
If (Not Page.IsPostBack) Then
ddlPlant.DataSource = myDataSource
ddlPlant.DataBind()
ddlPlant.SelectedValue = "1"
End If
HttpContext.Current.Session("PlantNumber") = ddlPlant.SelectedValue
End Sub
Protected Sub ddlPlant_SelectedIndexChanged(ByVal sender As Object, ByVal e As EventArgs) Handles ddlPlant.SelectedIndexChanged
End Sub
End Class
Public Class _Default Inherits Page
Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
Dim units As New List(Of EquipmentModel)
For Each unit As EquipmentModel In getUnits.Out.Results
If (CStr(unit.Plant_ID) = HttpContext.Current.Session("PlantNumber")) Then
units.Add(unit)
End If
Next
gvEquipmentUnit.DataSource = units.OrderBy(Function(n) n.Equipment_ID)
gvEquipmentUnit.DataBind()
End Sub
With the code above, when Session("PlantNumber") is set after PostBack, ddlPlant.SelectedIndex = Nothing, and ddlPlant.SelectedValue is an empty string.
I've tried moving the Session("PlantNumber") = ddlPlant.SelectedValue line to Site.Master's Page_Load instead, but that runs after it is needed in Default.aspx.vb
I looked up PreLoad, but apparently it doesn't work for the Master page.
Ultimately, I decided not to use a Session variable, and instead call the control directly from any page that needs that value on load (almost all of them):
DirectCast(Master.FindControl("ddlPlant"), DropDownList).SelectedValue

Web Controls Not Talking to Each other in ASP.NET using VB.NET

I have an asp.net page that loads two controls, Control A and Control B. Control A has some generic form submit and clear buttons that trigger click events in its' own code behind which use reflection to call the update function in Control B which has a few input fields. I have debugged this and everything seems to be in order, however; when the update function in control B is called the input fields are not returning a value when using inputname.text or me.inputname.text. Does anyone have any ideas why this is not working? Any guidance would be appreciated.
This is the code in Control A's codebehind which calls the update method in Control B's code behind
Protected Sub btnSave_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnSave.Click
Try
Dim lctlControl = Session("SelectedQstnCtl")
Dim methodObj = lctlControl.GetType().GetMethod("UpdateGenInfo", BindingFlags.NonPublic Or BindingFlags.Instance)
' Execute UpdateGenInfo method to update the data
methodObj.Invoke(lctlControl, Nothing)
Catch ex As Exception
'TODO: check for concurrency error here
End Try
End Sub
This is the update function in Control B that is being called. The session values are being passed, but the form fields are not.
Protected Sub UpdateGenInfo()
Dim lclUtil As New clUtility
Dim genInfo As New clGenInfo
Try
Dim dt As Integer
'Update Data for 1-2
dt = genInfo.UpdateGenInfo_E1_01_02(Session("ConnStrEP"), Me.varLastUpdate, Session("AppNo"), Session("RevNo"), _
Me.txtPrName.Text, Me.txtPrAddr1.Text, Me.txtPrAddr2.Text, _
Me.txtPrCity.Text, Me.txtPrState.Text, Me.txtPrZip.Text)
Catch ex As Exception
'Display error
lclUtil.DisplayMsg(Me.lblErrMsg, String.Format("Error Location: Sub LoadGenInfo (ctlE1_01_02) {0}", ex.Message))
End Try
End Sub
The most likely cause is that the control instance stored in the session is not the control instance on the current page. For example, if you're storing the control instance in the session when the page is first loaded, and retrieving it on post-back, it will be a different instance.
If you can't give Control A a direct reference to Control B, then change your code to store the reference in the Page.Items collection:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs)
Page.Items("SelectedQstnCtl") = TheSelectedQstnCtl
End Sub
Protected Sub btnSave_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnSave.Click
Dim lctlControl = DirectCast(Page.Items("SelectedQstnCtl"), YourControlClass)
lctlControl.UpdateGenInfo()
End Sub
I see you are using reflection which might be an overkill for that task.Try referencing the method in the control directly.Make then method UpdateGenInfo public and then reference it like this.
Protected Sub btnSave_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnSave.Click
Try
Dim lctlControl = CType(Session("SelectedQstnCtl"),YourControlClass)
lctlControl.UpdateGenInfo()
Catch ex As Exception
End Sub
Public Function UpdateGenInfo()
'your code here
Catch ex As Exception
End Try
End Function
This way you can easily trace where your values are getting lost.Let me know how it goes.
Try yet another simple approach working demo here
In control a
Protected Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim testb1 = CType(Me.NamingContainer.FindControl("testb1"), testb)
testb1.UpdateGenInfo()
End Sub
In control b
Public Function UpdateGenInfo()
Try
Dim a = Me.TextBox1.Text
Catch ex As Exception
End Try
End Function
Aspx Parent Page
<uc1:testa ID="testa1" runat="server" />
<uc2:testb ID="testb1" runat="server" />
The controls in testb are in an update panel.Try this and let me know if this works.

VB.net/Asp.net loading a control programatically with a public property to be set

I have a vb.net asp application where I'm loading a control (from another control on a page). I want to set a variable between the two controls when it loads.
This is where I load the control:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim AuditControl As Control = LoadControl("~/controls/auditcompanies.ascx")
phCompanies.Controls.Add(AuditControl)
End Sub
On the control that's being loaded i've exposed the item i want to change as a property
Public Property resultType() As String
Get
Return m_resultType
End Get
Set(ByVal value As String)
m_resultType = value
End Set
End Property
Basically all it is doing is setting a parameter for my table adaptor
Public Sub Load_Data()
dtblog = New dsDECorp.ClientInfoDataTable
dtblog = tablog.GetAudits(m_resultType)
For Each rClient As dsDECorp.ClientInfoRow In dtblog
CID = rClient.ClientID
ClientName = rClient.CompanyName
Next
dtblog.Dispose()
End Sub
How do I pass the parameter through the property from the first control to the second when it loads?
Thanks
Tom
I'm a C# guy so forgive me if I make any syntax errors but the following should work for you:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim AuditControl As Control = LoadControl("~/controls/auditcompanies.ascx")
Dim AuditControlType As Type = AuditControl.GetType()
Dim AuditField As FieldInfo = AuditControlType.GetField("resultType")
AuditField.SetValue(AuditControlType, "Your Value")
phCompanies.Controls.Add(AuditControl)
End Sub

Find out what stage of the life cycle a control is up to in ASP.NET WebForms

From the outside of a control, is it possible to find out what stage of the Page LifeCycle (Init, Load, PreRender etc), a particular control or page is up to?
For example, in pseudo code:
if myControl.CurrentLifeCycle == Lifecycle.Init
{ do something }
I'm afraid there is no builtin function to check in what Page-Lifecycle phase a Page is.
It is also difficult to add this functionality without handling all events in the Page itself, because some events are protected. Therefore you could also inherit the LifeCycleListener-class from Control, add it in the constructor to the page listening and override all events.
If you only need the public "phases" PreInit,Init,Load,DataBinding,PreRender,Unload,Disposed have a look at following approach(VB.Net, but i think you'll get the idea):
Public Enum LifeCyclePhase
AfterPreInit
AfterInit
AfterLoad
AfterDataBinding
AfterPreRender
AfterUnload
AfterDisposed
End Enum
Public Interface ITrackingLifeCycle
ReadOnly Property GetLifeCycleListener() As LifeCycleListener
End Interface
Public Class LifeCycleListener
Public Sub New(ByVal ctrl As Control)
Me._PageListening = ctrl.Page
AddListener()
End Sub
Private _CurrentPhase As LifeCyclePhase
Private _PageListening As Page
Public ReadOnly Property CurrentPhase() As LifeCyclePhase
Get
Return _CurrentPhase
End Get
End Property
Public ReadOnly Property PageListening() As Page
Get
Return _PageListening
End Get
End Property
Private Sub AddListener()
AddHandler _PageListening.PreInit, AddressOf PreInit
AddHandler _PageListening.Init, AddressOf Init
AddHandler _PageListening.Load, AddressOf Load
AddHandler _PageListening.DataBinding, AddressOf DataBinding
AddHandler _PageListening.PreRender, AddressOf PreRender
AddHandler _PageListening.Unload, AddressOf Unload
AddHandler _PageListening.Disposed, AddressOf Disposed
End Sub
Private Sub PreInit(ByVal sender As Object, ByVal e As EventArgs)
Me._CurrentPhase = LifeCyclePhase.AfterPreInit
End Sub
Private Sub Init(ByVal sender As Object, ByVal e As EventArgs)
Me._CurrentPhase = LifeCyclePhase.AfterInit
End Sub
Private Sub Load(ByVal sender As Object, ByVal e As EventArgs)
Me._CurrentPhase = LifeCyclePhase.AfterLoad
End Sub
Private Sub DataBinding(ByVal sender As Object, ByVal e As EventArgs)
Me._CurrentPhase = LifeCyclePhase.AfterDataBinding
End Sub
Private Sub PreRender(ByVal sender As Object, ByVal e As EventArgs)
Me._CurrentPhase = LifeCyclePhase.AfterPreRender
End Sub
Private Sub Unload(ByVal sender As Object, ByVal e As EventArgs)
Me._CurrentPhase = LifeCyclePhase.AfterUnload
End Sub
Private Sub Disposed(ByVal sender As Object, ByVal e As EventArgs)
Me._CurrentPhase = LifeCyclePhase.AfterDisposed
End Sub
End Class
The handler in this class are called after the handler in the page itself, so if you f.e. check the CurrentPhase in Page.Init you'll get PreInit. Therefor i have called this phase AfterPreInit.
Partial Public Class _Default
Inherits System.Web.UI.Page
Implements ITrackingLifeCycle
Private lcl As New LifeCycleListener(Me)
Public ReadOnly Property GetLifeCycleListener() As LifeCycleListener Implements ITrackingLifeCycle.GetLifeCycleListener
Get
Return lcl
End Get
End Property
You can now check the lifecycle-phase everywhere, even without a reference to a control via HttpContext.Current:
Public Class FooClass
Public Shared Sub Foo()
If Not (HttpContext.Current Is Nothing OrElse HttpContext.Current.Handler Is Nothing) Then
If TypeOf HttpContext.Current.CurrentHandler Is ITrackingLifeCycle Then
Dim page As ITrackingLifeCycle = DirectCast(HttpContext.Current.CurrentHandler, ITrackingLifeCycle)
Dim phase As LifeCyclePhase = page.GetLifeCycleListener.CurrentPhase
End If
End If
End Sub
End Class
This is neither tested sufficiently nor used by myself and certainly improvable, but maybe it helps you in your current situation.
I think what you try to achieve is conceptually wrong because you are thinking at the page events as page state. The page can’t be at “OnInit/OnLoad/…” state just because it’s an event.
What do you need it for? maybe we could suggest you a better approach to achieve your goal.
The „outside of your control” is the „inside” of another control or page or something being in a linked state of the lifecycle. You know its state already without testing.
I found out there is actually an internal property of the Page class that does just what I am looking for: it is an enum called ControlState:
internal enum ControlState
{
Constructed,
FrameworkInitialized,
ChildrenInitialized,
Initialized,
ViewStateLoaded,
Loaded,
PreRendered
}
I believe it is possible to access internal members in C#4, see here
As far as I understand the page lifecycle, you can't do it. Basically, Page class is the guy that has to raise events in a specific order. There is nothing in built that will tell the stage of processing. But in order to something, you can create a property and set up this property in different stages of processing.

Putting an object in session via a property in ASP.NET

This seems like it might be a bad idea, but I can't figure out why:
I have a class, cXYZ, with properties A, B and C. It also has a method 'sGetData' that loads those three properties from the database, and a method 'sSaveData' which saves it back.
class cXYZ
public property A as string...
public property B as string...
public property B as string..
public sub sGetData()...
public sub sSaveData()...
end class
A webform has the following property:
private property xyz() as cXYZ
get
return session("myXYZ")
end get
set (value as cXYZ)
session("myXYZ")=value
end set
end property
And the following events:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
if not ispostback() then
xyz=new cXYZ()
end if
end sub
Protected Sub ButtonLoad_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles ButtonLoad.Click
//Can now reference the class
txtA.text=xyz.A
txtB.text=xyz.B
txtC.text=xyz.C
end sub
Protected Sub ButtonSave_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles ButtonSave.Click
//Can now reference the class
xyz.A=txtA.text
xyz.B=txtA.text
xyz.C=txtC.text
xyz.sSaveData()
end sub
I can see some overhead with serializing/deserializing for each property reference- it might be worth doing this:
Protected Sub ButtonSave_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles ButtonSave.Click
dim localxyz as cXYZ=xyz
localxyz .A=txtA.text
localxyz .B=txtA.text
localxyz .C=txtC.text
xyz=localxyz
end sub
Other than that, views on why this is good or bad? The class is not large, it maintains the form state. Webforms suck, etc is not very useful..
I would improve that little bit:
private _xyz as cXYZ = nothing
private property xyz() as cXYZ
get
if _xyz is nothing then _xyz = TryCast(session("myXYZ"), cXYZ)
return _xyz
end get
set (value as cXYZ)
_xyz = value
session("myXYZ")=_xyz
end set
end property
I think its ok. I would addicionaly keep a variable to store the object to improve performance.
Something like this:
private _xyz as cXYZ = nothing
private property xyz() as cXYZ
get
if not _xyz is nothing then
return _xyz
else
return session("myXYZ")
end if
end get
set (value as cXYZ)
_xyz = value
session("myXYZ")=value
end set
end property
As long as your object is serializable you are OK.
Just don't hold any unmanaged object references in your session - otherwise you'll get into trouble once you move away from 'in process' session state to a web farm.

Resources