Confused about using ViewState with dynamically added usercontrol - asp.net

On my webpage I am loading multiple instances of a usercontrol, sometimes the usercontrol is laoded within itself. I need to save a bunch of properties for the round trip of a post back but i am confused on how to save those properties to ViewState and set them again to the repeater items within the usercontrol.
Can anyone help me in this situation, I have read the MSDN on Viewstate but I am not understanding it quite well for some reason
This is how I load the parent user controls (child controls are loaded the same way with the same user control)
Protected Sub Load_Controls(ByVal list As List(Of BSEvaluationGroup.category), ByVal gid As Integer, ByVal pid As Integer, ByVal fid As Integer)
Dim item As BSEvaluationGroup.category
For Each item In list
Dim ctl As PerformanceEvaluationSubcontractorControl = CType(Page.LoadControl("PerformanceEvaluationSubcontractorControl.ascx"), PerformanceEvaluationSubcontractorControl)
ctl.categoryid = item.catid
ctl.categoryname = item.catname
ctl.projectid = pid
ctl.folderid = fid
ctl.groupid = gid
ctl.parentid = item.parid
ctl.clist = item.categories
ctl.plist = item.points
ctl.parentpage = Me
ctl.EnableViewState = "true"
If (Not subcon Is Nothing AndAlso Not subcon.points Is Nothing) Then
ctl.epnts = subcon.points
End If
AddHandler ctl.BubbleCalculate, AddressOf Me.PostRating
Select Case gid
Case 1
Me.officephld.Controls.Add(ctl)
Dim ohrule As HtmlGenericControl = New HtmlGenericControl("hr")
ohrule.Style.Add("width", "100%")
ohrule.Style.Add("background-color", "Silver")
ohrule.Style.Add("size", "1px")
ohrule.Style.Add("border-width", "0")
ohrule.Style.Add("padding-top", "1px")
ohrule.Style.Add("float", "left")
Me.officephld.Controls.Add(ohrule)
Case 2
Me.sitephld.Controls.Add(ctl)
Dim shrule As HtmlGenericControl = New HtmlGenericControl("hr")
shrule.Style.Add("width", "100%")
shrule.Style.Add("background-color", "Silver")
shrule.Style.Add("size", "1px")
shrule.Style.Add("border-width", "0")
shrule.Style.Add("padding-top", "1px")
shrule.Style.Add("float", "left")
Me.sitephld.Controls.Add(shrule)
End Select
Next
End Sub

