ASP.Net GridView GridViewDeleteEventArgs.Keys empty - asp.net

I have following Gridview:
<asp:GridView ID="GridView1" runat="server" CssClass="table" DataKeyNames="groupId"
DataSource="<%# dsUserGroupsSelected %>" DataMember="Group" etc....>
and after firing RowDeleting event handler:
protected void GridView1_RowDeleting(object sender, GridViewDeleteEventArgs e)
e.Keys is empty. Moreover, in runtime if I check
dsUserGroupsSelected.Group.PrimaryKey
it is poulated with:
{System.Data.DataColumn[1]}
[0]: {groupId}
so it's really odd to me. Am I missing something? I have this kind of a workaround:
int groupId = (int)GridView1.DataKeys[e.RowIndex].Value;
which will work just fine, but I just can't get it why e.Keys (and e.Values) would be empty!? Any ideas?

It looks like this behaviour is intentional.
From http://forums.asp.net/p/1050092/2128091.aspx
Looking in Reflector at Gridview.HandleDelete(), it appears that
e.Keys and e.Values are only populated if
gridview.IsBoundUsingDataSourceID. That is, if you set the DataSource
in code then none of this will work. Good one, Microsoft! Might have
been useful to mention that in the help perhaps??!! Lachlan
Edit:
I ended up making my own data objects and put them in the app_code folder
ex.
public class CustomDataViews
{
public class FileQuery
{
public class File
{
public DateTime CreatedDate { get; set; }
public string FileName { get; set; }
public string Path { get; set; }
public void Delete() { }
}
public ArrayList GetFiles()
{
System.Collections.ArrayList files = new ArrayList();
System.IO.DirectoryInfo di = new System.IO.DirectoryInfo(HttpContext.Current.Server.MapPath("~/UserUpload/"));
foreach (System.IO.FileInfo fi in (from a in di.GetFiles() orderby a.CreationTime descending select a))
{
File myFile = new File();
myFile.CreatedDate = fi.CreationTime;
myFile.FileName = fi.Name;
myFile.Path = "/VACWeb/UserUpload/" + fi.Name;
files.Add(myFile);
}
return files;
}
public void Delete(string FileName)
{
if (FileName != null)
{
string path = HttpContext.Current.Server.MapPath("~/UserUpload/") + FileName;
if (System.IO.File.Exists(path))
System.IO.File.Delete(path);
}
}
}
}
aspx
<asp:GridView ID="gvFiles" runat="server" AutoGenerateColumns="False" DataKeyNames="FileName"
DataSourceID="ods1">
<Columns>
<cc:ExtendedCommandField DeleteConfirmationText="Are you sure you wish to delete this file?"
DeleteText="Delete" ShowDeleteButton="true" />
<asp:BoundField DataField="CreatedDate" HeaderText="Created Date" DataFormatString="{0:MM/dd/yyyy}" />
<asp:BoundField DataField="FileName" HeaderText="File Name" />
<asp:ImageField DataImageUrlField="Path" AlternateText="No Image" HeaderText="Image Preview"
ControlStyle-Width="100px">
<ControlStyle Width="100px" />
</asp:ImageField>
<asp:BoundField DataField="Path" HeaderText="Path" />
<asp:HyperLinkField DataNavigateUrlFields="Path" DataTextField="FileName" HeaderText="Link" />
</Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ods1" runat="server" DeleteMethod="Delete" SelectMethod="GetFiles"
TypeName="CustomDataViews+FileQuery">
<DeleteParameters>
<asp:Parameter Name="FileName" Type="String" />
</DeleteParameters>
</asp:ObjectDataSource>

Simple! ASP is a big piece of .... and GridView is a small piece in the big peace. Have the same problem, and while the workaround is ok for deleting, updating becomes really interesting... The actual problem is it seems that for no good or apparent reason a looooooooot of the functionality of GridView is missing if the DataSource is not a DataSourceControl: Like filling the keys & values properties in the eventargs. Enjoy! Viva Microsoft!

Is it possible you're programatically sorting your gridview in your Page_Load method? If so, try moving the sort into the Page_Init method, and see if that fixes the problem.

