Confused about databinding in ASP.NET - asp.net

Lets say I have an object
class Person {
public string Name { get; set; }
public int Age { get; set; }
}
And that object is retrieved from a Factory (ie Can't use SQLDataSource or anything like that)
Person person = PersonFactory.GetPerson();
How can I two-way DataBind the two properties to Textboxes on a web form? I looked into FormView, but that doesn't seem to fit my needs as I am not iterating over a collection of objects. And when I tried to use it, I don't seem to be getting the posted values in the Person object in the FormUpdated event. And I am binding like this
Markup
<asp:Textbox Text=<%# Bind("Name") %> />
Code behind
FormView1.DataSource = new List() { person };
FormView1.DataBind();
I feel like I am missing something really obvious. Should I be using a FormView? It doesn't seem like it a proper fit for simple data binding, but the <#% Bind %> method must be in some type of container -- is there a more suitable object?

You need to handle the updates to your FormView - the updates in the asp.net databound controls are not automatic. I'd also consider using an ObjectDataSource - keeping your binding all in the markup can make things easier to find. When you use the ObjectDataSource - it'll automatically wrap your single object in an IEnumerable, so binding to a method that returns a Person is acceptable. You could also consider using a DetailsView if you don't want to write out the form yoruself. In your case, you could do the following
<asp:FormView runat="server" DataSourceID="MyPersonDataSource"> ... </asp:FormView>
<asp:ObjectDataSource runat="server" ID="MyPersonDataSource"
TypeName="PersonFactory" DataObjectTypeName="Person"
SelectMethod="GetPerson" UpdateMethod="UpdatePerson" />
And to facilitate this, you'd need an UpdateMethod(Person) method on your PersonFactory class. Doing it this way eliminates your binding from the codebehind, and will allow your updates to your person object to be persisted to your data store without you having to handle the update events yourself.

Try calling the DataBind method on your TextBox controls.
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
MyTextBox1.DataBind();
MyTextBox2.DataBind();
}
I've never tried doing two way binding in exactly this way before, but using the Bind("property") syntax, it should work this way as far as I know.
If calling the DataBind method doesn't work, then the FormView is your best bet.

DataBinder.Eval(Container.DataItem, "Name")
This should do the trick, but I think you need some event-handling of your own to do the 2-way binding.

Related

Is it better to Cast and object or use dynamic during databinding

I guess I am curious what is better?
Casting the DataItem to the type i know it is...
Or
pass the object to a function that expects a Dynamic, and let the DLR do its magic.
<asp:Repeater ID="rptItems" runat="server">
<ItemTemplate>
<div>
<%# FormatBlogLink(Container.DataItem) %>
OR
<%# FormatBlogLink((BlogPost)Container.DataItem) %>
</div>
</ItemTemplate>
</asp:Repeater>
Code
protected string FormatBlogLink(dynamic blogPost)
{
/// Do a bunch of stuff
}
vs:
protected string FormatBlogLink(BlogPost blogPost)
{
/// Do a bunch of stuff
}
My example is simple,
I thought i read that the DLR will cache things it looked at so it,
so I am curious, what is worst for larger data sources... lots of casting or lots of using dynamic?
(or) am i a bit crazy ... :)
My personal opinion would be to cast to the appropriate type, if that is what you are expecting to use. The only reason to use dynamic in your FormatBlogLink is if you expect to pass different objects that just happen to share the same property names and methods, etc. Otherwise, cast to the appropriate type and benefit from intellisense.
I too would opt for the cast - but you can do this in your FromatBlockLink - method (let it take object). This has the advantage that you remove this (little) logic from your view and of course you can check the type in your function.
protected string FormatBlogLink(object blogPost)
{
var post = blogPost as BlogPost;
if (post == null)
{
// throw or use show error-message
}
/// Do a bunch of stuff
}

Why is this DropDownList data binding to a List<String> not working?