Accessing view-state is simple such as ViewState("PropertyName"). The View State bag is specific to a control instance so you can use same property name within multiple control types and instances.
Only important thing here is that ASp.NET run-time has to match view-state bags to control instances and for that it uses ID property (which is unique within the parent naming container). So its important that you assign unique IDs to your dynamic user control instances ( and maintain same control tree hierarchy and ids on postback - essentially it means that execute the same code on postback and don't use random ids). So your code should be something like
...
Dim n As Integer
n = 1
For Each item In list
Dim ctl As PerformanceEvaluationSubcontractorControl = CType(Page.LoadControl("PerformanceEvaluationSubcontractorControl.ascx"), PerformanceEvaluationSubcontractorControl)
ctl.ID = "MyCtl" & n.ToString()
ctl.categoryid = item.catid
....

It was a control ID issue, I removed it instead of adding an ID

Related

Type-casting entity framework results in ItemDataBound

So I've trying to optimize performance of a page which generates the navigation menu for a website. Basically there is a table called T_product_cat which contains 4 fields I care about:
product_cat_name (the product category name)
product_cat_uid (the unique identifier of the row)
product_cat_thumbnail_path (the image that represents the category)
product_cat_parent_uid (the unique identifier of the product category
that we sit underneath - since these "product folders" can nest)
I need to create a top navigation bar which has just the names of the top level product categories and then for the dropdown navigation I need to output the sub product categories along with their images and links etc. I've got a basic ASP:Repeater control with a nested ASP:Repeater to handle the sub-product categories
Basically though I want to start using EF to start querying for my data. Initially I did a query to just pull the top level items and then in the ItemDataBound event of the repeater, I'd access the data and query the database again for the sub categories. But this was hitting the database 8x (since I've got 7 top level categories). To speed things up I've been trying different iterations of an initial query. Here's my final one for reference:
Using db As New RmpEntities
' query all the items and then all their subitems at the same time so that we don't have to repeatedly hit the database
Dim qry = (
From
productCat In db.T_product_cat
Where
productCat.company_uid = WmsSession.company_uid AndAlso
productCat.enabled = 1 AndAlso
productCat.product_cat_parent_uid = GUIDs.GlobalGuids.RootCategoryUid AndAlso
productCat.product_cat_hide <> 1
Order By
productCat.product_cat_sequence
Select
categoryName = productCat.product_cat_name,
categoryUid = productCat.product_cat_uid,
imagePath = productCat.product_cat_thumbnail_path,
subCategories = (
From
productSubCat In db.T_product_cat
Where
productSubCat.company_uid = WmsSession.company_uid AndAlso
productSubCat.enabled = 1 AndAlso
productSubCat.product_cat_parent_uid = productCat.product_cat_uid AndAlso
productSubCat.product_cat_hide <> 1
Order By
productSubCat.product_cat_sequence
Select
categoryName = productSubCat.product_cat_name,
categoryUid = productSubCat.product_cat_uid,
imagePath = productSubCat.product_cat_thumbnail_path
)
)
rptMainNav.DataSource = qry.ToList()
rptMainNav.DataBind()
End Using
When the repeater's ItemDataBound event fires I do have working code to pick apart the results and bind the data to the sub-repeater (this code works):
Protected Class CategoryInfo
Public Property categoryName As String
Public Property categoryUid As Guid
Public Property imagePath As String
End Class
Private Sub rptMainNav_ItemDataBound(sender As Object, e As RepeaterItemEventArgs) Handles rptMainNav.ItemDataBound
Dim rItem As RepeaterItem = e.Item
Select Case rItem.ItemType
Case ListItemType.Item, ListItemType.AlternatingItem
Dim dataItem = rItem.DataItem
Dim subRepeater As Repeater = rItem.FindControl("rptSubNav")
Dim parentUid As Guid = dataItem.categoryUid
Dim st = DateTime.Now
Dim x As New List(Of CategoryInfo)
For Each item In dataItem.subCategories
x.Add(New CategoryInfo With {.categoryName = item.categoryName, .categoryUid = item.categoryUid, .imagePath = item.imagePath})
Next
subRepeater.DataSource = x
dTime.Add(New MyTimes With {.g = parentUid, .ts = DateTime.Now - st})
subRepeater.DataBind()
End Select
End Sub
But how can I cast the Object type returned by accessing the repeater's DataItem to a more strongly-typed object so I can get intellisense?
Or is there some way I can give up on the worries about typing and just use the query results directly as a DataSource for the sub-repeater? That was my first goal but when I tried to take
dataItem.subCategories.ToList()
It failed with:
Public member 'ToList' on type 'CompensatingCollection(Of VB$AnonymousType_1(Of String,Guid,String))' not found.
When I tried to modify my CategoryInfo class to have another property to hold a list of CategoryInfo classes (subcategories) and have the main query return structured data (so I'd have a type to cast to in the ItemDataBound) things failed even worse. The original query's .ToList failed so I couldn't even do the initial bind.
What is the correct way to handle this? Continue working on properties of an Object which represents an anonymous type? Surely there must be something better.
EDIT:
Robert - Thanks for your suggestions. It turned out that I did NOT have a navigation property to SubCategories. The reason being that the people who came before me designed the database such that all top level categories end up with an artificial product_cat_parent_uid (just some random GUID). It doesn't point to a real product category in the database - it just acts as a placeholder. I question why they didn't just use NULL but it's too late to change it. At any rate since the item doesn't exist in the database, the database itself doesn't have the foreign key since it's faking the link. The databse won't create the link with product categories pointing to a product categorie that is not in the database. I did use your suggestion to find more information about what could be done and I used this article (Entity Framework - Add Navigation Property Manually) to add those links I need.
The question still remains though - can I get strong typing within the ItemDataBind event?

Changing DropDown Selected Item Based on Selected Value (ASP.NET)

I have a dropdown list on my page (ddlProgram) which is populated via a database query like so:
Using dbContext as IRFEntities = New IRFEntities
Dim getPrograms = (From p in dbContext.IRF_Program _
Order By p.name _
Select p)
ddlProgram.DataSource = getPrograms
ddlProgram.DataTextField = "name"
ddlProgram.DataValueField = "id"
ddl.Program.DataBind()
End Using
So, for example, one might have a DataTextField of "Education" and an ID of "221".
Now, I prepopulate the form with information about the individual visiting the site (if available) - including the dropdown list like so:
If getProspect IsNot Nothing Then
If getProspect.user_id Is Nothing Then
ddlProgram.SelectedValue = getProspect.Program
End If
End If
The Program property contains a number that matches the ID of a Program. So, for example, this individual might have a Program of "221" which would match the "221" of Education mentioned above.
Currently the application successfully sets the SelectedValue to "221" for the DropDownList (ddlProgram), but the SelectedItem of the DDL remains the same (e.g., if it is initially "History" with an ID of "1" after the prepopulation it is "History" with an ID of "221").
What I'm trying to make happen is that the SelectedItem is updated to item which corresponds with the SelectedValue. So, in the end, if the individual has "221" for "Education" selected when the form is prepopulated they would see Education as the selected item and the selected value would be set correctly, whereas right now the form is showing the wrong SelectedItem but has the right SelectedValue behind the scenes.
Here is a more complete idea of the code flow from the Page_Load event:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Page.IsPostBack = False Then
' If prospect is coming from unique url
Dim prospect_url As String = Page.RouteData.Values("value")
' Save prospect_url into session variable
Session("prospect_url") = prospect_url
Using dbContext As IRFEntities = New IRFEntities
' Prepopulate the programs dropdown.
Dim getPrograms = (From p In dbContext.IRF_Program _
Order By p.name _
Select p)
ddlProgram.DataSource = getPrograms
ddlProgram.DataTextField = "name"
ddlProgram.DataValueField = "id"
ddlProgram.DataBind()
End Using
Using dbContext As IRFEntities = New IRFEntities
' Prepopulate the states dropdown.
Dim getStates = (From p In dbContext.IRF_States _
Order By p.name _
Select p)
ddlState.DataSource = getStates
ddlState.DataTextField = "name"
ddlState.DataValueField = "id"
ddlState.DataBind()
End Using
Using dbContext As IRFEntities = New IRFEntities
' Grab info. about prospect based on unique url.
Dim getProspect = (From p In dbContext.IRF_Prospects _
Where p.url = prospect_url _
Select p).FirstOrDefault
' If they have a record...
If getProspect IsNot Nothing Then
If getProspect.user_id Is Nothing Then
' Prepopulate the form with their information.
' These must have a value, so we need to make sure that no column is null in the database.
ddlProgram.SelectedValue = getProspect.program
txtFirst.Text = getProspect.first_name
txtLast.Text = getProspect.last_name
txtAddress.Text = getProspect.address
txtCity.Text = getProspect.city
ddlState.SelectedValue = getProspect.state
txtZip.Text = getProspect.zip
txtPhone.Text = getProspect.phone
txtEmail.Text = getProspect.email_address
txtYearEnrolling.Text = getProspect.enrolling_in
Else
' Redirect them to login.
Response.Redirect("login.aspx")
End If
End If
End Using
End If
End Sub
What you're doing looks like it should work. If you put a breakpoint after the setting of the value and check the SelectedItem text and value, do they appear as expected or mismatched?
Use the Immediate Window to check:
ddlProgram.SelectedItem.Text
ddlProgram.SelectedItem.Value
If they appear the same then I would presume the binding code is being refired and the list is being regenerated with the first item being selected.
To check this put a break point on the binding code and see if it is fired more than once and correct the order of the methods appropriately.
ADDED:
If it works on your local environment it should work when published, if the code is the same? Looking at your code, I'd start by seperating out some of the databinding code into seperate methods rather than have everything in Page_Load, one becuase it's good practice and two because it will make debugging easier. Further than that I'm not sure what else to suggest.

Accessing server-side control by its ID property in ASP.net

On my default.aspx page I have a bunch of divs with an ID and runat="server":
<div id="serverOne" runat="server"></div>
<div id="serverTwo" runat="server"></div>
<!--etc...-->
In my code behind I've declared a multidimensional array (or grid) with 2 values -- the first being an IP address and the second the server name.
Dim servers = {{"10.0.0.0", "serverOne"}, {"10.0.0.1", "serverTwo"}}
My question is, is there a way where I can target my divs from my code behind using a value from the array?
For i As Integer = 0 To 1
'This is what I want it to do:
servers(i, 1).InnerHtml = "<span>Testing " & servers(i, 1) & "</span>"
Next
You can do this using the FindControl method on the page. However, out of the box FindControl looks only at the first level of children, and does not go into the childrens' children. In order to handle this you need to use a helper method that allows FindControl to recursively search through the control hierarchy to find the one you want. Add this method to your code behind, or some shared class that multiple pages can access:
Protected Function FindControlRecursive(control As Control, id As String)
If (control.ID = id) Then
Return control
End If
For Each ctl In control.Controls
Dim foundControl = FindControlRecursive(ctl, id)
If (foundControl IsNot Nothing) Then
Return foundControl
End If
Next
Return Nothing
End Function
Once you have that, it's pretty easy to find your <div> just by using the string ID property.
For i As Integer = 0 To 1
Dim div = CType(FindControlRecursive(Me, servers(i, 1)), HtmlGenericControl)
div.InnerHtml = "<span>Testing " & servers(i, 1) & "</span>"
Next
Reference: http://forums.asp.net/t/1107107.aspx/1

dynamically set control ID

i have few texboxt and dropdownlist, which their id would be something like "txtName1, txtName2, txtName3..." and "ddlAction1, ddlAction2, ddlAction3...."! I would to to dynamically set the textboxt and dropdownlist id into something like this:
for i as integer = 0 to 6
a = txtName+i.text
b = ddlAction+i.SelectedValue
next i
Need help from you guys to do this! thanks....
The key is FindControl, which looks up a control by its expected ID:
For i As Integer = 0 To 5
Dim txt As TextBox = TryCast(Me.Page.FindControl("txtName" & i.ToString()), TextBox)
Dim ddl As DropDownList = TryCast(Me.Page.FindControl("ddlAction" & i.ToString()), DropDownList)
If txt IsNot Nothing AndAlso ddl IsNot Nothing Then
Dim a As String = txt.Text
Dim b As String = ddl.SelectedValue
End If
Next
It will return null/nothing if a control with that ID isn't found.
Note that FindControl will only search the given control's (or Page's) immediate children, not the entire control tree. To search recursively, you need to use your own FindControl method.
Private Function FindControlRecursive(ByVal control As Control, ByVal id As String) As Control
Dim returnControl As Control = control.FindControl(id)
If returnControl Is Nothing Then
For Each child As Control In control.Controls
returnControl = child.FindControlRecursive(id)
If returnControl IsNot Nothing AndAlso returnControl.ID = id Then
Return returnControl
End If
Next
End If
Return returnControl
End Function
.Net requires you to resolve your variable names at compile time, rather than runtime like your code is trying to do. This is a good thing, as it prevents you from making certain kinds of errors. But it does mean you'll need to look at an alternative approach for this particular problem.
One option is a FindControl -based method. But odds are the controls you care about are grouped together on the page. If they aren't already, put them in a common container control, like a panel. Then you can do something like this (requires System.Linq):
For Each t As TextBox In MyContainer.Controls.OfType(Of TextBox)()
a = t.Text
Next t
For Each d As DropDownList In MyContainer.Controls.OfType(Of DropDownList)()
b = d.SelectedValue
Next d
Also, I hope you're really doing something other than assignment inside your loop. Otherwise, most of the work is for nothing as you will exit the loop having simply assigned the value from the last iteration to your variable.
Finally, it seems like these controls might work in pairs. To me, that's a situation that calls out for you to implement a user control.
I'm not a webforms expert, but I believe the page stores a reference to each control present in the page.
You can think of webforms like an n-ary tree... each control has a parent and can have 0 to many children. So, if these are static controls you should just be able to grab a reference to their parent and iterate over that... with no need for the children's ids.
Also, you can query for children based on their id... something like myControl["myID1"] so you can concat the number with the string and get the control that way.
Lastly, if these are purely dynamic controls, i.e. you don't know how many there are, just store references to them in an ordered collection and iterate over them that way.
EDIT:
Here we go:
WebControl myControl;
myControl.Controls.Add(someControlReference);
Then, to grab a control by ID:
WebControl someControl = myControl.FindControl("someControlID1");
From there you can do like:
string a = someControl.Text

