DataRepeater without subcontrols - c#

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") %>

Related

How are HTML tags converted to HtmlControl?

Suppose I have this markup:
<asp:Repeater ID="Repeater1" runat="server">
<ItemTemplate>
<a runat="server" id="myLink" href="<%# Container.DataItem %>">Here</a>
</ItemTemplate>
</asp:Repeater>
In the code-behind, I can find out that <a> is converted to HtmlAnchor:
private void Repeater1_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
HtmlAnchor myLink = (HtmlAnchor)Repeater1.FindControl("myLink");
}
But how does the compiler know that <a> is HtmlAnchor? Is it hard-coded in the compiler?
If I write
<asp:Repeater ID="Repeater1" runat="server">
<ItemTemplate>
<Foo href="<%# Container.DataItem %>">Here</a>
</ItemTemplate>
</asp:Repeater>
and want the <Foo> tag to be converted to my HtmlFoo class, how do I achieve that?
I just want to have a deeper understanding of the compilation process behind the scenes.
You can learn a lot about the internals of ASP.NET by delving into the Reference Source.
It turns out that the mapping from unprefixed HTML tags to HtmlControl subclasses is hard-coded in an internal class called HtmlTagNameToTypeMapper:
static Hashtable _tagMap;
Type ITagNameToTypeMapper.GetControlType(string tagName, IDictionary attributeBag) {
Type controlType;
if (_tagMap == null) {
Hashtable t = new Hashtable(10, StringComparer.OrdinalIgnoreCase);
t.Add("a", typeof(HtmlAnchor));
t.Add("button", typeof(HtmlButton));
t.Add("form", typeof(HtmlForm));
// [and much more...]
_tagMap = t;
}
// [...]
}
GetControlType is called by another internal class called MainTagNameToTypeMapper:
int colonIndex = tagName.IndexOf(':');
if (colonIndex >= 0) {
// [...]
}
else {
// There is no prefix.
// Try the Html mapper if allowed
if (fAllowHtmlTags) {
return _htmlMapper.GetControlType(tagName, attribs);
}
}
There's no public API to register more unprefixed HTML control types.
On a more localized scale, it is possible for a parent control to customize how the tag names of its child controls are interpreted. To do this, derive from ControlBuilder, override GetChildControlType, and decorate the parent control class with the [ControlBuilder(typeof(...)] attribute.
When you add runat="server" to a control in ASP.NET, a corresponding variable of type HtmlControl (or some subclass thereof) is automatically added to your page (in the designer file). That way you can access that control as a variable. For most common HTML controls, there are subclasses of HtmlControl (such as HtmlAnchor for anchor/link tags). Other controls (such as <div>) get the HtmlGenericControl type. For those, you specify the tag (div) as a property. So your Foo tag would get like a div: a variable of type HtmlGenericControl with a tag of "Foo".
Edit: this is for standard HTML elements. If you create an ASP control (such as <asp:TextBox...>, then the resulting variable will be a subclass or WebControl instead of HtmlControl.

Wrestling with ObjectDataSource - other controls and variables not defined

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();
}
}
}
}

Is there an ASP.NET databindable control (like Repeater), which only expects a single item?

