ASP.Net binding attributes of a custom control's inner property - c#

I have created a UserControl with an Inner Property called "Actions", which is a List of "Action" objects. The code looks like this:
[ParseChildren(true)]
public class MyLink : UserControl
{
readonly List<Action> _actions = new List<Action>();
[PersistenceMode(PersistenceMode.InnerProperty)]
public List<Action> Actions
{
get { return _actions; }
}
public string Text { get;set; }
public string Url { get;set; }
public string MenuName { get; set; }
protected override void Render(HtmlTextWriter writer)
{
//Build link
StringBuilder sb = new StringBuilder();
sb.Append(#"
<table class=""myLink"">
<tr>
<td class=""myLinkLeft"">" + Text + #"</td>
<td class=""myLinkRight " + MenuName + #"_trigger""> </td>
</tr>
</table>
");
//Build actions
sb.Append("<ul id=\"" + MenuName + "_actions\" class=\"contextMenu\">");
foreach (Action action in _actions)
{
sb.Append("<li class=\"" + action.CssClass + "\">" + action.Text + "</li>");
}
sb.Append("</ul>");
writer.Write(sb.ToString());
}
}
public class Action : UserControl
{
public string Url { get; set; }
public string Text { get; set; }
public string ImageUrl { get; set; }
public string CssClass { get; set; }
}
If I then put this code in my aspx inside a DataRepeater, it works fine:
<uc1:MyLink runat="server" Url="/" Text='<%#DataBinder.Eval(Container.DataItem,"Text") %>' MenuName="contextMenu" id="contextMenu">
<Actions>
<uc1:Action runat="server" Url="http://mysite.com" Text="MyUrl" />
<uc1:Action runat="server" Url="http://google.com" Text="Google" />
</Actions>
</uc1:MyLink>
However, if I try to bind data to the attributes of the Action elements like so:
<uc1:MyLink runat="server" Url="/" Text='<%#DataBinder.Eval(Container.DataItem,"Text") %>' MenuName="contextMenu" id="contextMenu">
<Actions>
<uc1:Action runat="server" Url='<%#DataBinder.Eval(((RepeaterItem)Container.Parent).DataItem,"Url") %>' Text="MyUrl" />
<uc1:Action runat="server" Url="http://google.com" Text="Google" />
</Actions>
</uc1:MyLink>
I merely get the actual text "<%#DataBinder.Eval(((RepeaterItem)Container.Parent).DataItem,"Url") %>" assigned to the Url property, and not the evaluated server expression as I expected.
I've googled this for hours but cannot seem to find anybody else trying to do this. Any ideas why this isn't working and how to get around it?
Thanks,
Bjoern

you set the DataRepeater.Datasource in your aspx to a collection or a list static or get from database ...
instead of using DataRepeater try to make a loop inside that list you re already must create it and create new dynamic Action and that in page_load or page_init
Action a;
foreach(object x in objects)
{
a= new Action();
a.Url = ... ;
a.Text = ... ;
MyLink.Actions.Add(a);
}
Regards

I ended up using tokens for the innermost databinding and then handling the replacement in my control on bind. So the ASPX code looks like this:
<uc1:MyLink runat="server" Url="/">
<Actions>
<uc1:Action Url="/Page.aspx?cpr=##cpr##&opgaveId=##id##" />
<uc1:Action Url="/Test.aspx" />
</Actions>
</uc1:MyLink>
And the added CS code like this:
protected override void OnInit(EventArgs e)
{
DataBinding += BindData;
}
public void BindData(object sender, EventArgs e)
{
MyLink pl = (MyLink) sender;
IDataItemContainer container = (IDataItemContainer) pl.NamingContainer;
foreach (Action action in _actions)
{
action.Url = ReplaceTokens(action.Url, container);
action.Text = ReplaceTokens(action.Text, container);
}
}
private static string ReplaceTokens(string text, IDataItemContainer container)
{
Regex re = new Regex("##.*?##", RegexOptions.Compiled | RegexOptions.Singleline);
StringBuilder sb = new StringBuilder(text);
foreach (Match m in re.Matches(text))
{
string tokenValue = DataBinder.GetPropertyValue(container.DataItem, m.Value.Substring(2, m.Value.Length - 4), "{0}");
sb.Replace(m.Value, tokenValue);
}
return sb.ToString();
}

Related

How to make my class function like a ListItem when binding my repeater?

I need to populate a set of repeater drop down list items from a custom class but cannot seem to figure it out. It works perfectly when I use just a standard ListItem collection, but for some reason my custom class only returns a bunch of empty drop down lists. I'm new to repeaters and am trying to understand them the best I can, but this one kind of has me beat. Code is shown below. I have a base class that contains a child class, the child class is used to populate the drop down lists. When ListItemOptions is set to just a List<ListItem>', the actual type ofListItem', it works perfect. Why doesn't my repeater like my class?
// My base class. FYI: there are about 20 other properties in this class. I'm not just aimlessly jumping to a child class for fun!
public class ConfiguratorOption
{
private List<ListItemOptions> _listItemOptions = new List<ListItemOptions>();
public ConfiguratorOption() { }
public List<ListItemOptions> ListItemOptions { get { return _listItemOptions; } set { _listItemOptions = value; } }
public void AddListItemOption(string nodeId, string value, string displayName)
{
ListItemOptions lioNew = new ListItemOptions();
NodeID = nodeId;
Value = value;
DisplayName = displayName;
ListItemOptions.Add(lioNew);
}
}
// The child class.
public class ListItemOptions : IEnumerable
{
private string _nodeID = string.Empty;
private string _displayName = string.Empty;
private string _value = string.Empty;
public ListItemOptions() { }
public string NodeID { get { return _nodeID; } set { _nodeID = value; } }
public string Value { get { return _value; } set { _value = value; } }
public string DisplayName { get { return _displayName; } set { _displayName = value; } }
public IEnumerator GetEnumerator()
{
return (IEnumerator)this;
}
}
// The infamous repeater...
<asp:Repeater runat="server" ID="rptConfigurationOptions">
<ItemTemplate>
<div class="ConfigurationBlock">
<asp:DropDownList ID="ddnOption" runat="server" CssClass="DropDownStandard" style="width: 500px;" DataTextField="DisplayName" DataValueField="Value" AutoPostBack="true"
DataSource='<%# Eval("ListItemOptions") %>' OnSelectedIndexChanged="ddnOption_SelectedIndexChanged" ></asp:DropDownList>
</div>
</ItemTemplate>
</asp:Repeater>
// And the repeater code..
private void BindRepeater<T>(Repeater rpt, List<T> cOp)
{
rpt.Controls.Clear();
rpt.DataSource = cOp;
rpt.DataBind();
}
You can't use Eval this way. Eval will essentially evaluate the property and return a string. In your case that could render out looking like:
<asp:dropdown DataSource='List<ListItemOptions>'/>
Don't set the datasource via Eval. Handle the repeater's ItemDataBound event and set the DropDownList's datasource manually to the actual property.

HOW TO create new gridview column type

I'm creating a custom gridview control for my ASP.NET APP and one of the things I want it to create a new custom type of column in order to do the following:
(This is how it looks now to create a column)
<asp:TemplateField>
<HeaderTemplate>
<asp:LinkButton Text="R. Name" ToolTip="Resource Name" CommandName="Sort" CommandArgument="ResourceName"
runat="server" />
<uc:GridViewFilter ID="ucGridViewFilterResourceName" ColumnName="ResourceName" AssociatedControlType="TextBoxString"
OnFilterApplied="ucGridViewFilter_FilterApplied" runat="server" />
</HeaderTemplate>
<ItemTemplate>
<%# Eval("ResourceName") %>
</ItemTemplate>
I would like to have something like this:
<asp:GridViewExColumn HeaderTitle="R. Name" HeaderToolTip="Resource Name" ColumnName="ResourceName" SearchType="TextBoxString" OnFilterApplied="ucGridViewFilter_FilterApplied" Text='<%# Eval("ResourceName") %>' />
Any one can show some light at the end of the tunnel? I'm totally lost, I could successfully create my custom gridview but don't know of where to start with this custom column type. Thanks
I finally found the way to do what I was looking for, and I'll provide you with my founds and maybe you can even make it better.
I created a new .cs extending the control DataControlField like this:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Web.UI;
using System.Web.UI.WebControls;
using VMPortal.Tools;
public class GridViewExColumn : DataControlField
{
public string HeaderToolTip { get; set; }
public string GridViewID { get; set; }
public string SearchType { get; set; }
public string DataField
{
get
{
object value = ViewState["DataField"];
if (value != null)
return value.ToString();
return string.Empty;
}
set
{
ViewState["DataField"] = value;
OnFieldChanged();
}
}
protected override DataControlField CreateField()
{
return new BoundField();
}
public override void InitializeCell(DataControlFieldCell cell, DataControlCellType cellType, DataControlRowState rowState, int rowIndex)
{
base.InitializeCell(cell, cellType, rowState, rowIndex);
if (cellType == DataControlCellType.Header)
{
var lb = new LinkButton
{
Text = HeaderText,
ToolTip = String.IsNullOrWhiteSpace(HeaderToolTip) ? HeaderText : HeaderToolTip,
CommandName = "Sort",
CommandArgument = DataField
};
var tt = cell.Controls;
cell.Controls.Add(lb);
if (!String.IsNullOrWhiteSpace(SearchType))
{
// Add filter control
var ctrlFilter = Control.Page.LoadControl("~/Controls/GridViewFilter.ascx") as GridViewFilter;
cell.Controls.Add(ctrlFilter);
ctrlFilter.ID = GridViewID + DataField;
ctrlFilter.ColumnName = DataField;
ctrlFilter.AssociatedControlType = SearchType;
ctrlFilter.FilterApplied += ctrlFilter_FilterApplied;
}
}
else if (cellType == DataControlCellType.DataCell)
cell.DataBinding += new EventHandler(cell_DataBinding);
}
protected void cell_DataBinding(object sender, EventArgs e)
{
var cell = (TableCell)sender;
var dataItem = DataBinder.GetDataItem(cell.NamingContainer);
var dataValue = DataBinder.GetPropertyValue(dataItem, DataField);
string value = dataValue != null ? dataValue.ToString() : "";
cell.Text = value;
}
protected void ctrlFilter_FilterApplied(object sender, EventArgs e)
{
var filterExpression = sender as FilterExpression;
if (filterExpression != null)
{
if (FilterApplied != null)
FilterApplied(null, EventArgs.Empty);
}
}
Now I can add a new column like the follow
<uc:GridViewExColumn HeaderText="R. Name" HeaderToolTip="Resource Name" DataField="ResourceName" SearchType="TextBoxString" GridViewID="ucGridViewEx" OnFilterApplied="ucGridViewFilter_FilterApplied" />
Hope it helps someone else.

asp.net CompositeDataBoundControl for a single object

I have created a CompositeDataBoundControl that i can databind perfectly well.
Now i want to do the same thing, but not for a list of objects, but for a single object.
Reasons is that i want my colleagues to have the ability to simply use the <%# Eval("X") %> in their front end code.
The problem is that the CompositeDataBoundControl has a method that i have to override, which only accepts a collection as a datasource
CreateChildControls(System.Collections.IEnumerable dataSource, bool dataBinding)
Is there a way to do the same thing for a single object?
You need to override the DataSource property
public class SingleObjectView : CompositeDataBoundControl
{
private object dataSource;
public override object DataSource
{
get { return new List<object> { dataSource }; }
set { dataSource = value; }
}
protected override int CreateChildControls(System.Collections.IEnumerable dataSource, bool dataBinding)
{
SingleItem singleItem = null;
if (dataBinding)
{
var it = dataSource.GetEnumerator();
it.MoveNext();
singleItem = new SingleItem(it.Current);
}
else
{
singleItem = new SingleItem(null);
}
ItemTemplate.InstantiateIn(singleItem);
Controls.Add(singleItem);
if (dataBinding)
singleItem.DataBind();
return 1;
}
[PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(SingleItem))]
public ITemplate ItemTemplate { get; set; }
}
public class SingleItem : Control, IDataItemContainer
{
public SingleItem(object dataItem)
{
DataItem = dataItem;
}
public object DataItem { get; set; }
public int DataItemIndex
{
get { return 0; }
}
public int DisplayIndex
{
get { return 0; }
}
}
edit to show the usage
<ctrl:SingleObjectView ID="productView" runat="server">
<ItemTemplate>
<%# Eval("ProductName") %>
<br />
<%# Eval("Price","{0:c}") %>
</ItemTemplate>
</ctrl:SingleObjectView>
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
productView.DataSource = new { ProductName="My Product", Price="10"};
productView.DataBind();
}
}
So the DataSource receives an object which doesn't implement IEnumerable, but overriding the DataSource property of the SingleObjectView control, ensures that CreateChildControls is called.
What about create a list of control, add one single object and then call the method?
I use FormView to allow an instance of a single object to be templated:
protected void fvProduct_Init(object sender, EventArgs e)
{
// Load product template
fvProduct.ItemTemplate = LoadTemplate("Templates/ProductTemplate.ascx");
// Bind product to data source
fvProduct.DataSource = new[] { Product };
fvProduct.DataBind();
}
Then in ProductTemplate.ascx you can do stuff like this:
<h1><%# Eval("ProductName") %></h1>
<p><%# Eval("Description") %></p>
<h4><%# Eval("Price", "{0:C}")%></h4>
In your scenario you mentioned you were using a custom control but the idea is basically the same as the answers above. You just create an enumerable with one item and bind it. The reason for using a FormView is because it is designed to only display exactly one item at a time.

