DetailsView Trouble Retrieving Values - asp.net

The purpose of my detailsview is to collect report parameters for the user to enter when running a report. So I build the details view dynamically because report parameters are data driven, stored in the database per the report.
The object being bound to is created by reflection dynamically at run-time from the report parameters, with one property per each parameter, so it contains simple date or collection properties, which I turn into a combo box or date box dynamically by creating templates dynamically.
In the item_updating event, I simply want to get the values the user entered so I know how to run the report.
I looked in all the properties of e and also tried findcontrol to get to the values the user entered. both e and findcontrol are empty. This is how I got data back in the past so I don't know why they aren't there. It almost as if the controls themselves are not represented in the object model of the detailsview at that time.
<asp:DetailsView ID="DetailsView1" runat="server" AutoGenerateRows="false" DefaultMode="Edit" >
<Fields>
<asp:CommandField ButtonType="Button" UpdateText="Run" ShowCancelButton="false" ShowEditButton="true" />
</Fields>
</asp:DetailsView>
code behind
Private Sub BuildDetailView(DataSource As Object)
Dim Properties() As System.Reflection.PropertyInfo = DataSource.GetType.GetProperties
Dim Template As System.Web.UI.ITemplate
For Each PropertyInfo As System.Reflection.PropertyInfo In Properties
Template = Nothing
If PropertyInfo.PropertyType Is GetType(System.DateTime) Then
Template = New DateTemplate(DataSource, PropertyInfo)
ElseIf GetType(ICollection).IsAssignableFrom(PropertyInfo.PropertyType) Then
Template = New ListTemplate(DataSource, PropertyInfo, Report.InputReportParameters(Array.IndexOf(Properties, PropertyInfo)).Enumeration.MultiSelect)
End If
If Template IsNot Nothing Then
Dim TemplateField As New TemplateField
TemplateField.HeaderText = PropertyInfo.Name
TemplateField.ItemTemplate = Template
DetailsView1.Fields.Add(TemplateField)
End If
Next
End Sub
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
CheckAuthentication()
BuildDetailView(Report.ReportParametersDataObject)
If Report.ReportParameters.Any AndAlso Not Report.InputReportParameters.Any Then
Run() 'no parameters are for data entry
ElseIf Not IsPostBack Then
lblReportType.Text = String.Format("Report {0}", Report.Type)
DetailsView1.DataSource = New List(Of Object) From {Report.ReportParametersDataObject}
DetailsView1.DataBind()
End If
End Sub
Protected Sub DetailsView1_ItemUpdating(sender As Object, e As System.Web.UI.WebControls.DetailsViewUpdateEventArgs) Handles DetailsView1.ItemUpdating
Run(e.NewValues)
End Sub
List Template helper:
Public Class ListTemplate
Implements System.Web.UI.ITemplate
Public Sub New(DataSource As Object, PropertyInfo As System.Reflection.PropertyInfo, Multiselect As Boolean)
Me.DataSource = DataSource
Me.PropertyInfo = PropertyInfo
Me.Multiselect = Multiselect
End Sub
Private DataSource As Object
Private Multiselect As Boolean
Private PropertyInfo As System.Reflection.PropertyInfo
Public Sub InstantiateIn(container As System.Web.UI.Control) Implements System.Web.UI.ITemplate.InstantiateIn
Dim ListBox As New System.Web.UI.WebControls.ListBox
ListBox.DataSource = PropertyInfo.GetValue(DataSource, Nothing)
ListBox.DataTextField = "Text"
ListBox.DataValueField = "Value"
ListBox.SelectionMode = IIf(Multiselect, ListSelectionMode.Multiple, ListSelectionMode.Single)
ListBox.Rows = Math.Min(25, ListBox.DataSource.count)
ListBox.EnableViewState = True
container.Controls.Add(ListBox)
End Sub
End Class
DateTemplate Helper:
Public Class DateTemplate
Implements System.Web.UI.ITemplate
Public Sub New(DataSource As Object, PropertyInfo As System.Reflection.PropertyInfo)
Me.DataSource = DataSource
Me.PropertyInfo = PropertyInfo
End Sub
Private DataSource As Object
Private PropertyInfo As System.Reflection.PropertyInfo
Public Sub InstantiateIn(container As System.Web.UI.Control) Implements System.Web.UI.ITemplate.InstantiateIn
Dim Textbox As New System.Web.UI.WebControls.TextBox
Textbox.ID = PropertyInfo.Name.Replace(" ", String.Empty)
Textbox.EnableViewState = True
Dim CalendarExtender As New AjaxControlToolkit.CalendarExtender
CalendarExtender.TargetControlID = Textbox.ID
CalendarExtender.SelectedDate = PropertyInfo.GetValue(DataSource, Nothing)
CalendarExtender.DefaultView = AjaxControlToolkit.CalendarDefaultView.Months
CalendarExtender.Format = "MMMM yyyy"
container.Controls.Add(Textbox)
container.Controls.Add(CalendarExtender)
End Sub
End Class