I'm trying display lots of properties from a business object on a Web Form. I could of course, create loads of labels and assign all the values in code-behind, but I feel there must be a less verbose way.
What I want is something like an asp:Panel but where you can specify a datasource. It doesn't seem like Panels support any kind of databinding.
What I'd like is something like this
// C#
panel.DataSource = theCompany;
panel.DataBind();
Then:
// ASPX
<asp:Panel runat="server">
Name: <%# Eval("Name") %>
Phone: <%# Eval("Phone") %>
...
</asp:Panel>
..but I can't find anything which will allow me to work in this way.
I thought I might be able to use asp:FormView but this just gives the error "Data source is an invalid type. It must be either an IListSource, IEnumerable, or IDataSource."
A caveat is that I do not want to call a global DataBind() (this has caused us no-end of problems in the past) - I would like the databind to be constrained to a particular part of the page.
It seems you can do this using a Panel, but you have to assign your business object to a page property first, as there's no way to set the business object as a "DataSource" for the panel (as you would for a Repeater control, for instance).
Once the object is assigned as a page property, you can then use the following syntax in the .aspx to access the properties of that object, without needing to manually assign each item to control values in code behind:
<%# Company.Name %>
You don't need to databind (although you can). What you need is a simple expression evaluator. Add a property to your code behind like this
public string Test { get; set; }
protected void Page_Load(object sender, EventArgs e)
{
Test = "<script>alert('test');</script>";
}
Then use it to render code directly to the page like this
The value: <%: Test %>
Note that the <%: syntax escapes the input. If you wish to NOT escape the input then you can use <%= syntax. Note that you don't need to have a string object you can access any properties you like for example
The value lenght: <%: Test.Length %>
Use The below:
<asp:DetailsView runat="server">
Name: <%# Eval("Name") %>
Phone: <%# Eval("Phone") %>
</asp:DetailsView>
Use DetailsView. You can add it from the Toolbox. It's for Single Row Data Presentation.
<asp:DetailsView runat="server">
Name: <%# Eval("Name") %>
Phone: <%# Eval("Phone") %>
</asp:DetailsView>
Why not using DetailsView. Its perfect for what you want. Showing single row of data only and that too in two column form.
I suggest using a standard Repeater, databound with an array containing a single item.
Repeater.DataSource = new [] { theCompany };
Repeater.DataBind();
Advantage over databinding to a Panel: you can still use the ItemType attribute, and have access to the nice strongly typed Item object and don't have to go about using Eval, i.e.:
<asp:Repeater runat="server" Id="Repeater" ItemType="CompanyViewModel">
<ItemTemplate>
Name: <%# Item.Name %>
Phone: <%# Item.Phone %>
</ItemTemplate>
</asp:Repeater>
(Replace "CompanyViewModel" with the Type of your: "theCompany".)
You can also try experimenting with DetailsView, but it's not as malleable as a Repeater.
Create your own user control that shows the properties of the objects. You can use reflection to read property names and values and display in control.
Create a property for your object in your user control. Inside user control code behind write function Show() with below code.
//Build html strin from all propeties
PropertyInfo[] properties = yourObject.GetType().GetProperties();
string lbl = "<label>{0}</label>";
string value= "<span>{0}</span>";
string tab ="\t";
StringBuilder sb = new StringBuilder();
foreach (PropertyInfo pi in properties)
{
var label = string.Format(lbl,pi.Name);
var val = string.Format(value, pi.GetValue(yourObject, null))
sb.Append(label+tab+val);
sb.Append("<br/>")
}
Response.Write(sb.ToString());
Now in your pager add that control and sets its object property in code behind like
myControl.MyObject = yourObject;
myControl.Show();
NickG's answer will work... however consider two scenarios.
If your business object is ever null, the page will crash with an "Object Reference" error. This can be avoided with a cumbersome looking
<% if(MyObject != null) { %><%= MyObject.Prop %><% } %>
... but doing that every time makes for messy code.
If your page uses PostBack processing via UpdatePanel, the business object will have to be reloaded to the property every time the Page lifecycle runs... even if that portion of the page isn't being redrawn. This is because IIS will resolve all the <%= MyObject.Prop %> references regardless, causing wasted CPU cycles and probably wasted database calls if your object is coming from a database.
For these reasons I always use a Repeater control, which is lightweight, supports ViewState, can easily be assigned a one item list, and avoids the aforementioned issues. Here's an example using the HttpContext.Current.Request object as a "business object".
<asp:Repeater ID="rptTest" runat="server">
<ItemTemplate>
Request.URL = <%# Eval("Url") %>
</ItemTemplate>
</asp:Repeater>
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
var objTest = Request; //Using the Request object as a test business object
rptTest.DataSource = new List<System.Web.HttpRequest>() { objTest };
rptTest.DataBind();
}
}
To make this work we can customize ASP.NET Panel by inheriting it and using Custom Server control for ASP.NET
Use Below Code to modify the ASP.NET Panel in an ASP.NET Custom Server Control Project:
[DefaultProperty("Text")]
[ToolboxData("<{0}:CustomPanel runat=server></{0}:CustomPanel>")]
public class CustomPanel : Panel
{
[Bindable(true)]
public object MyDataSource
{
get;
set;
}
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("")]
[Localizable(true)]
public StringBuilder Text
{
get;
set;
}
public void MyDataBind()
{
Text = new StringBuilder();
foreach (PropertyInfo p in MyDataSource.GetType().GetProperties())
{
Text.Append(string.Format("<b>{0}</b>", p.Name));
Text.Append(":");
if (p.GetIndexParameters() == null || p.GetIndexParameters().Length == 0)
Text.Append(p.GetValue(MyDataSource, null));
Text.Append("<br />");
}
}
protected override void RenderContents(HtmlTextWriter output)
{
output.Write(Text);
}
}
Then add this control's reference and toolbox item to your ASP.NET page:
<cc2:CustomPanel ID="MyCustomPanel" runat="server">
</cc2:CustomPanel>
Use the control as shown below:
MyCustomPanel.MyDataSource = theCompany;
MyCustomPanel.MyDataBind();