Apparently, the GridViewDeleteEventArgs.Keys property only works if you set the GridView's DataSource by setting the DataSourceID property -- i.e. setting the DataSource property and then manually calling DataBind() leaves the Keys property (and the Values property as well) empty.
Source: http://forums.asp.net/t/1050092.aspx

Related

ASP.NET empty GridView & DetailView problem

I have this page with a GridView control inside that's bound to database. GridView has a command row for insert, delete and updating. The problem is that, since my database table is initially empty, I see an empty grid. I don't even see the command row in order to be able to insert anything into it.
I managed to solve this by checking for GridView row count and changing its display to InsertTemplate. But I'm wondering if there's a standard way of doing this, may it already has such functionality?
The same problem with DetailView.
Thanks
You can use the EmptyDataTemplate to handle inserting a new item if the grid's data source is initially empty.
Define your grid in the ASPX as follows:
<asp:GridView runat="server" ID="grid" AutoGenerateColumns="false"
OnRowCommand="grid_RowCommand">
<Columns>
<asp:ButtonField ButtonType="Link" CommandName="Add" Text="Add" />
<asp:BoundField DataField="Id" HeaderText="Id" />
<asp:BoundField DataField="FirstName" HeaderText="First Name" />
<asp:BoundField DataField="LastName" HeaderText="Last Name" />
</Columns>
<EmptyDataTemplate>
<asp:TextBox ID="CustomerFirstName" runat="server"></asp:TextBox><br />
<asp:TextBox ID="CustomerLastName" runat="server"></asp:TextBox><br />
<asp:Button ID="Save" Text="Save" runat="server" CommandName="EmptyAdd"
UseSubmitBehavior="False" />
</EmptyDataTemplate>
</asp:GridView>
A collection of Customer objects is used as the datasource for the grid.
public class Customer
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Bind the grid to a data source (empty for this example).
protected void Page_Load(object sender, EventArgs e)
{
var customers = new List<Customer> {};
grid.DataSource = customers;
grid.DataBind();
}
You can then handle the Add and EmptyAdd command in the grid_RowCommand event handler.
protected void grid_RowCommand(object sender, GridViewCommandEventArgs e)
{
if (e.CommandName == "EmptyInsert")
{
}
if (e.CommandName == "Insert")
{
}
}

ListView + ObjectDataSource SelectMethod called twice when EnableViewState="false"

Note: This question has been completely modified now that I have a simpler example.
I have set up a sample page which only has a ListView and ObjectDataSource. The first time the page comes up (!IsPostBack), my GetList method is called once. After paging (IsPostBack), the GetList method is called twice--the first time with the old paging values and the second time with the new values.
If I set EnableViewState="true" on the ListView, then the GetList method is only called once. It seems to me that the ListView wants an "initial state", which it either gets from ViewState or by re-running the method.
Is there any way to disable ViewState on the ListView and also prevent SelectMethod from being called twice?
ASPX page:
<asp:ListView ID="TestListView" runat="server" DataSourceID="ODS" EnableViewState="false">
<LayoutTemplate>
<asp:PlaceHolder ID="itemPlaceHolder" runat="server" />
<asp:DataPager ID="TestPager" runat="server" PageSize="10">
<Fields>
<asp:NumericPagerField />
</Fields>
</asp:DataPager>
</LayoutTemplate>
<ItemTemplate>
<div><%# Eval("Title") %></div>
</ItemTemplate>
</asp:ListView>
<asp:ObjectDataSource ID="ODS" runat="server" SelectMethod="GetList" SelectCountMethod="GetListCount"
TypeName="Website.Test" EnablePaging="true" />
ASPX code-behind:
namespace Website
{
public partial class Test : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
public IList<DataItem> GetList(int maximumRows, int startRowIndex)
{
return GetListEnumerable().Skip(startRowIndex).Take(maximumRows).ToList();
}
public IEnumerable<DataItem> GetListEnumerable()
{
for (int i = 0; i < 100; i++)
{
yield return new DataItem { Title = i.ToString() };
}
}
public int GetListCount()
{
return 100;
}
}
public class DataItem
{
public string Title { get; set; }
}
}
Either turn ODS caching on.
<asp:ObjectDataSource ID="ODS" ... EnableCaching="true" />
This way the GetList will be called only when new data is needed. Post backs to pages that already had data retrieved will use the cached version and not call the GetList.
Or move your DataPager out of the ListView and set the PagedControlID property.
Actually you should be using the OnSelecting event.
What happens is that ObjectDataSource calls the method SelectMethod twice
First time it gets the data.
Next time it gets the count.
So I think you have to implement the OnSelecting event
<asp:ObjectDataSource ID="ODS" runat="server" SelectMethod="GetList" SelectCountMethod="GetListCount"
OnSelecting="ods_Selecting">
TypeName="Website.Test" EnablePaging="true" />
and then cancel the event when the ObjectDataSource tries to call the count method.
protected void ods_Selecting(object sender,
ObjectDataSourceSelectingEventArgs e)
{
if (e.ExecutingSelectCount)
{
//Cancel the event
return;
}
}
You can look for full implementation as mentioned in the link below
http://www.unboxedsolutions.com/sean/archive/2005/12/28/818.aspx
Hope this helps.
I had a similar problem where it worked different depending on browser. IE one way and all other browsers one way.. Might not be the same issue as you have.
I solved it this way:
protected void DropDownDataBound(object sender, EventArgs e)
{
// Issue with IE - Disable ViewState for IE browsers otherwhise the dropdown will render empty.
DropDownList DDL = (DropDownList)sender;
if (Request.Browser.Browser.Equals("IE", StringComparison.CurrentCultureIgnoreCase))
DDL.ViewStateMode = System.Web.UI.ViewStateMode.Disabled;
else
DDL.ViewStateMode = System.Web.UI.ViewStateMode.Inherit;
}