How to enable/disable web elements of a parent aspx page from the child ascx page?

I have an aspx page with three web controls: one to control the List Users page, one to control the Edit Users page, and one to control the Add User page. I have discovered a method for accessing these elements, but it seems to be limited. Here is what I have done:
Protected Sub editUser(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewEditEventArgs)
'set selected user from gridview.
Dim index As Integer = e.NewEditIndex
Dim userId As Integer = gvListUsers.DataKeys(index).Value
Session.Item("SelectedUserId") = userId
'show edit page, hide list and add page.
With Page.Form.Controls(1)
.Controls(getControlId("loadAddUser")).Visible = False
.Controls(getControlId("loadEditUser")).Visible = True
.Controls(getControlId("loadListUser")).Visible = False
End With
End Sub
The getControlId function looks like this:
Public Function getControlId(ByVal control As String) As Integer
Dim enumer As System.Collections.IEnumerator = Page.Form.Controls.Item(1).Controls.GetEnumerator
Dim i As Integer
For i = 0 To (Page.Form.Controls.Item(1).Controls.Count - 1)
If Page.Form.Controls(1).Controls.Item(i).ID = control Then
Return i
End If
Next
Return Nothing
End Function
This works in most cases. However, I am unable to access the "enabled" attribute of these web controls. Why is this, and how might I access that attribute?
Thanks :)
You could raise events from your UserControls which you subscribe to in the parent ASPX page. In the parent page event action you could enable/disable your controls,
Here's an example of events in UserControls: http://codebetter.com/blogs/brendan.tompkins/archive/2004/10/06/Easily-Raise-Events-From-ASP.NET-ASCX-User-Controls.aspx
Something else to think about: are you getting any benefit from moving this code into usercontrols? Would any of the individual controls be re-usable on their own? Creating tightly coupled controls that rely on each other being present doesn't give you much re-usability of the individual controls.
Visible is a property provided by the System.Web.UI.Control class, which is why you can access it directly. Enabled is not a property on this class, so you need to map the control object to a variable of the type of your custom control class if you want to access the Enabled property.
Dim myControl As TheAddUserControl
With Page.Form.Controls(1)
myControl = .Controls(getControlId("loadAddUser"))
myControl.Enabled = False
.Controls(getControlId("loadEditUser")).Visible = True
.Controls(getControlId("loadListUser")).Visible = False
End With
To expose an Enabled property in you user control:
Public Property Enabled As Boolean
Get
Return (Child1.Enabled And Child2.Enabled And Child3.Enabled)
End Get
Set(ByVal value As Boolean)
Child1.Enabled = value
Child2.Enabled = value
Child3.Enabled = value
End Set
End Poperty

Resources