I'm trying to bind a List<String> to a DropDownList in a user control. I think I'm doing the right thing, but it seems that after my code executes the bindings are cleared. Here's the code for review!
User control:
<asp:DropDownList ID="subjectNameDropDown" runat="server"/>
<asp:DropDownList ID="yearLevelDropDown" runat="server"/>
Auto-generated designed code-behind:
public partial class NewSiteMetadataUserControl {
protected global::System.Web.UI.WebControls.DropDownList subjectNameDropDown;
protected global::System.Web.UI.WebControls.DropDownList yearLevelDropDown;
}
Code-behind:
public partial class NewSiteMetadataUserControl : UserControl
{
protected override void CreateChildControls()
{
subjectNameDropDown = new DropDownList();
yearLevelDropDown = new DropDownList();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
EnsureChildControls();
// Attempt 1
List<String> subjectNames = GetSubjectValues();
foreach (var subjectName in subjectNames)
subjectNameDropDown.Items.Add(subjectName);
subjectNameDropDown.DataBind();
// Attempt 2
List<String> yearLevels = GetYearLevelValues();
yearLevelDropDown.DataSource = yearLevels;
yearLevelDropDown.DataBind();
}
}
Should this approach work?
If it should, how can I debug what happens after the code executes?
Yes, this approach should work, here's why it currently isn't,
A DropDownList done with DataBind needs a DataSource. This is why Attempt #1 is not working.
If you're binding to a List<string>, there is no clear key/value pair to bind to. This is why when binding to a List<Person> (for example), you need to override .ToString() in the Person class to provide the key/value binding, or manually set the DataTextField, DataValueField.
There is no way for ASP.NET to work out a key/value pair for a string.
Think about what HTML you want. What should be the key/value for a simple string? Doesn't make sense does it.
Since you don't really care about the "key" (only what is displayed), i suggest you bind to a Dictionary<TKey,TValue> instead.
Either make your method return that, or iterate through the list and add them to the dictionary with an index.
The problem here was CreateChildControls. Somewhere in my attempts to make this work I added this method that initialises the controls. This isn't necessary and in fact caused the data bindings to be wiped out, as it was automatically called by the framework after OnLoad.
The solution was to remove this method and the call to EnsureChildControls.

Bind to Object Itself, Not One of It's Properties

Using Asp.net's Bind() method, how do I bind to the object itself, not one of it's properties?
I think what Ryan means here like if you have to an object like this
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
And if you bind Person object to anywhere in GridView or Repeater to any DataSource you only bind Person and it get a default bind value from one of its properties.
support we have a variable Ryan from Person type so i want to get the variable value from calling <%# Eval("Ryan") %> not <%# Eval("Ryan.FirstName") %>
I tried to put an attribute DefaultBindingProperty for the class but it's not working
[System.ComponentModel.DefaultBindingProperty("FirstName")]
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
So does any one knows how to do it properly?
I ended up working around this by adding a property called SelfReference that simply returns this. If anyone reads this and has a better solution, I'd like to hear it.
You may use Container.DataItem instead:
Item='<%# Container.DataItem %>'
I'm not sure to what exactly you want to bind. The only thing that would make sense to me at the moment is to bind to some UI control, say a DropDown control for instance.
There usually some text properties for the value being displayed and value properties for the actual value to function as identifier. On the Dropdown
DataTextField
DataValueField
There you specify DataTextField = "Firstname" and DataValueField = "Id" given that you have an object that has properties "Firstname" and "Id".
On lists you can use the Eval function directly on your ASPX code or you add server-side controls (i.e. Literals, Labels) inside the list templates and implement the ItemDataBound event (taking the Repeater as example). Here's a good example which illustrates this further.
Hope I was able to help a little ;)
I figured out a way somehow. Actually it is in "http://msdn.microsoft.com/en-us/library/ms752347.aspx"
ListBox ItemsSource="{**Binding**}" IsSynchronizedWithCurrentItem="true"/>
Note that although we have emphasized that the Path to the value to use is one of the four necessary components of a binding, in the scenarios which you want to bind to an entire object, the value to use would be the same as the binding source object. In those cases, it is applicable to not specify a Path. Consider the following example:
XAML
Copy

How to sort using GridView and ObjectDataSource?