In the absense of any answers or even comments, I worked around this issue by creating my own asp.net custom control.
The way it works is very simple. It has a DataSource property and it creates the UI adding an edit widget for each property. According to the propertyinfo.propertytype, it creates an editor for that type.
Once the user submits changes it raises a server-side event handing back the datasource object containing the changed values.
It's so simple, even I can understand how to use it.

Related

Override an ASCX control method in parent page

I have an ASCX user control that is being used in about 60 web forms pages. This control basically renders a series of nested drop down lists.
Inside the control, the method for populating the final list is like this:
Public Sub PopulateList()
Dim dt as DataTable = MyDAL.GetListValues()
For each dr as DataRow in dt.Rows
Dim li as new ListItem
' ...
myDDL.Items.Add(li)
Next
End Sub
In a small handful of pages, I need this method to work slightly differently (the list items are populated with more details, from a different table).
Is it possible somehow for the parent page of the control to somehow override the method? I read about Overriding on various MSDN pages, but can't figure this out. I can declare the method as...
Public Overridable Sub PopulateList()
... but then in VS2015, when I try to create an overriding method using Public Overrides the Intellisense menu does not contain any reference to the user control, or the method. I assume that this is because the control isn't actually being inherited by the page?
Is this possible to do, or is there another way please?
You cannot "override" the method in the parent page, because the page does not inherit from your control's class.
You could possibly create an event handler, or pass in a delegate to modify the behavior of the method.
For example:
Public Class Test1
Dim t2 As New Test2
Sub New()
' Call populateList with an action handler
t2.PopulateList(Sub(ddl)
' Do your logic here
Dim dt As DataTable = MyDAL.GetListValues()
For Each dr As DataRow In dt.Rows
Dim li As New ListItem
' ...
ddl.Items.Add(li)
Next
End Sub)
End Sub
End Class
Public Class Test2
Public Sub PopulateList(Optional handler As Action(Of DropDownList) = Nothing)
If handler Is Nothing Then
' Default behavior
Dim dt As DataTable = MyDAL.GetListValues()
For Each dr As DataRow In dt.Rows
Dim li As New ListItem
' ...
myDDL.Items.Add(li)
Next
Else
' Invoke action handler and pass a reference to the dropdown you want to add items to
handler(myDDL)
End If
End Sub
End Class
Example using an event:
Event MyCustomEvent As EventHandler(Of MyCustomEventArgs)
Public Sub PopulateList()
Dim args As New MyCustomEventArgs()
args.ListObject = myDDL
RaiseEvent MyCustomEvent(Me, args)
' Do default behavior if not handled by event code
If Not args.Handled Then
Dim dt As DataTable = MyDAL.GetListValues()
For Each dr As DataRow In dt.Rows
Dim li As New ListItem
' ...
myDDL.Items.Add(li)
Next
End If
End Sub
Custom event args class:
Public Class MyCustomEventArgs
Inherits EventArgs
Public Property Handled As Boolean
Public Property ListObject As DropDownList
End Class
Handled on your page:
Protected Sub MyControl_MyCustomEvent(sender As Object, e As MyCustomEventArgs) Handles MyControl.MyCustomEvent
e.Handled = True
' Do work on your list
Dim mylist = e.ListObject
End Sub