getting attributes from aspx-page

i have an aspx-page which contains a detailsview.
this detailsview contains one or more templated-fields.
what i need is a additional attribute (or metadata information) to determite the bound datafield.
some thing like that would be nice (simplified):
<asp:DetailsView>
<fields>
<TemplateField DataField="DataField1">
...
</TemplateField>
</fields>
</asp:DetailsView>
is it possible to get attribute "DataField" ?
otherwise i will subclassing TemplateField and add a property :)
I haven't done that for a while, but I seem to remember if you add a public set/get "DataField" property to the TemplateField class, ASP.NET should automatically initialize it with the value you pass in the attribute.
i thought subclassing of TemplateField will do the job:
[AspNetHostingPermission(SecurityAction.Demand, Level=AspNetHostingPermissionLevel.Minimal)]
[DefaultProperty("DataField")]
public class DataTemplateField : TemplateField
{
private String _dataField;
public String DataField
{
get {
return _dataField;
}
set {
_dataField = value;
}
}
}
now u can use this field in detailsview like this
<Fields>
<dvt:DataTemplateField HeaderText="Feld1" DataField="DIS">
<ItemTemplate>
<asp:Button runat="server" Text="Button" />
</ItemTemplate>
</dvt:DataTemplateField>
</Fields>
and get that data
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
foreach(DetailsViewRow row in DetailsView1.Rows)
{
DataControlFieldCell cell = (DataControlFieldCell)row.Cells[1];
if (cell.ContainingField is DataTemplateField)
{
var field = (DataTemplateField)cell.ContainingField;
cell.Enabled = !field.DataField.Equals(fieldToDisable);
}
}
}

Binding Gridview to IList<BusinessObject> that contains an IList<BusinessObject>