I have a GridView with an ObjectDataSource and I want to be able to sort it.
Paging works correctly, however Sorting gives me an exception:
The GridView gridView fired event Sorting which wasn't handled.
How do I enable sorting on the server side?
(i.e. gridView.EnableSortingAndPagingCallbacks must remains false)
Set the gridView.AllowSorting property to true. From here the grid should allow you to sort data automatically on postback if you are using an object that implements IBindingList. However, since that is most likely not the case, you should take TheTXI's advice above and handle the sorting event yourself. Either wire the GridView.Sorting event in the codebehind, like so:
gridView.Sorting += new GridViewSortEventHandler(gridView_Sorting);
Handle the sorting inside the gridView_Sorting method, which should look like this:
private void gridView_Sorting(object sender, GridViewSortEventArgs e)
{
//Sorting logic here
}
Also, you can wire the event on the page itself using OnSort="gridView_Sorting" attached to the control.
Remember, since you are setting gridView.EnableSortingAndPagingCallbacks to false, this will not be immediately fired when the user tries to sort, it instead will wait for the postback to the server.
I hope this helps!
EDIT:
Since ObjectDataSource seems to be the middleman of choice, here is a brief explanation of wiring that for sorting as well. Use the following in your page (The full example can be found here on the MSDN, near the bottom):
<asp:GridView ID="TestGridView" runat="server" DataSourceID="ObjectDataSourceTest"
AllowSorting="True">
</asp:GridView>
<asp:ObjectDataSource ID="ObjectDataSourceTest" runat="server"
SelectMethod="SelectMethod"
TypeName="Samples.AspNet.CS.SortingData"
SortParameterName="sortExpression">
</asp:ObjectDataSource>
Instead of actually using the gridView.Sorting event, you'll be jumping over to the ObjectDataSource to take care of the sorting. Once the sort is triggered it should call the method found in SelectMethod in your code behind. Then, inside SelectMethod, you would handle the rebuilding of your GridView object, which would look like:
public void SelectMethod(string sortExpression)
{
//Rebuild gridView table if necessary, same method used in
//on a postback, and retrieve data from the database. Once
//completed sort the data with:
gridView.Sort(sortExpression, SortDirection.(Ascending or Descending))
}
I am using Linq2Sql and a ObjectDataSource and it does Paging and Sorting very well.
I implemented a Class to be used as the ObjectDataSource. It has a Select and a Count method calling my business layer which uses Linq2SQL queries to retrieve data from the DB. The select methods gets the first item index, page size and the sort expression as parameters automatically.
public List<EntityClass> Select(int startIndex, int pageSize, string sortBy) {}
public int Count() {}
In the ASPX, the DataSource is configured like this:
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
SelectMethod="Select" EnablePaging="true"
StartRowIndexParameterName="startIndex"
MaximumRowsParameterName="pageSize"
SortParameterName="sortBy" SelectCountMethod="Count" >
</asp:ObjectDataSource>
The Select and the Count method use Linq queries to retrieve the data from the DB. I use the Skip(), Take() and Orderby() methods. For the OrderBy to accept a string sort expression I use DynamicLinq There is not much to code, Databinding, paging and Sorting are automatically working.
Pass SortExpression in method of Data Access Layer that calls StoredProcedure
and write below way SP to handle sorting on SQL. This way you can improve the performance of your sorting.
Database SP:
Select ROW_NUMBER() OVER(ORDER BY '+#sortExpression +' ) as RowNum
,* from (SELECT CUSTOMERID,
LEDGERDESCRIPTION,
CustomerDescription as CustomerName
WHERE REGIONID ='''+#RegionID+''')t
order by RowNum'
You can use LINQ for this just use OrderBy on the selected column, do this:
public static List<YourDataObject> GetSortedData(string orderBy)
{
List<YourDataObject> sortedDataList = new List<YourDataObject>();
switch (orderBy)
{
case "Col1": sortedEmployeeList = GetDefaultObjects().OrderBy(x => x.Col1).ToList();
break;
case "Col2":
//Do this for all columns
default:
sortedEmployeeList = GetDefaultObjects();
break;
}
return sortedEmployeeList;
}
This is done in the Select method of the object data source.
In the GridView add sort keys in it. And add SelectParameter in the object data source a `orderBy'
<asp:GridView AllowSorting="true" DataSourceID="objDsAllObjects" ....>
<Columns>
<asp:BoundField SortExpression="Col1"/>
<!-- Do this for all columns -->
</Columns>
</asp:GridView >
<asp:ObjectDataSource ID="objDsAllObjects" SortParameterName="orderBy" runat="server"
SelectMethod="GetAllEmployees" TypeName="YourObjectClass"></asp:ObjectDataSource>
It worked for me without changing the DAL layer and stored procedures.

How to databind a property within a property

I cureently have a set up like below
Public ClassA
property _classB as ClassB
End Class
Public ClassB
property _someProperty as someProperty
End Class
what I want to do is to databind object A to a gridview with one of the columns being databound to ClassB._someProperty. When I try to databind it as Classb._someProperty I get a "Field or Property not found on Selected Datasource" error
I have tried to use the objectContainerDataSource and also directly binding to the gridview with no success.
Has anyone come across this in the past?
Ordinary databinding doesn't generally allow for expressions. Under the hood the datagrid is using reflection (rather the executing code the way DataBinder.Eval does on an ASP.NET page) to find the property that you specify to bind to a column. To do what you want it would need to evaluate the binding as an expression, work out that you were looking for a parent -> child relation, and then reflect down to that level. AFAIK the inbuilt databinding on the grid is too dumb to know how to do this.
I've had the same issue recently, and my solution was to do a LINQ projection and bind that to the grid instead. Something like the following (in C# because I'm not comfortable with the LINQ syntax in VB):
IList<ClassA> listOfClassAObjects = GetMyListOfClassAObjectsFromSomewhere();
var projection = from ClassA a in listOfClassAObjects
select new { SomeProperty = a.SomeProperty,
SomeOtherProperty = a.SomeOtherProperty,
SomePropertyFromB = a.ClassB.SomeProperty };
datagrid.DataSource = projection;
datagrid.DataBind();
You'll get back a list of anonymous types containing that projection, and you can bind the appropriate column to SomePropertyFromB.
For extra encapsulation (if you do this a lot) put the projection into an extension method so you can do something like
var data = GetMyListOfClassAObjectsFromSomewhere().ProjectionForDataGrid();
datagrid.DataSource = data;
datagrid.DataBind();
I found the way to do this is to use a template field and eval (see below)
Set the datafield as property classB and then:
<asp:TemplateField HeaderText="_someProperty">
<ItemTemplate>
<%#Eval("classB._someProperty")%>
</ItemTemplate>
</asp:TemplateField>

Resources