Gridview rowcommand event not firing in dynamically added usercontrol

I have a usercontrol with gridview and rowcommand event.
This usercontrol is added dynamically using LoadControl on a button click of a page. The gridview's rowcommand doesn't fire.
Here is the code that loads the usercontrol on button click:
Protected Sub btnSearch_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnSearch.Click
'<ucTitle:SearchList ID="ucSearchList" runat="server" Visible="false" />
Dim ucSearchList As TitleSearchList = LoadControl("~/Controls/TitleSearchList.ascx")
ucSearchList.ISBN = txtSearchISBN.Text
ucSearchList.LoadTitleSearchList()
pnlSearchResults.Controls.Add(ucSearchList)
End Sub
And here is the code in usercontrol
Public Class TitleSearchList
Inherits System.Web.UI.UserControl
Public Property ISBN As String
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
LoadTitleSearchList()
End If
End Sub
Public Sub LoadTitleSearchList()
Dim _isbn As String = ISBN
Dim titles As New List(Of Title)
titles = New List(Of Title) From {
New Title With {.ISBN = _isbn, .TitleName = "Title check"},
New Title With {.ISBN = _isbn, .TitleName = "Title check"},
New Title With {.ISBN = _isbn, .TitleName = "Title check"},
New Title With {.ISBN = _isbn, .TitleName = "Title check"}
}
gvTitle.DataSource = titles
gvTitle.DataBind()
End Sub
Public Sub gvTitle_Rowcommand(ByVal sender As Object, ByVal e As GridViewCommandEventArgs) Handles gvTitle.RowCommand
If e.CommandName = "TitleDetail" Then
Response.Redirect("TitleSearch.aspx?isbn=" & e.CommandArgument().ToString())
End If
End Sub
End Class
Events in GridViews that are added dynamically in a UserControl get a little ugly. Since UserControls that are added dynamically have to be re-added on the post-back, you have to rebind the GridView DataSource, which makes you lose out on automatically getting those Events fired for you. That being said, you can still pull it off with some parsing of the __EVENTTARGET of the Form.
Add a HiddenField that tells you whether or not you need to re-add the UserControl. Add this in your Page_Load Event Handler:
If CBool(hdnTitleSearchActive.Value) = True Then
AddSearchListToPanel()
End If
Call AddSearchListToPanel() in your btnSearch_Click Event Handler.
Now this implementation of AddSearchListToPanel can be cleaned up some, but this should be good enough to get you going. Note that the Button triggering the GridView Command in my example has the ID of lbtTest. You will have to adjust based on the ID that you are using.
Private Sub AddSearchListToPanel()
Dim ucSearchList As TitleSearchList = LoadControl("~/Controls/TitleSearchList.ascx")
ucSearchList.ISBN = txtSearchISBN.Text
ucSearchList.LoadTitleSearchList()
pnlSearchResults.Controls.Add(ucSearchList)
hdnTitleSearchActive.Value = True
Dim strEventTarget As String = HttpContext.Current.Request.Form("__EVENTTARGET")
If Not strEventTarget Is Nothing AndAlso strEventTarget.Contains("gvTitle$") AndAlso _
strEventTarget.Contains("$lbtTest") Then
'Value example = gvTitle$ctl02$lbtTest
Dim intRowNumber As Integer = (CInt(strEventTarget.Substring(11, 2)) - 1)
Dim lbtCommandSource As LinkButton = CType(CType(ucSearchList.FindControl("gvTitle"), GridView).Rows(intRowNumber).FindControl("lbtTest"), LinkButton)
Dim objCommandEventArguments As New CommandEventArgs(lbtCommandSource.CommandName, lbtCommandSource.CommandArgument)
Dim objGridViewCommandEventArgs As New GridViewCommandEventArgs(lbtCommandSource, objCommandEventArguments)
ucSearchList.gvTitle_Rowcommand(lbtCommandSource, objGridViewCommandEventArgs)
End If
End Sub