I'm having trouble figuring out how to bind a custom IList to a gridview. The IList contains another custom IList. I need to bind a property from this IList to the gridview.
public class Seminar : BusinessObject
{
private string _description = String.Empty;
private List<User> _attendees;
public string Description {get {return _description;} set {_description = value;}}
public List<User> Attendees {get {return _attendees;} set {_attendees = value;}}
}
public class User : BusinessObject
{
private string _name = String.Empty;
public string Name { get { return _name; } set { _name = value; } }
}
Backend page.aspx.cs:
List<Seminar> seminarList = SeminarManager.Get(id);
gridSeminars.DataSource = seminarList;
gridSeminars.DataBind();
Frontend page.aspx:
<asp:GridView ID="gridSeminars" runat="server">
<Columns>
<asp:BoundField DataField="Id" />
<asp:BoundField DataField="Description" />
<asp:BoundField DataField="Name" />
</Columns>
</asp:GridView>
The problem is with populating the "Name" field in the gridview. All suggestions are welcome.
Have you tried,
Attendees.Name
Edit:
Attendees is an IEnumerable itself. The above suggestion only works with Attendee.Name
If you wan´t to display all the attendees you need to make a templatefield and maybe use a repeater or something...something like:
<asp:TemplateField>
<ItemTemplate>
<asp:Repeater runat="server" DataSource='<%# Eval("Attendees") %>'>
<ItemTemplate>
<tr>
<td>
Name
</td>
<td>
<%# Eval("Name")%>
</td>
</tr>
</ItemTemplate>
</asp:Repeater>
</ItemTemplate>
</asp:TemplateField>
Johan's answer is a good one, specifically the part about how you are trying to bind a collection to a column that is expecting something that can be converted to a string.
Another option would be to put a TemplateField that contains the bindable control (Repeater, another GridView, etc.) you choose to use and binding the DataSource property to the Attendees property of your business object.
Also, I don't if you're married to the idea of using a GridView, but in cases like these where you need more control of the layout, I would suggest the new ListView (.NET 3.5) control, or lately I've just been using nested repeaters so I can have more refined control of the layout I am trying to generate.
Hope this helps.
I'm assuming you want to flatten the hierarchy and display one row for every user. If you have access to Linq, you can use this:
List<Seminar> seminarList = SeminarManager.Get(id);
gridSeminars.DataSource = from seminar in seminarList // loop through all of the seminars in the list
from user in seminar.Attendees // loop through all of the users in the current seminar
select new // create a new, flattened object to bind to.
{
seminar.Id,
seminar.Description,
user.Name
};
gridSeminars.DataBind();
http://weblogs.asp.net/zeeshanhirani/archive/2008/03/26/select-many-operator-part-1.aspx

ASP.NET DataGrid and custom paging

I'm trying to implement a DataGrid in ASP.NET, and want to achieve custom paging so that I don't have to provide all the data in one go. I've spent several hours researching on the internet, but haven't found anything useful.
When I view the page I see the first set of results in the grid, with the previous link disabled. When I click next however, I once again see the first page of the grid with the previous link disabled. When debugging the code I ascertained that the MyGrid_PageIndexChanged() event handler is never called.
I've included my simplified code below. I've changed variable names and omited methods to focus on the datagrid paging issue.
In the ASPX file:
<asp:DataGrid ID="myGrid" runat="server" GridLines="None" UseAccessibleHeader="true" AutoGenerateColumns="false" AllowPaging="true" AllowCustomPaging="true" PageIndexChanged="MyGrid_PageIndexChanged">
<PagerStyle Mode="NextPrev" NextPageText="Next >" PrevPageText="< Previous" />
<Columns>
<asp:BoundColumn HeaderText="Title" DataField="Name" />
<asp:BoundColumn HeaderText="Date" DataField="Date" />
</Columns>
</asp:DataGrid>
And in the CS file:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
myGrid.PageSize = 20;
myGrid.VirtualItemCount = GetNumItems();
BindMyGrid();
}
}
protected void MyGrid_PageIndexChanged(object sender, DataGridPageChangedEventArgs e)
{
myGrid.CurrentPageIndex = e.NewPageIndex;
BindMyGrid();
}
private int GetNumItems()
{
return 500;
}
private void BindMyGrid()
{
Data[] array = GetDataFromInternetSomehow();
this.myGrid.DataSource = array;
this.myGrid.DataBind();
}
private class Data
{
public string Date { get; set; }
public string Name { get; set; }
}
Any thoughts on this would be much appreciated.
There is an error in your ASPX: to wire up the PageIndexChanged event handler use the property OnPageIndexChanged (not PageIndexChanged as in your code):
<asp:DataGrid ID="myGrid" runat="server"
OnPageIndexChanged="MyGrid_PageIndexChanged" /// <--- here's the error
...
Then, if you have AllowCustomPaging="true", you must ensure that the GetDataFromInternetSomehow() method will only return the data for the currently selected page, e.g. pass the current page to the method and return only the corresponding data:
GetDataFromInternetSomehow(e.NewPageIndex);
Otherwise, disable custom paging and it will just work (but all data will be loaded everytime).

Resources