I want to drive a RadioButtonLists SelectedValue property from a property in my datasource, but I'm not managing to make it work.
I have a <asp:RadioButtonList on my page bound to an <asp:ObjectDataSource. This datasource in turn provides the following model class:
public class CollectionWithDefault : Collection<string>
{
CollectionWithDefault(IEnumerable<string> items, string defaultItem)
{
foreach (var item in items)
Add(item);
DefaultItem = defaultItem;
}
public string DefaultItem { get; }
}
Notice that this class is a standard collection of strings that also exposes which one of them is the default option.
Consider that I have the following implementation for a value provider. This is a simple in-memory implementation, but keep in mind that this could be coming from a database or any other source:
public static class ItemProvider
{
public static CollectionWithDefault GetAvailableItems()
{
var items = new [] { "option1", "option2", "option3" };
return new CollectionWithDefault(items, items[1]);
}
}
I tried the following:
<asp:ObjectDataSource runat="server"
ID="ItemSource"
TypeName="MyNamespace.ItemProvider"
SelectMethod="GetAvailableItems" />
<asp:RadioButtonList runat="server"
DataSourceID="ItemSource"
SelectedValue='<%# Eval("DefaultItem") #>' />
I'm getting the following exception in the Eval call:
System.InvalidOperationException: 'Databinding methods such as Eval(), XPath(), and Bind() can only be used in the context of a databound control.'
How can I ensure that the correct radio is preselected based on the field coming from my datasource?
Changing the collection model itself to make it work is acceptable, but I can't set the SelectedValue from codebehind. I wanted to rely on the datasource control to do the heavy lifting.
I managed to make this work seamlessly without requiring manual assignments in codebehind by extending the original RadioButtonList control and modifying the core databinding method to honor ListItem objects.
It goes something like this:
public class MyRadioButtonList : RadioButtonList
{
protected override void PerformDataBinding(IEnumerable dataSource)
{
if (dataSource is IEnumerable<ListItem> listItems)
{
...
foreach (var listItem in listItems)
Items.Add(listItem);
...
}
else
{
base.PerformDataBinding(dataSource);
}
}
}
With this in place, it was just a matter of converting my source model into a IEnumerable<ListItem> on the presentation layer (easy to accomplish with an adapter/proxy implementation) and then feed these ListItems to the control.
Once I got this in place, I could see my selected items reflected correctly in the UI based on the datasource field. Considering how trivial the extension is, I feel it was quite worth it :)
The same inheritance approach can probably be used for similar controls like CheckBoxList, which suffers from the very same limitation.
For the more adventurous folks, one could also make this work by introducing extra DataSelectedField and DataEnabledField properties in the control and using Eval on top of them as part of the original databinding algorithm (which already does this with DataTextField and DataValueField). I felt this would be a little bit more involved for my use case and decided to go with a simpler override, but it is definitely a valid approach that could even live along my proposed solution for an even more robust RadioButtonList.
Related
I created several user controls - most containing a single web control (text box, drop down, radio button etc) - along with one or more validation controls. The point being to combine control and validation in a single user control.
I created a base class for these user control with some common functionality - setters for several properties of a single web control, specifically CssClass and Style to be set in the control in the ascx.
Eg a single text box with a single required field validator.
Sample code for the base class:
public WebControl ctrl {get; set;} //allow derived class access to this
public string CssClass
{
set { ctrl.CssClass = value; } //allow CssClass to be set in the aspx page
}
Sample code for derived class:
(in constructor or control OnInit Event - or ?)
base.ctrl = txt; //tell the base class which web control to apply common properties to.
public string ErrorMessage
{
set { val.ErrorMessage = value;} //this works !
}
Sample code for ascx:
<asp:TextBox ID="txt" Cssclass="input-text-m" maxlength="50" runat="server" />
<asp:RequiredFieldValidator ID="val" runat="server" ControlToValidate="txt"
ErrorMessage="">*</asp:RequiredFieldValidator>
Sample code for aspx:
<uc:TextBox ID="ForeName" Cssclass="input-text-m" maxlength="50"
ErrorMessage="Forename" runat="server"/>
The problem I found was that I couldn't find a way for the derived class to set the base class web control reference before the base classes property setters are called.
If I set base.ctrl in the derived class constructor - then the derived class control reference (txt) is still null at this point.
If I set base.ctrl in any of the control events - eg OnInit - then this is too late.
So far I have got around the problem by simply not using a base class, and writing the property setter code in the user control class instead, however this means duplication of code, which I was trying to avoid.
Is there a way to inform the base class of the control I want it to set the properties for in advance of them being set - or am I going about things the wrong way...
What about calling EnsureChildControls before any get/set operations and including the set operation for ctrl = txt in EnsureChildControls? This is pretty standard practice for a normal servercontrol, I would think it would work for UserControls too.
public string CssClass { set { EnsureChildControls(); ctrl.CssClass = value; } }
Override EnsureChildControls, leaving in the call to base, and set ctrl = txt; here after the call to base.
More information: http://msdn.microsoft.com/en-us/library/system.web.ui.control.ensurechildcontrols.aspx
I'm writing item template for repeater in separate control, and then I'm using following code:
HospitalRepeater.DataSource = LocationsList;
HospitalRepeater.ItemTemplate = Page.LoadTemplate("~/UserControls/HospitalDetails.ascx");
HospitalRepeater.DataBind();
This code worked fine, however now we want to add custom events to HospitalDetails control.
We created following event with custom event args:
public class HospitalItemEventArgs : EventArgs
{
public Int32 HospitalID { get; set; }
public HospitalItemEventArgs() { }
public HospitalItemEventArgs(Int32 hID)
{
this.HospitalID = hID;
}
}
public event EventHandler<HospitalItemEventArgs> HospitalAction;
protected virtual void OnHospitalAction(HospitalItemEventArgs e)
{
if (HospitalAction!= null)
this.HospitalAction(this, e);
}
Now here is a problem - I can't access this custom event from my code after loading this control as template, because it returns object of System.Web.UI.ITemplate.
I assumed that this is wrapper above my exact control, but this assumtion is wrong.
Cast to my control type fails with following error message:
Unable to cast object of type 'SimpleTemplate' to type 'UserControls.HospitalDetails'.
I've tried reverse action - load control using
Page.LoadControl("~/UserControls/HospitalDetails.ascx");
It returns object of correct HospitalDetails type, but It does not implement ITemplate interface.
Whan I tried to do that I've received error message:
Unable to cast object of type 'ASP.usercontrols_hospitaldetails_ascx' to type 'System.Web.UI.ITemplate'.
Can anyone help me to deal with this cast, or find another solution which matches following requrements:
Repeater should be bound to list of HospitalItems to display details.
Template for displaying details should hide it's UI interactions and expose few simple events like HospitalAction with hospital ID.
Separate object must have ability to subscribe to this events.
Your best bet is probably to create some <asp:Button /> controls specifying the CommandName and CommandArgument attributes. You can then handle the bubbled even on the repeater itself, interrogate the arguments for CommandName and CommandArgument.
I'm not using a separate control as a template but the same thing can be accomplished like this.
<asp:Repeater ID="myRepeater" runat="server">
<ItemTemplate>
<asp:Button ID="SaveButton"
CommandName="Save"
CommandArgument="{insert hospitol ID here}"
Text="Save"
runat="server" />
</ItemTemplate>
</asp:Repeater>
void myRepeater_ItemCommand(object source, RepeaterCommandEventArgs e)
{
if (e.CommandName == "Save")
{
int id = int.Parse(e.CommandArgument.ToString());
//Do some saving
}
}
Update
The purpose for the button's CommandArgument and CommandName attributes is to allow you to provide the user with individual actions to take on databound ui content. The CommandArgument attribute is to allow you a point of entry back into the data to retrieve the relevant information for the event. I have never tried to put multiple values in this attribute, but I do not see a reason why it would not work.
Another option is to create a separate list that contains HospitalID and DoctorID associations, create a unique ID for each association and store that list in a database (if you need to persist it), cache (if used by multiple users), session (if used by multiple pages by same user) or Viewstate (if used by a single page and the list is relatively short).
I'm trying to show a custom column in my gridview which displays a content type based on a couple of boolean fields in my database. Everything works fine but it's causing a lot of overhead the way I do it now.. like this:
<ItemTemplate>
<asp:Label ID="lblType" runat="server" Text='<%# GetType((int)DataBinder.Eval(Container.DataItem))%>' />
</ItemTemplate>
This calls a function GetType which queries the database based on the ArticleID. Of course this happens for every item in the gridview. Now I would like to know if it's possible to send the current (subsonic) collection item to this function instead? Because the item is already available but I don't know how to put this in my itemtemplate.
My current item is DAL.Article which contains everything I need.
I hope I made myself clear a little !Thanks for your time.
Kind regards,
Mark
Subsonic generated classes are partial and thus extendable.
Let's say you have a DAL object called Person. You can create a new file Person.cs (in a different folder of course).
namespace Your.Dal.Namespace {
public partial class Person
{
public string DisplayName
{
get
{
return String.Format("{0}, {1}", this.LastName, this.FirstName);
}
}
}
}
Now you can access the DisplayName property of your class:
PersonCollection col = new PersonCollection().Load();
foreach(Person p in col)
Console.WriteLine(p.DisplayName);
I use this technique for binding Subsonic Collections to a Windows.Forms DataGridView a lot.
But it should work for asp.net, too.
The setup: Web form with lots of TextBox controls.
When I set any one of the TextBox control's Enabled property to False, I'd like to "swap" that TextBox out for a label at runtime. The idea here being if it's read only anyway, don't display it in a control designed for editing.
I'm thinking this should be pretty simple and reusable, but what's the best way to do this?
Not sure its the best way, I would make a custom server control is a textbox,
then override the render method, check if it is readonly,
if it is read only then render your span tags like a label controls does.
if not then let the base( textbox ) render take over...
public class SpecialTextbox : TextBox
{
public override void RenderControl(HtmlTextWriter writer)
{
if (!this.ReadOnly)
{
base.RenderControl(writer);
}
else
{
writer.Write(string.Format("<span id=\"{0}\" class=\"{1}\">{2}</span>",
this.ClientID,
this.CssClass,
this.Text));
}
}
}
One possible solution would be to create a new control extending TextBox. Your specialized control would then override (parts of) the rendering code, causing the control to render similar to a Label when ReadOnly = true.
Another way would be to look into using a control adapter. You would essentially be able to do the exact same thing that BigBlondeViking reccomends, but you could coninue to use a regular asp:textbox control in your code. That will be much easier on you and other developers.
About control adapters
Ok time to show my complete lack of knowladge for all things web forms but here goes. I am extending the Panel control and OnPreRender sticking some additional controls inside of it (lets just say 1 textbox for simplicity). From here I am just letting the Panels Render method do its thing.
The issue I am having is that obviously every time this control is rerendered it is just sticks that same TextBox in the panel again with the value I am coding in the OnPreRender method. Now I dont actually want to repopulate the panel every time,
I want to stick the textbox contorl in there on first load and have them reloaded from the control/viewstate caches. In this case with my example of just sticking a single textbox in the panel, if the value of the textbox changes and a postback occurs I want that value to to remain the changed value.
Really basic webforms stuff I know, but I have never had to create custom controls in my time. ANy help appreciated.
Chris.
You need to (re)create the child control (the textbox) in OnInit - so that it's there when LoadViewState and ProcessPostBackData is called.
See the server control lifecycle for more info.
Dynamic controls in ASP.NET are tricky, especially if you are new to webforms and the page lifecycle. If you can avoid dynamic controls, do so. Use controlName.Visible=false, and other tricks instead.
If you must then read this article. Rule of thumb,add controls early in the page life cycle, reference them later in the page lifecycle. PreRender is almost the very end, an uncommon place to be adding and using controls.
Not sure if this applies to all versions of .Net, (I think 2.0 or later) but there is a method called CreateChildControls that isn't really a part of the lifecycle exactly, it's basically called anytime the EnsureChildControls method is called. By default it is called before PreRender if it's not a postback. So basically your code would look like this:
public class SomeControl : WebControl, INamingContainer
{
private TextBox someTextBox;
protected override void CreateChildControls()
{
base.CreateChildControls();
someTextBox= new TextBox();
someTextBox.ID = "tbxMain";
Controls.Add(textboxToCheck);
}
}
Now the part to not is that unless you call EnsureChildControls, you can't be 100% sure that the controls exist before the Public Properties on your control are filled by the ViewState load. What does this mean? Well take the code from before and add a property for the CssClass:
public class SomeControl : WebControl, INamingContainer
{
private TextBox someTextBox;
protected override void CreateChildControls()
{
base.CreateChildControls();
someTextBox= new TextBox();
someTextBox.ID = "tbxMain";
Controls.Add(textboxToCheck);
}
public String CssClass { get; set; }
}
In CreateChildControls you won't want this:
someTextBox.CssClass = CssClass;
Since there is no way to be sure the control exists yet. There's a couple ways you can handle this:
public String CssClass
{
get
{
EnsureChildControls();
return someTextbox.CssClass;
}
set
{
EnsureChildControls();
someTextbox.CssClass = value;
}
In this example I am calling EnsureChildControls (Assuming you are setting the CssValue on the textbox in the CreateChildControls method) and setting or getting from the textbox.
Another way is putting anything that depends on the control's public properties in the OnPreRender method:
protected override void OnPreRender(EventArgs e)
{
someTextbox.CssClass = CssClass;
}
Thus avoiding the mess of worrying about the property being filled already during the ViewState load.
One Note:
The use of INamingContainer can be important. Basically all that does is makes sure the controls on the parent control have an id that is unique on the page by applying the parent's name (And maybe more) to the id. Basically if the parent ID is Parent and the child control ID is Child the ID might show up as Parent_Child. This will solve problems with ViewState not populating the properties correctly or not at all.
Inside your code you will need to manage the restore of viewstate information should you need the services of viewstate.
A good example here is this View State example by Microsoft. There are a few other items referenced in the code sample, but it should get you along the right path.