Why is ASP.NET server control property nothing

I'm trying to create a new server control type in ASP.NET. This control would put a RequiredFieldValidator into a certain place. My class inherits from the WebControl class and it has a ControlID property which is the ID of a given control. The RequiredFieldValidator will be generated near the control with the given ControlID. However, the value of ControlID is nothing. In what events can I successfully use this property?
Public Class MyControl
Inherits WebControl
'...
Protected Property ControlID As String
Protected Property IsLinkedBrother As Boolean = False
'...
Protected Overrides Sub OnPreRender(e as System.EventArgs)
MyBase.OnPreRender(e)
Dim rootControl = If(IsLinkedBrother, Parent, Page)
Dim controls As Stack(Of Control) = New Stack(Of Control)
Dim currentControl As Control = Nothing
controls.Push(rootControl)
Dim result As Control = Nothing
While ((result Is Nothing) AndAlso (controls.Count > 0))
currentControl = controls.Pop
If ((Not String.IsNullOrEmpty(currentControl.ID)) AndAlso (currentControl.ID.Equals(ControlID))) Then
result = currentControl
Else
For Each child As System.Web.UI.Control In currentControl.Controls
controls.Push(child)
Next
End If
End While
'...
End Sub
'...
End Class
But ControlID is Nothing for some reason and the event throws an exception. ControlID is never changed after initialization and it is initialized this way:
<MyControl runat="server" ID="IDValue" ControlID="ControlIDValue" EnableCliendScript="true"
CssClass="Whatever" Display="Dynamic" />
I've searched and tried for hours but with no luck. Can anybody tell me what can cause this behavior and why, does anybody has some suggestions for a solution? Thank you in advance
ControlID must be public for this to work; it can't be protected and be able to be set from markup.

Two-way data binding in ASP.NET

