ListView in ASP.NET and DataKeyNames - asp.net

I have a ListView, which is bound with a list of 'A', which looks like this:
Class A
Property Id as Integer
Property TestStringA as String
Property B as B
End Class
Class B
Property Id as Integer
Property TestStringB as String
End Class
In the ListView i can refer to 'link property' values (whats the correct term for this?):
<asp:ListView runat="server" ID="lwTest" ItemType="A">
<LayoutTemplate>
<tr runat="server" id="itemPlaceHolder"></tr>
</LayoutTemplate>
<ItemTemplate>
<tr>
<td><%# Item.TestStringA %></td>
<td><%# Item.B.TestStringB %></td> (this is what I mean)
</tr>
</ItemTemplate>
</asp:ListView>
This is also working when using the Eval-method (if unable to use 'ItemType'):
<%# Eval(B.TestStringB) %>
I want to loop the ListView´s items and use the values from the container, without saving them in hidden fields (isn't that the purpose of 'DataKeyNames'?). The issue is, that I can not refer to a property of another object by it's link-attribute/property (in the example B.Id) in DataKeyNames. When I do like this, I get an error telling me that there´s no property called 'B.Id'):
<asp:ListView runat="server" ID="lwTest" ItemType="A" DataKeyNames"Id, B.Id">
<LayoutTemplate>
<tr runat="server" id="itemPlaceHolder"></tr>
</LayoutTemplate>
<ItemTemplate>
<tr>
<td><%# Item.TestStringA %></td>
<td><%# Item.B.TestStringB %></td> (this is what I mean)
</tr>
</ItemTemplate>
</asp:ListView>
Does anyone know if it is possible to do this? I know that I can add readonly properties returning the value directly to the bound object (but I would like to avoid this if possible):
Class A
Property Id as Integer
Property TestStringA as String
Property B as B
ReadOnly Property BId as Integer
Get
Return B.Id
End Get
End Property
End Class
Class B
Property Id as Integer
Property TestStringB as String
End Class
Thank you in advance!

