I have a Gridview with an ObjectDataSource, sort of like this:
<asp:GridView ID="myGridView" runat="server" AllowSorting="True" ondatabound="myGridView_DataBound" cssClass="coolTable"
OnRowDataBound="myGridView_RowDataBound"
AllowPaging="True" AutoGenerateColumns="False" DataSourceID="myDataSource">
<PagerSettings mode="NextPreviousFirstLast">
</PagerSettings>
</asp:GridView>
<asp:ObjectDataSource ID="myDataSource" runat="server"
SelectMethod="GetSearchResults" EnablePaging="true"
StartRowIndexParameterName="startIndex"
MaximumRowsParameterName="pageSize"
SortParameterName="sortBy" SelectCountMethod="GetSearchCount" >
</asp:ObjectDataSource>
Andy my function, GetSearchResults, is called, so that's all good. The problem is that in GetSearchResults, I want to use other variables besides the ones passed to it, but they do not seem to have values when GetSearchResults runs. I stepped through in the debugger, and I can see that Page_Load is called before GetSearchResults - but referencing any of the controls on my page throws an error, and fields belonging to my page have no value (even though I set them at Page_Load).
I read ASP.Net Object Data Source - Data Binding and skimmed the Page Life Cycle Overview linked to there, but still do not understand why my other variables are not available.
But here is my real question - I don't really care why they aren't available; I would like to know a good pattern to make values available (that were set during Page_Load) to my GetSearchResults function. Currently I'm saving things in session, but that seems kind of ridiculous.
[EDIT to add background]
I am doing some database queries on Page_Load to set some values which in turn affect the layout and content of my page. Those values are also used to modify the selection criteria for the data in my GridView. I started using the ObjectDataSource because to allow me to efficently page through a lot of records (https://msdn.microsoft.com/en-us/library/bb445504.aspx) but didn't initially understand that a new instance of the Page is created and the method called after that - I was thinking it was handled like a postback. I was hoping to avoid saving those interim values in form fields, session variables, etc. It looks like maybe the only way to do that is to fill the Gridview during the normal page lifecycle, but it looks like that means giving up the automatic paging of the Gridview.
In this question Andy points why the page elements are not available in SelectMethod, here is the MSDN explanation :
If it is an instance method, the business object is created and
destroyed each time the method that is specified by the SelectMethod
property is called.
But, in my trials I can access the page variables by current context's Request.Forms collection. It's a little confusing. But if you define some html form elements, you can access them in SelectMethod by Request.Forms collection. Also you can access server variables' values, but, if you dig into it, you can see their name depends on the hierarchy in page control tree.
Here is my trial, in my aspx file :
<input name="txtCriteria"></input>
<asp:Button runat="server" ID="btnPostSearch" OnClick="btnPostSearch_Click"/>
<asp:GridView ID="myGridView" runat="server" AllowSorting="True"
AllowPaging="True" AutoGenerateColumns="False" DataSourceID="myDataSource">
<Columns>
<asp:BoundField DataField="Result" />
</Columns>
<PagerSettings mode="NextPreviousFirstLast">
</PagerSettings>
</asp:GridView>
<asp:ObjectDataSource ID="myDataSource" runat="server" TypeName=""
SelectMethod="GetSearchResults">
</asp:ObjectDataSource>
And this is the code behind file :
public List<SearchResult> GetSearchResults()
{
string criteria = string.Empty;
if (HttpContext.Current.Request["txtCriteria"] != null)
{
criteria = HttpContext.Current.Request["txtCriteria"];
}
List<SearchResult> searchResults = new List<SearchResult>();
searchResults.Add(new SearchResult() { Result = "trial 1 " + criteria });
searchResults.Add(new SearchResult() { Result = "trial 2 " + criteria });
return searchResults;
}
protected void btnPostSearch_Click(object sender, EventArgs e)
{
myGridView.DataBind();
}
When you start using ASP.NET's DataSourceControls (ObjectDataSource, SqlDataSource, AccessDataSource, etc.) and their counterparts DataBoundControls (DropDownList, DetailsView, ListView, FormView, GridView), you really want to forget about ASP.NET lifecycle (and stop pulling hair), and if you do it well, you can even forget about code-behind code (aspx.cs files) because now the system can be pretty automatic between datasources and databound controls.
In fact this paradigm (that has appeared only starting with .NET 2.0) really helps to focus on declarative HTML-like code.
A DataSourceControl uses a collection of objects of type Parameter as parameters to the methods it uses. For example, the ObjectDataSource's SelectMethod uses the SelectParameters property (of ParameterCollection type).
You can define these parameters declaratively. Let's take an example:
<form id="form1" runat="server">
<div>
<asp:TextBox ID="MyTextBox" Text="3" runat="server" />
<asp:Button runat="server" Text="Run" />
<asp:GridView ID="myGridView" runat="server" DataSourceID="myDataSource" />
<asp:ObjectDataSource ID="myDataSource" runat="server"
SelectMethod="GetSearchResults"
TypeName="WebApplication1.Code.MyModel">
<SelectParameters>
<asp:ControlParameter ControlID="MyTextBox" PropertyName="Text" Name="myCount" />
</SelectParameters>
</asp:ObjectDataSource>
</div>
</form>
Here, myDataSource defines a GetSearchResults as the SelectMethod (I've omitted your other parameters but the idea is the same) method on a MyModel class in some class. It also defines a parameter named myCount. This parameter is a ControlParameter (there are others): it will connect to the ASP.NET control MyTextBox which happens to be defined as a TextBox control, and will use the TextBox's Text property as the the value of the myCount parameter.
Here is the code of the object model:
namespace WebApplication1.Code
{
public class MyModel
{
public string Name { get; set; }
public static IEnumerable GetSearchResults(int myCount)
{
for (int i = 0; i < myCount; i++)
{
yield return new MyModel { Name = "item " + i };
}
}
}
}
As you see, the method also has a myCount parameter defined (it's case sensitive, and yes, ASP.NET will convert automatically from string to int, it's almost magic, it uses TypeConverters under the hood), so everything will work as expected, without any code-behind code. It's some kind of an MV pattern (M model V view) and DataSourceControl/DataBoundControl do the binding.
So, you have to think this way now, and use parameters. If the provided list of parameters (QueryString, Cookie, Form, Profile, Route, Session) is no enough, you can provide your own. For example I can define a special parameter that will get myCount randomly (it's just an example :-):
I can use it like this, and I can define custom arguments for this parameter:
<%# Register Namespace="WebApplication1.Code" TagPrefix="my" Assembly="WebApplication1" %>
...
<SelectParameters>
<my:RandomParameter Name="myCount" Min="10" Max="20" />
</SelectParameters>
The custom parameter type code:
public class RandomParameter : Parameter
{
protected override object Evaluate(HttpContext context, Control control)
{
// you can get to page or environment from here with 'context' and 'control' parameters
return new Random(Environment.TickCount).Next(Min, Max);
}
[DefaultValue(1)]
public int Min
{
get
{
object o = ViewState["Min"];
return o is int ? (int)o : 1;
}
set
{
if (Min != value)
{
ViewState["Min"] = value;
OnParameterChanged();
}
}
}
[DefaultValue(10)]
public int Max
{
get
{
object o = ViewState["Max"];
return o is int ? (int)o : 10;
}
set
{
if (Max != value)
{
ViewState["Max"] = value;
OnParameterChanged();
}
}
}
}
Related
I have a grid view that uses dropdownlist to select employees ID but displaying names.
<EditItemTemplate>
<asp:DropDownList ID="DropDownList5" runat="server" AppendDataBoundItems="True" DataSourceID="SqlDataSourceEmployees" DataTextField="name" DataValueField="empID" SelectedValue='<%# Bind("employee") %>'>
<asp:ListItem></asp:ListItem>
</asp:DropDownList>
</EditItemTemplate>
This works fine, but the SqlDataSourceEmployees is called the moment user clicks on the dropdownlist, which causes quite annoying delay, since as I understand first it fires a SQL command (simple SELECT empID,NAME FROM EMPLOYEES WHERE department=#department) and then the list is populated. It would be much better to bind the dropDowList to something that is already in memory, especially that I don't have to worry that the data in the list would change after page has been loaded.
I've thought about loading it to the DataTable on PageLoad and then binding such table to dropDownList but the list cannot find mentioned above table. I've even put the DataTable as public method of the webpage:
public partial class PlannersCurrentRoster : System.Web.UI.Page
{
private DataSet.employeesDataTable employeesTable;
public DataSet.employeesDataTable EmployeesTable
{
get { return employeesTable; }
set { employeesTable = value; }
}
protected void Page_Load(object sender, EventArgs e)
{
DataSetTableAdapters.employeesTableAdapter TA = new DataSetTableAdapters.employeesTableAdapter();
DataSet.employeesDataTable empTable = TA.GetDataByDepartment(lblDepartment.Text);
EmployeesTable = empTable;
but then changing the bind of the list
<EditItemTemplate>
<asp:DropDownList ID="DropDownList5" runat="server" AppendDataBoundItems="True" DataSourceID="EmployeesTable" DataTextField="name" DataValueField="empID" SelectedValue='<%# Bind("employee") %>'>
<asp:ListItem></asp:ListItem>
</asp:DropDownList>
fails to find the "EmployesTable".
EDIT:
Following the solution below I've tried:
protected void GridView5_RowUpdating(object sender, GridViewUpdateEventArgs e)
{ ((DropDownList)GridView5.Rows[e.RowIndex].FindControl("DropDownList5")).DataSource = EmployeesTable;
((DropDownList)GridView5.Rows[e.RowIndex].FindControl("DropDownList5")).DataBind();
}
Which doesn't speed up things a bit, I'm sure that the DDL still takes data from SQL source (when I was trying to remove it, I had an error stating that SelevtedValue is invalid)
So I've tried to assign it one step earlier, during editing event
protected void GridView5_RowEditing(object sender, GridViewEditEventArgs e)
{
((DropDownList)GridView5.FindControl("DropDownList5")).DataSource = EmployeesTable;
((DropDownList)GridView5.FindControl("DropDownList5")).DataBind();
}
but then it fails to find dropdownlist5
EDIT: I give up. After reading this article I've simply changed SQLDataSource type to DataReader which indeed improved performance. Or maybe its the placebo effect for my tired mind.
You can't do it the way you have it since the your employeesTable variable is destroyed as soon as the page is served. Each postback then gets a new instance. If the list is not unique, instead store it into the Cache object. You can then set a timeout for it as well.
If it is based on data for the page, you can either store it in session which could carry it across pages but can degrade performance if you have a large number of objects storing in session across users.
If it's not too large you could store it in ViewState instead. It will then be serialised to the client. The downside is, the data can bloat the HTML sent to the client.
In your case, since the datatable seems to be dependent upon the a text field, it may be better to use the viewstate. In your case though, complexity is added due to the fact you need to know when the text value has changed as well so you can negate the data.
The following is a crude example of ViewState but you can also adapt for Session and the Cache.
private string SelectedDepartmentText
{
get
{
if(ViewState["SelectedDepartmentText"] != null)
return ViewState["SelectedDepartmentText"].ToString();
else
return string.Empty;
}
set{ViewState["SelectedDepartmentText"] = value;}
}
public DataSet.employeesDataTable EmployeesTable
{
get
{
if(!SelectedDepartmentText.Equals(lblDepartment.Text))
{
// if the SelectedDepartmentText isn't the same as the lblDepartment.Text, go fetch it
DataSetTableAdapters.employeesTableAdapter TA = new DataSetTableAdapters.employeesTableAdapter();
ViewState["EmployeesTable"] =TA.GetDataByDepartment(lblDepartment.Text);
// save the lblDepartment.Text value to the viewstate for next time.
SelectedDepartmentText = lblDepartment.Text;
return ViewState["EmployeesTable"];
}
else
{
// let's see if we have something already and return it
if(ViewState["EmployeesTable"] != null)
return (DataSet.employeesDataTable)ViewState["EmployeesTable"];
else
{
// if we don't, let's get it, this also handles an empty string for the
// lblDepartment.Text
DataSetTableAdapters.employeesTableAdapter TA = new DataSetTableAdapters.employeesTableAdapter();
// store it in the viewstate
ViewState["EmployeesTable"] =TA.GetDataByDepartment(lblDepartment.Text);
// and return whatever we got back
return (DataSet.employeesDataTable)ViewState["EmployeesTable"];
}
}
return null;
}
set{ ViewState["EmployeesTable"] = value;}
}
I have two select lists in my ASP.NET site that are filled by the server with some elements.
// .aspx
<asp:dropdownlist id="abc" runat="server"></asp:dropdownlist>
<asp:dropdownlist id="def" runat="server"></asp:dropdownlist>
// .aspx.cs
abc.Items.Add(new ListItem("element1", "value1"));
def.Items.Add(new ListItem("element1", "value1"));
Due to too complicated reasons to explain right now, I also need to modify the options of the select lists with JavaScript, adding some values.
// In the <head> of the .aspx page
var abcList = document.getElementById("abc");
var defList = document.getElementById("def");
var newAbcElement = new Option("element2", "value2", false, false);
var newDefElement = new Option("element2", "value2", false, false);
abcList.options[abcList.length] = newAbcElement;
defList.options[defList.length] = newDefElement;
Of course, this will mess up Even Validation as soon as I send the form back to the server (be it by submitting or as a PostBack from some other form elements with AutoPostBack="true").
Invalid postback or callback argument. Event validation is enabled using in configuration or <%# Page EnableEventValidation="true" %> in a page. For security purposes, this feature verifies that arguments to postback or callback events originate from the server control that originally rendered them. If the data is valid and expected, use the ClientScriptManager.RegisterForEventValidation method in order to register the postback or callback data for validation.
Now, I don't have the resources and budget to completely overhaul the whole page design, so: What is the fastest and easiest way to change the dropdownlist that does not mean I have to rewrite the whole thing?
So that the values added via JavaScript are recognized by my CodeBehind file when submitting the form?
Ok, here is one more option for you. You can add those items to your lists using AsyncPostBackTrigger.
Some hidden fields:
<asp:TextBox ID="newItemsForAbc" runat="server" style="display:none;"></asp:TextBox>
<asp:TextBox ID="newItemsForDef" runat="server" style="display:none;"></asp:TextBox>
<asp:Button ID="addNewItems" runat="server" OnClick="addNewItems_Click"
style="display:none;" />
The Update Panel:
<asp:UpdatePanel runat="server" ID="UpdatePanel" UpdateMode="Conditional">
<ContentTemplate>
<asp:dropdownlist id="abc" runat="server"></asp:dropdownlist>
<asp:dropdownlist id="def" runat="server"></asp:dropdownlist>
</ContentTemplate>
<Triggers>
<asp:AsyncPostBackTrigger ControlID="addNewItems" EventName="Click" />
</Triggers>
</asp:UpdatePanel>
JS Function for doing an async post back:
<script type="text/javascript"> function UpdateStuff(value1, value2)
{
var abcItems = document.getElementById("<%= newItemsForAbc.ClientID %>");
var defItems = document.getElementById("<%= newItemsForDef.ClientID %>");
abcItems.value=value1;
defItems.value=value2;
__doPostBack("<%= addNewItems.ClientID %>","");
}
</script>
Server-side function that handles button click:
protected void addNewItems_Click(object sender, EventArgs e)
{
string[] n1 = newItemsForAbc.Text.Split(';');
string[] n2 = newItemsForDef.Text.Split(';');
foreach(string i in n1)
{
abc.Items.Add(new ListItem(i, i));
}
foreach(string i in n2)
{
def.Items.Add(new ListItem(i,i));
}
}
To Update Your Lists:
var newAbcElements = "Element1;Element2;Element3";
var newDefElements = "Element4;Element5";
UpdateStuff(newAbcElements, newDefElements);
Final note:
This piece of code probably will not do the job for you as it is. You may need to change the way you store new items in a string, thus splitting/parsing would change too. You may even need different strings for displaying a list item and its actual value. But I believe you get the basic idea.
You can disable the ViewState entirely for the DropDownList.
<asp:dropdownlist id="abc" runat="server" EnableViewState="false"></asp:dropdownlist>
To your question update:
This changes the question quite a lot. The new question is answered here: Invalid postback or callback argument. Event validation is enabled using '<pages enableEventValidation="true"/>'
You have a few options.
First, you might rewrite your code so that the server side generates all possible items for the DropDownList and then in your JavaScript remove the unneeded items instead of adding new ones.
Second option is to create a custom class derived from System.Web.UI.WebControls.DropDownList. The class should contain the one method shown below. The important piece is that your custom class will not have the System.Web.UI.SupportsEventValidationAttribute added to it - so DropDownList base methods will skip the event validation automatically. Now replace the usage from <asp:dropdownlist> to your method.
In case you can't modify the .aspx code (or you have a ton of dropdownlists to replace) you might use tag mapping in your configuration.
namespace Project.MyWebControls
{
public class MyDropDownList : System.Web.UI.WebControls.DropDownList
{
protected override bool LoadPostData(string postDataKey, NameValueCollection postCollection)
{
if (base.LoadPostData(postDataKey, postCollection))
return true;
// this means that the value selected was not present in the .Items collection
string[] values = postCollection.GetValues(postDataKey);
if (values == null || values.Length == 0)
return false;
// add the value to the Items collection so that it can be processed later on.
this.Items.Add(new ListItem("Custom value created by JavaScript", values[0]));
this.SetPostDataSelection(this.Items.Count - 1);
}
}
}
Note that depending on your code you might want to remove these custom values from the Items collection before rendering.
Sample for the .aspx file:
<%# Register TagPrefix="my" Namespace="Project.MyWebControls" Assembly="Project" %>
<my:MyDropDownList runat="server" ...></my:MyDropDownList>
I need to build a <table> to display Products. I am not very familiar with GridView or ListView, not sure if they can do these below.
The requirements:
Automatic paging. Fetch only 100 records at a time (not the whole 1000000 records when it first loads)
Sortable. I need to be able to specify the default sort order, but user can also click on the header
Searchable. User needs to be able to filter the data they want to see.
I (the programmer) need to be able to easily turn off/on columns (Ideally I only need to comment out one line to hide a column)
Data will be from SQL Server, multiple tables
Does anyone know any ASP.NET control that can do all the above? Thanks in advance!
Both GridView and ListView can do paging if you put a DataPager in them. ListView is a templated control - the display is completely customised so you have to roll your own "columns"; don't have much use for GridView so I don't know about that one.
As for searching and sorting, there's some sort of builtin support for it that becomes useless if you need a data source ASP.NET can't query directly - an ObjectDataSource lets you create your own data access code, but you also have to implement everything that can't be done automatically.
For searching, you can handle the ODS's Selecting event, you can pass arbitrary parameters to the query method; I believe there's also some way to automagically get control values / query string parameters etc.
For sorting, ListView lets you add Buttons where CommandName="Sort" and CommandArgument="[column to sort by]". When using a non-custom data source, I believe they will Just Work. When using an ObjectDataSource, all they do is make the ListView keep track of a "sort expression" that you can pass into your data source in the ODS.Selecting event. The format of the sort expression is specified… somewhere I can never find when I need it. When using single-column sorting, this will be either "[column name]" for an ascending sort, or "[column name] DESC" for a descending one. You can pass this directly to ObjectQuery.OrderBy; EntityFramework.Extended also provides that extension method for the new API surface. ListView (I believe) only handles single column sorting, for multiple-column sorting you have to manage the sort expression yourself anyway.
The documentation for all this is scattered, a good starting point is this tutorial, its followups, and the links for ListView you can find in the sidebar to the left.
My project also has a reasonably simple use of a ListView in combination with an ObjectDataSource – meaning, no part of it relies on magic RAD features, that follows below. I extracted it from my actual project, so it might have minor inconsistencies.
ListView+ObjectDataSource Example
The following example retrieves "messages" (think news announcements on a company portal) from a WCF service (not included).
MessagesDataSource.cs
An ObjectDataSource delegate that calls a WCF service. The data source is responsible for:
filtering based on the username and type parameters
sorting based on the sort expression in the sort parameter
paging as specified by the skip and take parameters - named to be consistent with the LINQ operators
Because we're using an ObjectDataSource, all of this has be implemented in the delegate – this is where the service is ultimately called.
public enum MessageType
{
None = 0,
All,
General,
OfficeHoursUpdate,
// …
}
[DataObject]
public class MessagesDataSource : IDisposable
{
IMessagesService _svc = new MessagesServiceClient();
[DataObjectMethod(DataObjectMethodType.Select)]
public IEnumerable<Message> Select(string username, MessageType type, string sort, int skip, int take)
{
return _svc.GetMessages(username, type, sort, skip, take);
}
public int SelectCount(string username, MessageType type, string sort, int skip, int take)
{
return _svc.CountMessages(username, type);
}
}
Messages.ascx
Renders a list of messages as a table with one <tbody> per message, with one row split into columns for the message "headers", and a second row with all columns merged for the message body. Points to note:
When a value is selected in the dropdown list, the ListView is refreshed starting with the first page.
All the parameters the MessageDataSource.Select() and .SelectCount() methods take should be declared in the ODS's <SelectParameters>. (Visual Studio will show an error if they don't match.)
The TypeName attribute of the ObjectDataSource control indicates the ODS delegate that gets instantiated by the ODS. For more control over this, handle the ObjectCreating / ObjectCreated events of the ODS. (For instance, you can pass the containing control to the ODS delegate.)
The values for the parameters named by the StartRowIndexParameterName and MaximumRowsParameterName attributes will be provided automatically by the data pager.
I don't believe the value for the SortParameterName parameter is filled in by the ListView when using an ObjectDataSource - it might or might not be when using a GridView. I also don't think it's necessary in this case but then again it's not hurting anybody to keep it there.
I omitted the data pager fields, they're just clutter here.
The CommandArgument values of the sorting LinkButtons in the header don't have to match up the properties of the data objects and can in fact be completely arbitrary.
<asp:Label ID="lblType" runat="server" AssociatedControlID="ddlType" Text="Message Type:"/>
<asp:DropDownList runat="server" ID="ddlType" AutoPostBack="true"
OnSelectedIndexChanged="ddlType_SelectedIndexChanged"
OnLoad="ddlType_Load" />
<table>
<asp:ListView ID="lvMessages" runat="server" DataSourceID="dsMessages" ItemPlaceholderID="message">
<LayoutTemplate>
<thead>
<tr>
<th>
<asp:LinkButton runat="server" Text='Timestamp'
CommandName="Sort" CommandArgument="Timestamp" />
<asp:Literal ID="lvMessages__Timestamp" runat="server"
OnPreRender="UpdateSortIndicator" />
</th>
<th>
Sender
</th>
<th>
<asp:LinkButton runat="server" Text='Subject'
CommandName="Sort" CommandArgument="Subject" />
<asp:Literal ID="lvMessages__Subject" runat="server"
OnPreRender="UpdateSortIndicator" />
</th>
</tr>
</thead>
<tfoot>
<tr>
<td colspan="3">
<asp:DataPager ID="dpMessages" runat="server"
PageSize="10" PagedControlID="lvMessages"
OnInit="dpMessages_Init">
<Fields>
<%-- Data Pager Fields --%>
</Fields>
</asp:DataPager>
</td>
</tr>
</tfoot>
<%--
The following tag gets replaced with the rendered contents of
ItemTemplate for each data item
--%>
<tbody runat="server" id="message" />
</LayoutTemplate>
<ItemTemplate>
<tbody>
<tr>
<td>
<%# Eval("Timestamp") %>
</td>
<td>
<%# Eval("Sender") %>
</td>
<td>
<%# Eval("Subject") %>
</td>
</tr>
<tr>
<td colspan="3">
<%# Eval("Body") %>
</td>
</tr>
</tbody>
</ItemTemplate>
<EmptyDataTemplate>
No messages loaded!
</EmptyDataTemplate>
</asp:ListView>
</table>
<asp:ObjectDataSource ID="dsMessages" runat="server" TypeName="Foo.MessagesDataSource"
DataObjectTypeName="Foo.Message" SelectMethod="Select"
SelectCountMethod="SelectCount" StartRowIndexParameterName="skip" MaximumRowsParameterName="take"
OnSelecting="dsMessages_Selecting" EnablePaging="true" SortParameterName="sort">
<SelectParameters>
<asp:Parameter Name="username" />
<asp:Parameter Name="scope" />
<asp:Parameter Name="sort" />
<asp:Parameter Name="skip" />
<asp:Parameter Name="take" />
</SelectParameters>
</asp:ObjectDataSource>
Messages.ascx.cs
This class is reasonably straightforward and deals mostly with initialising the page and tangential issues like sort indicators. Most of the "interesting" bits have had comments added to them.
public partial class Messages : UserControl
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
// Initialise the dropdown list values
var types = Enum.GetValues(typeof(MessageType));
types.Remove(MessageType.None);
foreach (var t in types)
{
ddlType.Items.Add(new ListItem(t.ToString()));
}
// Default message filter and sort
Type = MessageType.All;
lvMessages.Sort("Timestamp", SortDirection.Descending);
}
}
private MessageType _type;
public MessageType Type
{
get { return _type; }
set
{
if (!_type.Equals(value))
{
ddlType.SelectedValue = value.ToString();
_type = value;
}
}
}
private static readonly IDictionary<SortDirection, string> SortIndicators =
new Dictionary<SortDirection, string>
{
{SortDirection.Ascending, "\u25b4"}, // upwards triangle
{SortDirection.Descending, "\u25be"} // downwards triangle
};
/// This is where you can programmatically add / change the values of
/// parameters that will get passed to MessagesDataSource.Select()
protected void dsMessages_Selecting(object sender, ObjectDataSourceSelectingEventArgs e)
{
// Add "filter" parameters that have to be determined programmatically.
e.InputParameters["username"] = GetUsernameLoggedIn();
e.InputParameters["type"] = Type;
}
/// When the message type changes, go back to the first page
protected void ddlType_SelectedIndexChanged(object sender, EventArgs e)
{
Type = (MessageType) ddlType.SelectedValue);
var pager = (DataPager)lvMessages.FindControl("dpMessages");
if (pager == null)
{
((IPageableItemContainer)lvMessages).SetPageProperties(0, 10, true);
}
else
{
pager.SetPageProperties(0, pager.PageSize, true);
}
}
/// Reload the value from the dropdown list.
protected void ddlType_Load(object sender, EventArgs e)
{
Type = (MessageType) ddlType.SelectedValue;
}
protected void UpdateSortIndicator(object sender, EventArgs e)
{
var indicator = (Literal) sender;
if (indicator.ID.EndsWith("__"+ lvMessages.SortExpression))
{
indicator.Text = SortIndicators[lvMessages.SortDirection];
}
else
{
indicator.Text = "";
}
}
}
Disclaimer: I don't claim to be an authority on the topic and am in fact fairly new to .NET. This just happens to be something I had to deal with recently and was also confused by the scattered documentation that tended to either take shortcuts with a part of the problem (i.e. either use GridView to get a complete view automatically, or use an ADO.NET data source instead of custom data access code); or spend too much space on fluff (e.g. screenshots of what button to click in which Visual Studio wizard.)
It's not free but Telerik's RadGrid is incredibly full-featured and either does 100% of what you need or else it will get you pretty darn close.
Telerik RadGrid: http://demos.telerik.com/aspnet-ajax/grid/examples/overview/defaultcs.aspx
if your data is going to be just displayed only not edited or updated you can go with a Data Repeater control. It is a very light weight control but then you may have write all the logic for the paging and sorting.It is good in a way so that you get fine grained control over your code..
Check this link for more details.ASP.net Repeater Control
What is the right way to do this in a datarepeater control?
<asp:Repeater ID="Repeater1" runat="server">
<ItemTemplate>
<strong><%= Eval("FullName") %></strong><br />
<p>
<%= Eval("Summary") %>
</p>
</ItemTemplate>
</asp:Repeater>
Getting error Databinding methods such as Eval(), XPath(), and Bind() can only be used in the context of a databound control.
I'd like to just write out the FullName and Summary. But I don't want to nest subcontrols.
Is Repsonse.Write the best way?
UPDATE:
Not sure if this is necessary, but the only way I was able to solve it was with controls
The repeater requires a datasource, assigned like so:
public class Foo
{
public string FullName { get;set; }
public string Summary {get;set; }
public Foo(fullName,summary)
{
FullName=fullName;
Summary=summary;
}
}
/// elsewhere...
List<Foo> myFoos = new List<Foo>();
myFoos.Add(new Foo("Alice","Some chick"));
myFoos.Add(new Foo("Bob","Some guy"));
myFoos.Add(new Foo("Charlie","Indeterminate"));
Repeater1.DataSource = myFoos;
Repeater1.DataBind();
As this example shows, your datasource can be anything that implements IEnumerable - lists are my favorites, but most collections in C# fall into this category. Your datasource does not have to come from a database or anywhere particular.
You don't have to use response.write, or subcontrols. (server controls aren't valid inside of a repeater, anyway). You might try replacing
<%=Eval("...
with
<%#Eval("...
I'm unsure of the difference, but the second form is used in most examples.
You can always try the follow:
<%# DataBinder.Eval(Container.DataItem, "FullName") %>
I have an ASP.NET page that uses a repeater nested within another repeater to generate a listing of data. It's to the effect of the following:
<asp:Repeater>
<ItemTemplate>
<span><%#Eval("Data1") %></span>
<!-- and many more -->
<asp:Repeater DataSource='<%#Eval("Data2")%>'>
<HeaderTemplate>
<ul>
</HeaderTemplate>
<ItemTemplate>
<li><%#Container.DataItem%></li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
</ItemTemplate>
</asp:Repeater>
In the (C#) code-behind I'm basically using LINQ to pull a listing of information from an XML document and bind that information to the first repeater.
Searching for the answer to this, it seems the method is to determine whether the data for the nested repeater is empty. If it is, then you set the visibility of the repeater to false.
Unfortunately, I haven't been able to determine how to do that inline, and not in the code-behind (since it won't necessarily work for what I'm doing).
Since my pages aren't validating now, because the ul ends up being empty for any items without Data2, and because I'd like to keep using an unordered list, I seek your help.
Any ideas?
Thanks!
UPDATE:
If it helps, since it could very well be possible to do in the code-behind, the LINQ is something to this effect:
var x = from y in z
select new {
Data1 = d,
// etcetera
Data2 = (from j in k
where j.Value != String.Empty
select j.Value).ToList()
};
blah.DataSource = x;
blah.DataBind();
This won't hide the repeater completely, but you can subclass the Repeater control so that it includes a GridView-like empty data template:
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
public class EmptyCapableRepeater : Repeater
{
public ITemplate EmptyDataTemplate { get; set; }
protected override void OnDataBinding ( EventArgs e )
{
base.OnDataBinding( e );
if ( this.Items.Count == 0 )
{
EmptyDataTemplate.InstantiateIn( this );
}
}
}
You can them use it in your .aspx like this:
<custom:EmptyCapableRepeater runat="server" ID="rptSearchResults">
<ItemTemplate>
<%# Eval( "Result" )%>
</ItemTemplate>
<SeparatorTemplate>
<br />
</SeparatorTemplate>
<EmptyDataTemplate>
<em>No results were found.</em>
</EmptyDataTemplate>
</custom:EmptyCapableRepeater>
Try something like:
<asp:Repeater runat="server" DataSource='<%#Eval("Data2")%>'
Visible='<%# ((IEnumerable)Eval("Data2")).GetEnumerator().MoveNext() %>'>
for the nested repeater
Why not use a ListView? It offers much of the same functionality including an EmptyDataTemplate.
use this:
protected void Repeater1_PreRender(object sender, EventArgs e)
{
if (Repeater1.Items.Count < 1)
{
container.Visible = false;
}
}
When you get your LINQ query executed, check its Count property (providing its a list of some sort). If its 0, then just turn the Visible property to false.
As far as I know you must do this via the codebehind, just use the ItemDataBound event to handle it, you can leave pretty much everything as is, just simply input some logic that gets the dataset and determines if it has entries, if not hide the repeater.
I don't think what you are doing is going to work I get an error when I try and set the DataSource as you are trying to do; however, in the code behind you do this:
Assuming you added a listener to your parent repeater's ItemDataBoundEvent, then you will need to change your linq query slightly to not use an anonymous type (Create a protected class that has your properties) In mjy case I am using dto as the class name.
void rep1_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
Repeater rep2 = (Repeater)e.Item.FindControl("rep2");
rep2.DataSource = ((dto)e.Item.DataItem).y;
rep2.DataBind();
}
I'd love to learn why you think you can't solve this in the code behind.
I know this is an old thread and the answer above is a very nice solution, but I had a similar problem and have found another vary simple solution I thought I would share also. This validates just fine and displays the same.
Just change your footer template to:
<FooterTemplate>
<li style="display:none;">This will not show.</li></ul>
</FooterTemplate>
Or if your using tables:
<FooterTemplate>
<tr> style="display:none;"><td>But something must be in here.</td></tr></table>
</FooterTemplate>
Hope that helps someone!
In the OnItemDataBound event, set visibility to false if ItemType is a Header and set visibility to true if ItemType is an Item.