'object' does not contain a definition error using repeater in ascx file

I have a table called "Entries" and am using LINQ to SQL for data access. I have created a stored procedure that fetches all entries. I've mapped that stored proc to a method in my data context, I've set the return type to "Entry".
I have created a method in my data access class that returns the results of this method call as List. I am binding the list to a repeater in a user control and attempting to access the property "EntryId" in my ascx file and am getting the following error:
'object' does not contain a definition for 'EntryId'
My method in my DataAccess class:
public static List<Entry> GetAllEntries()
{
using (DataClassesDataContext context = new DataClassesDataContext())
{
return context.fbGetAllEntries().ToList();
}
}
And in the user controls Page_PreRender event:
//my alias at the top of the file for clairification
using ASPWebControls = System.Web.UI.WebControls;
protected void Page_PreRender(System.Object sender, System.EventArgs e)
{
rptEntries = new ASPWebControls.Repeater();
rptEntries.DataSource = DataAccess.GetAllEntries();
rptEntries.DataBind();
}
And in my ascx file:
<asp:Repeater ID = "rptEntries" runat = "server" >
<ItemTemplate>
<tr>
<td><input type="checkbox" runat="server" value="<%# Container.DataItem.EntryId %>" /></td>
</tr>
</ItemTemplate>
</asp:Repeater>
When I Googled this error I found it can happen when using anonymous types but that is not the case here. I am certain my repeater has a list of strongly typed objects bound to it. I've tried explicitally casting:
rptEntries.DataSource = (List<Entry>)DataAccess.GetAllEntries();
I am certain my Entry type has an EntryId as I can do this:
foreach (Entry en in (List<Entry>)rptEntries.DataSource)
{
//when I step through I can see these are the correct values
int i = en.EntryId;
}
I've tried importing my namespace in my ascx file:
<%# Import NameSpace="Namespace.in.my.DataClass.cs" %>
I'm not sure where things are going sideways, could anyone suggest what I am doing wrong? Any help would be appreciated, thanks!
Edit: missed a code block
Even though the items are strongly-typed, the repeater control exposes them as object-types in the ItemTemplate. You need to unbox it explicitly:
<%# ((Entry)Container.DataItem).EntryId %>
Note that ASP.Net 4.5 has a strongly-typed repeater that allows you to specify an ItemType property and removes the need for the unboxing.
Try the following in your data-binding statement for the checkbox:
value="<%# Eval("EntryId") %>"

How can I hide a repeater in ASP.NET C# if the DataSource contains no items?

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.

Categories