Problem with threads(and redirect) while searching list with queryString

EDIT: Have updated the code, The remaining problem is that i need to wait for the "The thread '' has exited with code 0" to fire before I can make a new search. If i dont the button events fire, but not the page_Load. Is there a way to handle it?
I am creating a aspx webpage (localhost) to study for a certification. The page contains a grindview that presents data, a search box and a button to summit searches. I use Linq to get the result from the database, querystring to store the search while post-back and the Cache to store the non searched result. The page loads slow the first time and load fast after a refresh (so the caching probably works). The Linq query also gives the expected result and the url on the site is in accord with what the user type in the textbox.
Public void Button_Search(object sender, EventArgs e)
protected void Button1_Click(object sender, EventArgs e)
{
Debug.WriteLine("Button Pressed");
String s = ("~/Matches.aspx");
if (TextBox1.Text != null && TextBox1.Text != "")
{
s = (s + "?Search=" +TextBox1.Text);
}
Debug.WriteLine("Redirction adress:" +s);
Response.Redirect(s, false);
Context.ApplicationInstance.CompleteRequest();
}
then my page_Load function
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
Debug.WriteLine("---------------");
Debug.WriteLine("PageLoad");
if (Request.QueryString["Search"] != null)
{
Debug.WriteLine("SerchValueFound");
search = Request.QueryString["Search"];
}
if (Cache["MatchesCache"] == null)
{
Debug.WriteLine("Cache Loading");
using (ConnectionToDBDataContext context = new ConnectionToDBDataContext())
{
try
{
var lista = (from game in context.Games
join home in context.Teams on game.HomeTeamID equals home.TeamID
join away in context.Teams on game.AwayTeamID equals away.TeamID
join arenaName in context.Arenas on game.ArenaID equals arenaName.ArenaID
select new Match
{
MatchID = (int)game.MatchID,
Date = (int)game.Date,
TimeStart = game.TimeStart,
HomeTeam = home.TeamName,
AwayTeam = away.TeamName,
HomeGoals = (int)game.HomeTeamGoals,
AwayGoals = (int)game.AwayTeamGoals,
Arena = arenaName.ArenaName,
Line = "-"
});
list = lista.ToList();
Cache.Insert("MatchesCache", list, null, DateTime.Now.AddDays(1), System.Web.Caching.Cache.NoSlidingExpiration);
}
catch { Debug.WriteLine("Failed to update cache"); }
}
}
list = (List<Match>)Cache["MatchesCache"];
Debug.WriteLine("List loaded from Cache");
if (search != null && search != "")
{
Debug.WriteLine("Search is beeing done");
List<Match> newList = new List<Match>();
foreach (Match m in list)
{
if (m.AwayTeam.Contains(search) || m.HomeTeam.Contains(search))
{
newList.Add(m);
}
}
list = newList;
}
GridView1.DataSource = list;
GridView1.DataBind();
search = "";
Debug.WriteLine("---------------");
}
Have you tried putting some debug code in there to check whether the list of Match objects are being retrieved from the Cache vs the DB, without using breakpoints?
---- Update ----
I've taken your code (except for the database bit) and put it into an ASP.NET 4.0 web application, and it all seems to be working fine...
Here is the aspx page:
<%# Page Title="Home Page" Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs"
Inherits="WebApplication1._Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head id="Head1" runat="server">
<title></title>
</head>
<body>
<form id="Form1" runat="server">
<div>
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" />
<asp:GridView ID="GridView1" runat="server">
</asp:GridView>
</div>
</form>
</body>
</html>
And here is the code-behind for it:
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace WebApplication1
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
List<Match> list;
string search = null;
Debug.WriteLine("---------------");
Debug.WriteLine("PageLoad");
if (Request.QueryString["Search"] != null)
{
Debug.WriteLine("SerchValueFound");
search = Request.QueryString["Search"];
}
if (Cache["MatchesCache"] == null)
{
Debug.WriteLine("Cache Loading");
try
{
list = new List<Match>
{
new Match{MatchId = 1, Date = 1, TimeStart = 1, AwayTeam = "the flying fijians", HomeTeam = "wallabies"},
new Match{MatchId = 2, Date = 1, TimeStart = 1, AwayTeam = "wallabies", HomeTeam = "all blacks"},
new Match{MatchId = 3, Date = 1, TimeStart = 1, AwayTeam = "springboks", HomeTeam = "all blacks"},
};
Cache.Insert("MatchesCache", list, null, DateTime.Now.AddDays(1), System.Web.Caching.Cache.NoSlidingExpiration);
}
catch
{
Debug.WriteLine("Failed to update cache");
}
}
list = (List<Match>)Cache["MatchesCache"];
Debug.WriteLine("List loaded from Cache");
if (!string.IsNullOrEmpty(search))
{
Debug.WriteLine("Search is beeing done");
var newList = new List<Match>();
foreach (var m in list)
{
if (m.AwayTeam.Contains(search) || m.HomeTeam.Contains(search))
{
newList.Add(m);
}
}
list = newList;
}
GridView1.DataSource = list;
GridView1.DataBind();
Debug.WriteLine("---------------");
}
}
protected void Button1_Click(object sender, EventArgs e)
{
Debug.WriteLine("Button Pressed");
var s = ("~/Default.aspx");
if (!string.IsNullOrEmpty(TextBox1.Text))
{
s = (s + "?Search=" + TextBox1.Text);
}
Debug.WriteLine("Redirction adress:" + s);
Response.Redirect(s, false);
}
}
public class Match
{
public int MatchId { get; set; }
public int Date { get; set; }
public int TimeStart { get; set; }
public string HomeTeam { get; set; }
public string AwayTeam { get; set; }
}
}
Perhaps it is a problem with your ConnectionToDBDataContext?