Trying to use data binding between a list of objects and a data list control. What I want to do are
create the list of objects
have them bound to the controls
change data in the UI
have the changes in the ui bound to the list of objects
on post back - have the list of objects with the new values from the ui
<body>
<form id="form1" runat="server">
<div>
<asp:DataList ID="DataList1" runat="server" DataKeyField="ClassID" ViewStateMode="Enabled">
<ItemTemplate>
<asp:TextBox ID="txtValue1" runat="server" Text='<%# Bind("Value1") %>'></asp:TextBox>
<asp:TextBox ID="txtValue2" runat="server" Text='<%# Bind("Value2") %>'></asp:TextBox>
<asp:TextBox ID="txtvalue3" runat="server" Text='<%# Bind("Value3") %>'></asp:TextBox>
</ItemTemplate>
</asp:DataList>
<asp:Button ID="btnDoPostBack" runat="server" Text="Do Post Back" />
</div>
</form>
</body>
Option Explicit On
Option Strict On
Imports System.Diagnostics
Partial Class _Default
Inherits System.Web.UI.Page
Dim Class1List As List(Of Class1)
Protected Sub Page_PreLoad(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.PreLoad
Dim txtValue1 As TextBox
Dim txtValue2 As TextBox
Dim txtValue3 As TextBox
Dim ItemIndex As Integer = 0
If Page.IsPostBack Then
Class1List = CType(Session("Class1List"), List(Of Global.Class1))
'Class1List = CType(DataList1.DataSource, List(Of Global.Class1))
For Each myDataListItem As DataListItem In DataList1.Items
txtValue1 = CType(myDataListItem.FindControl("txtValue1"), TextBox)
Long.TryParse(txtValue1.Text, Class1List(ItemIndex).Value1)
txtValue2 = CType(myDataListItem.FindControl("txtValue2"), TextBox)
Integer.TryParse(txtValue2.Text, Class1List(ItemIndex).Value2)
txtValue3 = CType(myDataListItem.FindControl("txtValue3"), TextBox)
Class1List(ItemIndex).Value3 = txtValue3.Text
ItemIndex += 1
Next
End If
End Sub
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim myClass1 As Class1
If Not Page.IsPostBack Then
Class1List = New List(Of Class1)
myClass1 = New Class1
Class1List.Add(myClass1)
BindData()
Else
'Class1List = CType(DataList1.DataSource, List(Of Global.Class1))
Debug.WriteLine("Page_Load, Value1 = " & Class1List(0).Value1.ToString())
Debug.WriteLine("Page_Load, Value2 = " & Class1List(0).Value2.ToString())
Debug.WriteLine("Page_Load, Value3 = " & Class1List(0).Value3)
End If
End Sub
Protected Sub Page_Unload(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Unload
Session("Class1List") = Class1List
End Sub
Sub BindData()
DataList1.DataSource = Class1List
DataList1.DataBind()
End Sub
Protected Sub DataList1_ItemDataBound(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.DataListItemEventArgs) Handles DataList1.ItemDataBound
Dim myClass1 As Class1
If e.Item.ItemType = ListItemType.Item OrElse e.Item.ItemType = ListItemType.AlternatingItem Then
myClass1 = CType(e.Item.DataItem, Class1)
Debug.WriteLine("DataList1_ItemDataBound, Value1 = " & myClass1.Value1.ToString())
Debug.WriteLine("DataList1_ItemDataBound, Value2 = " & myClass1.Value2.ToString())
Debug.WriteLine("DataList1_ItemDataBound, Value3 = " & myClass1.Value3)
End If
End Sub
Protected Sub btnDoPostBack_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnDoPostBack.Click
Dim myRandom As New Random
Class1List(0).Value1 = myRandom.Next(100)
Class1List(0).Value2 = myRandom.Next(100)
Class1List(0).Value3 = myRandom.Next(100).ToString()
Debug.WriteLine("btnDoPostBack_Click, Value1 = " & Class1List(0).Value1.ToString())
Debug.WriteLine("btnDoPostBack_Click, Value2 = " & Class1List(0).Value2.ToString())
Debug.WriteLine("btnDoPostBack_Click, Value3 = " & Class1List(0).Value3)
BindData()
End Sub
End Class
The Class Class1 is trivial:
Option Explicit On
Option Strict On
Imports Microsoft.VisualBasic
Public Class Class1
Private _ClassID As Long
Private _Value1 As Long
Private _Value2 As Integer
Private _value3 As String = String.Empty
Public Property ClassID As Long
Get
Return _ClassID
End Get
Set(ByVal value As Long)
_ClassID = value
End Set
End Property
Public Property Value1 As Long
Get
Return _Value1
End Get
Set(ByVal value As Long)
_Value1 = value
End Set
End Property
Public Property Value2 As Integer
Get
Return _Value2
End Get
Set(ByVal value As Integer)
_Value2 = value
End Set
End Property
Public Property Value3 As String
Get
Return _value3
End Get
Set(ByVal value As String)
_value3 = value
End Set
End Property
End Class
Update: I got the code behind above to do what I want it to do - I was thinking there was a better way?
You didn't show your databinding "Load" phase (the code which binds the data from the list to the controls)--so I assume the part you are unhappy with is the "Save" phase (the code in Page_PreLoad which binds the modified values from the controls back to the list), i.e. #4 in your list:
have the changes in the ui bound to the list of objects
It sounds like you want "two-way Data Binding": you want .NET to update your model as easily as it reads from your model. This is a common complaint. One solution is to subclass WebControl, but that's a mess.
You are already using the <%# Bind("...") %> syntax, so you have the right idea. That approach should work out-of-the-box with <asp:SqlDataSource>, but you want to update a custom class, so you need to use <asp:ObjectDataSource> instead. Use the approach in this article, except with ObjectDataSource instead of SqlDataSource.
But first you have to make your model (i.e., Class1) compatible with ObjectDataSource by marking it with [System.ComponentModel.DataObject] and designating the appropriate update method like this:
[System.ComponentModel.DataObjectMethodAttribute(
System.ComponentModel.DataObjectMethodType.Update, true)]
public bool UpdateProduct(string productName, ...) {
...
}
This would allow you to use an ObjectDataSource on your webform and finally get nice 2-way databinding. Read the links to for full details.
Visual Studio offers various clunky ways of automating this, such as TableAdapters and the infamous Strongly-Typed DataSet (STD), but those don't help people like yourself who have their own object model. I don't recommend STDs anyway.
I was thinking there was a better way?
I don't think your current approach is bad. If you're worried about having tons of logic in your webforms, you would gain much more by using an MVC approach than worrying about binding sugar...

Populate TextBox From Public Property From Code-Behind

I am trying to use a property from the code-behind to populate a textbox instead of using in the code-behind textbox.text=. I am using vb.net. Here is the code for the aspx page:
<asp:Content ID="Content2" ContentPlaceHolderID="MainContentPlaceHolder" runat="server">
<asp:TextBox runat="server" ID="roleTextBox" Text='<%# CurrentRole.Name%>'></asp:TextBox>
</asp:Content>
Here is the code behind code:
Imports Compass.UI.components
Imports Compass.Core.Domain
Imports Compass.Core.Domain.Model
Namespace app.administration.Roles
Partial Public Class edit
Inherits ClaimUnlockPage
Private _roleRepository As IRoleRepository
Private _roleId As Integer
Private _role As Role
Public Property CurrentRole() As Role
Get
Return _role
End Get
Set(ByVal value As Role)
_role = value
End Set
End Property
Public Property RoleRepository() As IRoleRepository
Get
Return _roleRepository
End Get
Set(ByVal value As IRoleRepository)
_roleRepository = value
End Set
End Property
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
LoadRole()
End Sub
Private Sub LoadRole()
_roleId = Config.RequestVal("id", Request)
_role = _roleRepository.GetById(_roleId)
End Sub
End Class
End Namespace
When I run the page the text box is empty.
I didn't see roleTextBox.text=value in your code! in LoadRole or anywhere.
And if you try to bind it, you need a static class for Role.
Just for testing try to add the following line in LoadRole
Private Sub LoadRole()
_roleId = Config.RequestVal("id", Request)
_role = _roleRepository.GetById(_roleId)
roleTextBox.text =CrrentRole.Name;
End Sub
if the roleTextBox is still empty then the CurrentRole.Name is empty.
As far as I know you can't bind a property of a control like this (I wish you could but I've never been able to figure out or find an example how to). The way I've always done it is create a protected function to return e.g.
Protected Function GetCurrentRoleName() As String
Return CurrentRole.Name
End Function
And in your markup bind like so
Text='<%# GetCurrentRoleName() %>'
You have to DataBind the container-control which contains your Textbox(f.e. a GridView,UserControl,etc.). So at least your aspx-page must be databound.
"When called on a server control, this method resolves all data-binding expressions in the server control and in any of its child controls."
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Me.CurrentRole = New Role("Administrator")
Me.DataBind() '!!!!!!!
End Sub
Private _currentRole As Role
Protected Property CurrentRole() As Role
Get
Return _currentRole
End Get
Set(ByVal value As Role)
_currentRole = value
End Set
End Property
Public Class Role
Public Sub New(ByVal name As String)
Me.Name = name
End Sub
Public Name As String
End Class
Then you can use your aspx-code to set the TextBox'-text property.

Resources