First of all, you can only use direct properties of a class as your DataKeyNames. With the Eval instruction you can go deeper(but I believe only one level).
What you can do is change what is shown in your list view through the code-behind.
To do this, add an ItemDataBound event.
The data for the item that is being created can be found in e.Item.DataItem end will be of type A (do cast it before using).
You should be able to access your controls directly through e.Item (don't know which properties to use by heart on this one) and once you've found the correct cell, you can set any text to it you want and since you have the bound instance of A itself you can call any property or sub property you want without restrictions.
This function gets called for every entry in your ListView, so they should only provide you data of the current entry.
Hope this helps you solve your issue.

Related

Databinder inside a "for" loop in an ASPX page

I'm still trying to understand how I can take advantage from DataBinder ( Is there a way to use a DataBinder.Eval statement as an index of a specific array in an ASPX page? ).
I'm currently building tables with the help of repeater and I would like to use a loop defining the Item label dynamically, to allow more interactions.
Currently, this test code is working:
<asp:Repeater id="Fish" runat="server">
<table>
<ItemTemplate>
<tr>
<td><%# Container.DataItem("ITEM")%></td>
<td><%# Container.DataItem("AGG")%></td>
</tr>
</ItemTemplate>
</table>
</asp:Repeater>
But as you can imagine, this type of structure doesn't allow to choose dynamically the columns that are displayed from the columns to be ignored.
I was thinking that by using a "for" loop structure, I would be able to choose dynamically which column could be displayed. And I tried this as a test:
Public Test_id() As String
Public Test_idp As String
<% Test_id = New String() {"id", "Agg"} %>
<asp:Repeater id="Fish" runat="server">
<table>
<ItemTemplate>
<tr>
<% For Each Test_idp as String In Test_id%>
<td><%# Container.DataItem(Test_idp)%></td>
<% Next Test_idp%>
</tr>
</ItemTemplate>
</table>
</asp:Repeater>
which is not working... and is granted by the following error message:
Overload resolution failed because no Public 'Item' is most specific for these arguments:
'Public Overrides ReadOnly Property Item(name As String) As System.Object': Not most specific.
'Public Overrides ReadOnly Property Item(i As Integer) As System.Object': Not most specific.
Any idea?
Edit:
to answer Mike C's question, I have tried DataBinder.Eval(Container.DataItem, Test_idp) instead of Container.DataItem(Test_idp). It still does not work, but the error is different:
System.ArgumentNullException: value cannot be null
Test_Idp is an Object (since it wasn't declared otherwise).
Therefore, the compiler cannot figure out which of those overloads to call.
You need to explicitly declare it As String.
You can use a nested repeater for the columns.
<asp:Repeater id="Fish" runat="server">
<table>
<ItemTemplate>
<tr>
<asp:Repeater id="columns" runat="server">
<ItemTemplate>
<td><%# ((RepeaterItem)Container.Parent.Parent).DataItem("ITEM")%></td>
<td><%# ((RepeaterItem)Container.Parent.Parent).DataItem("AGG")%></td>
</ItemTemplate>
</asp:Repeater>
</tr>
</ItemTemplate>
</table>
</asp:Repeater>

How to hide an item in datalist

I want to hide an item in datalist according to some condition suing ItemBound, how ?
Wrap a PlaceHolder control around the entire content of the ItemTemplate.
Then in your ItemDataBound event, you could do something like:
Protected Sub myDataList_ItemDataBound(sender As Object, e As System.Web.UI.WebControls.DataListItemEventArgs) Handles myDataList.ItemDataBound
If Not Value = Value2 Then
Ctype(e.Item.FindControl("myPlaceHolder"), PlaceHolder).Visible = False
End If
End Sub
A better approach (however I've not had chance to test it), would be to hide the whole item using e.Item.Visible. This way no HTML table elements would be rendered for the item. It would also mean no PlaceHolder would have to be added.
Protected Sub myDataList_ItemDataBound(sender As Object, e As System.Web.UI.WebControls.DataListItemEventArgs) Handles myDataList.ItemDataBound
If Not Value = Value2 Then
e.Item.Visible = False
End If
End Sub
Alternatively, if the values you are checking are from a database source, you could filter the items out before binding:
WHERE Value=#Value2
A simple solution could be to set the visibility of your Item container by evaluating your desired condition in your ItemTemplate:
<ItemTemplate>
<div id="itemdiv" visible='<%# (Convert.ToInt32(Eval("YourValue")) == 5) %>' runat="server">
<%# Eval("SomeOtherValue") %>
</div>
</ItemTemplate>
My example uses a constant but you could use any variable in scope.
Pitfall!
DataList will insist to create empty rows for hidden items, so you may have to use ListView instead to fully control creating your filtered itemlist.
Update
Using a ListView instead will only create rows for visible items:
<ItemTemplate>
<tr id="itemdiv" visible='<%# (Convert.ToInt32(Eval("YourValue")) == 5) %>' runat="server">
<td><%# Eval("SomeOtherValue") %></td>
</tr>
</ItemTemplate>
<LayoutTemplate>
<table border="1">
<tr runat="server" id="itemPlaceholder" />
</table>
</LayoutTemplate>

Object reference not set to an instance of an object (NullreferenceException was unhandled by user code)

How can I get around this exception?
Dim imagepathlit As Literal = DownloadsRepeater.FindControl("imagepathlit")
imagepathlit.Text = imagepath
Here is the repeater:
<asp:Repeater ID="DownloadsRepeater" runat="server">
<HeaderTemplate>
<table width="70%">
<tr>
<td colspan="3"><h2>Files you can download</h2></td>
</tr>
</HeaderTemplate>
<ItemTemplate>
<tr>
<td width="5%">
<asp:Literal ID="imagepathlit" runat="server"></asp:Literal></td>
<td width="5%"></td>
<td> </td>
</tr>
</table>
</ItemTemplate>
</asp:Repeater>
Here is the code that gets the data for the repeater:
c.Open()
r = x.ExecuteReader
While r.Read()
If r("filename") Is DBNull.Value Then
imagepath = String.Empty
Else
imagepath = "<img src=images/" & getimage(r("filename")) & " border=0 align=absmiddle>"
End If
End While
c.Close()
r.Close()
My guess is that there is no control found in the DownloadsRepeater control called imagepathlit, therefore the imagepathlit control is null after the call.
Remember that Control.FindControl() looks up the control based on ID, not the name of the control. Therefore, to find the control in the collection...you would have to had something like this earlier in the application:
Dim imagepathlit As Literal = new Literal()
imagepathlit.ID = "imagepathlit"
UPDATE
Since you're using a repeater, the child controls get layed out a bit differently. You're going to have an instance of the Literal for each Item in the Repeater. Therefore, to get each instance of the control, you have to loop through the Items in the Repeater and call FindControl() on each Item:
For Each item As Item In DownloadsRepeater.Items
Dim imagepathlit As Literal = item.FindControl("imagepathlit")
Next
Assuming the code you posted is where the exception is indeed thrown, I would say that the DownloadRepeater does not have a control in it that has an ID of imagepathlit.
Check your aspx.
Because the control is within the ItemTemplate, you cannot use repeater.findcontrol; you have to loop through the items of the repeater to look for the control, as the itemtemplate is repeatable. So you have to loop through each one to look for the control as in:
foreach (var item in repeater.Items)
{
var control = item.FindControl("ID") as Type;
}
Use that syntax.

FormView not passing a value contained within "runat=server" row

I have the following code in the EditItemTemplate of my FormView:
<tr id="primaryGroupRow" runat="server">
<td class="Fieldname">Primary Group:</td>
<td><asp:DropDownList ID="iPrimaryGroupDropDownList" runat="server" DataSourceID="GroupDataSource" CssClass="PageText"
DataTextField="sGroupName" DataValueField="iGroupID" SelectedValue='<%# Bind("iPrimaryGroup") %>'></asp:DropDownList></td>
</tr>
If I remove the runat="server" for the table row, then the iPrimaryGroup field is bound 100% and passed to the business logic layer properly. However in the case of the code above, it is passed with a value of zero.
Can anyone tell me why this is or how to get around it? This is in a control that needs to hide this table row, based on whether or not an administrator or a regular user is editing it. ie: some fields are admin writeable only and I'd like to hide the controls from the view if the user isn't an admin.
If security is a concern perhaps this might work better
<tr>
<td colspan='2'>
<asp:panel runat='server' visible='<%= IsUserAdmin %>'>
<table>
<tr>
<td class="Fieldname">Primary Group:</td>
<td><asp:DropDownList ID="iPrimaryGroupDropDownList" runat="server" DataSourceID="GroupDataSource" CssClass="PageText" DataTextField="sGroupName" DataValueField="iGroupID" SelectedValue='<%# Bind("iPrimaryGroup") %>'></asp:DropDownList>
</td>
</tr>
</table>
</asp:panel>
</td>
If I'm not mistaken any markup within the panel will not be rendered if visible=false
Have a shot at this:
Remove the runat=server attribute
Define a css class
.hidden{ display:hidden;}
Then set the class attribute based on whether or not the user is an admin
<tr class='<%= if(IsUserAdmin) "" else "hidden" %>' >
It appears that this functionality is by design, although that's not exactly confirmed.
http://weblogs.asp.net/rajbk/archive/2009/08/03/formview-binding-gotcha.aspx
When using the FormView object, if you have a nested control, then two-way databinding isn't going to work properly. You can access the controls in code, and you can get at the data, but it's just not going to automatically update the value in the back end of your Business Logic Layer(BLL) like it's supposed to.
Fortunately, there's a workaround. The way to get it working is to create an event for ItemUpdating. It will have a signature like this:
protected void frmProfile_ItemUpdating(object sender, FormViewUpdateEventArgs e)
This gives you access to the FormViewUpdateEventArgs, which in turn allows you to make changes to the ObjectDataSource values while they are in flight and before they hit your BLL code, as follows:
protected void frmProfile_ItemUpdating(object sender, FormViewUpdateEventArgs e)
{
if (frmProfile.FindControl("iPrimaryGroupDropDownList") != null)
{
DropDownList iPrimaryGroupDropDownList = ((DropDownList)frmProfile.FindControl("iPrimaryGroupDropDownList"));
e.NewValues["iPrimaryGroup"] = iPrimaryGroupDropDownList.Text;
}
}

How to dynamically assign datasource to listview

I am having a problem with dynamically assigning datasource to listview.
For example I have list of receivedBonuses(Bonus), receivedLeaves(Leave) and I want listview to display those list items depending on what link button user clicked.
Researching internet and stackoverflow.com i found 3 solutions:
Using repeater inside the listview. But in my case, I could not apply it to my case and i got totally confused
Using nested listviews. I tried to do like this:
<asp:ListView ID = "bonuses" runat="server" DataSource ='<%# Eval("received_bonuses") %>' >
<ItemTemplate>
<tr>
<td><%# Eval("bonus_desc")%></td>
<td><%# Eval("bonus_type")%></td>
</tr>
</ItemTemplate>
<LayoutTemplate>
<table>
<tr>
<th>Bonus Description</th>
<th>Bonus Received Date</th>
</tr>
<tr ID="itemPlaceholder" runat="server" />
</table>
</LayoutTemplate>
<table>
<tr>
<th>Bonus Description</th>
<th>Bonus Received Date</th>
</tr>
<tr ID="itemPlaceholder" runat="server" />
</table>
</LayoutTemplate>
</asp:ListView>
<br />
and on back code I tried to write like this:
protected void dataBound(object sender, ListViewItemEventArgs e)
{
this.DataBindChildren();
}
It didn't give any errors it just didn't work.
Using data pager
I have no idea how to apply it to my case.
Any help is appreciated.
Thanks a lot.
All you have to do on the server side is change the DataSource or DataSourceID property and call DataBind on the ListView.
You have to make sure when using <%# Eval("") %> syntax that the objects you are binding to have those properties that are named in the Eval. So you may have a problem with with switching datasources when your properties are prepended with the typename and underscore.
That being said. There are 2 options you have an changing a data source. In the click event of the button or whatever switching mechanism you are using you can just write something like.
Not using a DataSource in the markup:
List<Bonus> bonusList = GetBonuses();
MyListView.DataSource = bonusList;
MyListView.DataBind();
Using a DataSource in the markup:
//where bonus list would be the id of the datasource in the markup
MyListView.DataSourceID= "BonusList";
MyListView.DataBind();
Do you need to do this dynamically? If you only have "bonuse" and "leave" can you not create two listviews and then just do display logic to visible=true/false the listview based upon the link button clicked?

Resources