How to output strings from a list of Objects using Repeater

I'm not sure if this is the best way to go about this but I'm trying to show some search results using a repeater control. The query is written in the code behind page and i cant figure out how to bind the results to the control. So far I've created a list of saleItem objects, the saleItem object contains the string i want to show in the repeater.
Search.aspx.cs
List<SaleItem> resultsList = new List<SaleItem>();
SqlDataReader reader = doMainQuery.ExecuteReader();
while (reader.Read())
{
SaleItem newItem = new SaleItem((string)reader["saleTitle"]);
resultsList.Add(newItem);
}
showResults.DataSource = resultsList;
showResults.DataBind();
SaleItem.cs
public class SaleItem
{
private String connectionString;
public string saleTitle;
public SaleItem(string s)
{
saleTitle = s;
}
public string getTitle()
{
return saleTitle;
}
}
Search.aspx
id like to be able to show the title in a similar way to this, any ideas?
<asp:repeater
id="showResults"
Runat="server" >
<ItemTemplate>
<%# Eval("saleTitle")%></ItemTemplate> // resultsList.SaleItem.getTitle()?
</asp:repeater>
You can refactor your code as follow
public class SaleItem
{
public string saleTitle {get;set;}
}
public static class Extension
{
public static IEnumerable<T> Select<T>(this SqlDataReader reader, Func<SqlDataReader, T> projection)
{
while (reader.Read())
{
yield return projection(reader);
}
}
}
List<SaleItem> resultsList = new List<SaleItem>();
SqlDataReader reader = doMainQuery.ExecuteReader();
var resultsList = reader.Select(x => new SaleItem { saleTitle = x["saleTitle"].ToString() }).Distinct().ToList();
showResults.DataSource = resultsList;
showResults.DataBind();
<asp:repeater id="showResults" Runat="server" >
<ItemTemplate>
<%# Eval("saleTitle")%>
</ItemTemplate>
</asp:repeater>

Categories