ASP Webforms Binding
ADO/Dataset notes
MSDN: GridView, DetailsVew, SQLDataSource, ObjectDataSource, Asp.net paging with ODS tutorial
Eval/Bind syntax
Note: do not confuse <%# binding with...
- <%@ Page ... control directives
- <%=DateTime.Now%> - inline expressions (Response.Write)
- <%:DateTime.Now%> - inline expressions in .Net 4 with automatic HtmlEncoding
<%# Container.DataItem("Price") %> | ASP1. Format: DataBinder.Eval(Container.DataItem, "Price", "{0:C}") |
<%# Eval("Price", "{0:C}") %> | ASP2. One way with optional formatting. Eval == DataBinder.Eval (DataBinder==Container in ASP2) |
<%# (((System.Data.DataRowView)Container.DataItem)["Price"]) %> | Explcit cast, avoid reflection. To find the actual type (DataRowView here), add a <%# Container.DataItem %> |
<%# Bind("Price") %> | ASP2. Two way |
<%# MyMethod(Container.DataItem) %> | Remember this can be useful |
<%# Container.DataItemIndex %> | The row index. For instance, for a "manual" radio button in a gridView (Request["rPick"] will be the item index):
<input type="radio" name="rPick" id='r<%# Container.DataItemIndex %>' value='<%# Container.DataItemIndex %>' /> |
ASP Data access
- Most .net websites uses the old .net 1 programatic way: gridview1.DataSource = dataAccess.GetData();
- SqlDataSource had the terrible idea that you want SQL in your page markup.
- You can cache data. EnableCaching=true caches the Select result (+ CacheDuration="60" DataSourceMode="DataSet")
- The MS ORM versions are LinqDataSource (Linq2Sql) and EntityDataSource (EF)
- These have a "Where" property, but for fuller linq use a QueryExtender control (.Net 4). There's a PropertyExpression (==), SearchExpression (StartsWith/EndsWith/Compares), RangeExpression, OrderByExpression (and ThenByExpression) etc.
- DataSources and QueryExtenders have asp:Parameter collections- esp. ControlParameter (control / property) and QueryStringParameter
ObjectDataSource
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" TypeName="PersonRecord"
SelectMethod="FindAll"
UpdateMethod="Update" DataObjectTypeName="PersonRecord" />
<asp:GridView ID="GridView1" runat="server" DataSourceID="ObjectDataSource1"
AutoGenerateEditButton="true" />
The ASP ObjectDataSource is pretty limited for normal business objects/POCOs but it's okay for simple ActiveRecord type classes. In practice I use my ObjectAdaptor a lot, and sometimes you just have to downgrade to a dataTable (here's a base EntityDataSource that reflects the object to create a datatable [CodeBetter].
- Set TypeName to the BLL name
- It's created using default constructor (to keep the instance for caching, override ObjectCreating -supply ObjectInstance- and ObjectDisposing - e.Cancel= true - events - see here)
- Populate the properties and call designated Select/Insert/Update/Delete methods (all made by non-cached reflection).
- For paging, EnablePaging="true" and SelectCountMethod plus (for nondefault named) MaximumRowsParameterName and StartRowIndexParameterName (NB the Configure DataSource wizard wrongly puts this in the asp:Parameter list)
- For InsertMethod there is an InsertParameters to map the properties to arguments. Otherwise set DataObjectTypeName.
- Optimistic concurrency using ConflictDetection= ConflictOptions.CompareAllValues
Obviously, there's no filtering unless you use a Dataset (/table/view) and paging and sorting has to be done manually (but fairly simple for a typed dataset).
Gridviews: Getting the Data/Row
From a asp:ButtonField int rowIndex = Convert.ToInt32(e.CommandArgument);
If you template it, you can do the same trick with one of the following:
CommandArgument='<%# ((GridViewRow) Container).RowIndex %>' or
CommandArgument='<%# DataBinder.Eval(Container, "RowIndex") %>' or
(won't work in nested UpdatePanel) CommandArgument='<%# Container.DataItemIndex %>'.
<asp:GridView ID="GridView1" runat="server" AutoGenerateEditButton="True"
DataKeyNames="Id"
onrowediting="GridView1_RowEditing" onrowcommand="GridView1_RowCommand" >
<Columns>
<asp:ButtonField CommandName="DeleteButton" HeaderText="Delete" Text="Delete" />
<asp:TemplateField HeaderText="Delete (Template)">
<ItemTemplate>
<asp:LinkButton ID="LinkButton1" runat="server" CausesValidation="false"
CommandName="DeleteTemplate" Text="Delete"
CommandArgument='<%# ((GridViewRow)Container).RowIndex %>' />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e)
{
GridView gv = (GridView)sender;
//for ButtonField e.CommandArgument is the integer rowIndex
//for templated columns it's not unless CommandArgument bound to ((GridViewRow)Container).RowIndex
int rowIndex = Convert.ToInt32(e.CommandArgument);
GridViewRow row = gv.Rows[rowIndex];
}
protected void GridView1_RowEditing(object sender, GridViewEditEventArgs e)
{
GridView gv = (GridView)sender;
int rowIndex = e.NewEditIndex; //AutoGenerateEditButton / CommandName="Edit"
GridViewRow row = gv.Rows[rowIndex];
int id = Convert.ToInt32(gv.DataKeys[rowIndex][0]);
}
GridView Events | RowIndex/Row | Data |
---|---|---|
RowCommand | //if ButtonField or CommandArgument set as above int rowIndex = Convert.ToInt32(commandArgument); GridViewRow row = gv.Rows[rowIndex]; |
gv.DataKeys[rowIndex][0] |
RowEditing | int rowIndex = e.NewEditIndex; GridViewRow row = gv.Rows[rowIndex]; |
|
RowUpdating | int rowIndex = e.RowIndex; | //if bound to DataSource e.Keys[0];e.NewValues["Name"] |
Gridviews and DataViews
Bound controls are pretty limited- the fact that DataFormatString will only work if HtmlEncode= false is particularly crap.
<asp:BoundField DataField="Date" HeaderText="Date"
DataFormatString="{0:dd/MM/yyyy}" HtmlEncode="False" />
<asp:BoundField DataField="Cost" HeaderText="Cost"
DataFormatString="{0:c}" HtmlEncode="False" />
Practically you have to template most columns (to add the validators). Oh yes, and the CompareValidator still doesn't understand currency symbols if Type="currency". Grrr.
ButtonFields with ButtonType="Image" always post twice. As a button, or a templated field, it's okay. (MS bug- marked "fixed" but it's still in 3.5)
- Example: DetailView/SqlDataSource with integer identity
- Example: SqlDataSource with guid primary key
If you're deleting, you'll get paging display bugs in GridView because ObjectDataSource doesn't return AffectedRows (SqlDataSource default paging works, but it loads all the data every time).
protected void DataSourceDeleted(object sender, ObjectDataSourceStatusEventArgs e)
{
e.AffectedRows = (int)e.ReturnValue;
}
ImageField
Showing images in gridviews. DataImageUrlFormatString has the url, DataImageUrlField has the property. DataAlternateTextField (optionally with DataAlternateTextFormatString) can be used for alt text.
<asp:ImageField DataImageUrlField="CategoryId" DataAlternateTextField="CategoryName" DataImageUrlFormatString="~/Viewers/ImageViewer.ashx?id={0}" />
GridView PagerRow
Adding text to the pager, without creating a template.
//data is datasource; count is total number
gv.RowCreated += delegate(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.Pager)
AddPagerText(e.Row, count);
};
gv.DataSource = data;
gv.DataBind();
}
private static void AddPagerText(TableRow row, int count)
{
Label lab = new Label();
lab.CssClass = "PagerLabel"; //easy styling
lab.Text = string.Format("{0} found. Result page: ", count);
Table table = new Table(); //put into table like the page numbers
TableRow tabrow = new TableRow();
TableCell cell = new TableCell();
cell.Controls.Add(lab);
tabrow.Controls.Add(cell);
table.Controls.Add(tabrow);
//pager template render one cell containing an HTMLTable
row.Cells[0].Controls.AddAt(0, table);
}
Dynamic Data
Dynamic Data includes scaffolding for Linq2Sql/EF data models (useful for simple Admin websites; it's customizable but I don't think anybody has used it for real-world websites). It also reads richer information about types (UIHints, foreign keys etc) for better presentation- this part is useful (see DataAnnotations.
There are VS2010 templates for EF and L2Sql. You can only have one model.
Use buddy classes to add metadata - add a partial class for the type with MetadataType attribute, and attribute the buddy class ([ScaffoldColumn(false)], UIHint, DisplayFormat, Editable, EnumDataType). You can also use the partial methods OnChanging/OnChanged - for validation, throw a DataAnnotations.ValidationError.
Structure
- Global.asax has a public static MetaModel called DefaultModel; Application_Start RegisterRoutes using
model.RegisterContext(typeof(NorthwindDataContext), new ContextConfiguration() { ScaffoldAllTables = true });
A DynamicDataRoute is added using the DefaultModel. To show ListDetails (master-detail on same page) you have to change the routing (Action = PageAction.List, ViewName = "ListDetails" Model = model) - DynamicData/PageTemplates contains aspx pages: List.aspx/Details.aspx/Edit.aspx etc
- DynamicData/EntityTemplates contains ascx user controls: Default_Edit.ascx etc
- DynamicData/FieldTemplates contains ascx user controls for fields (DateTime, Text, Email)
- DynamicData/CustomPages is empty- create eg Customers/List.aspx to override list for customers. Default routing already does this. You can customize the List GridView by AutoGenerateColumns=false and adding DynamicField (instead of BoundField).
In existing websites
To use dynamic data behaviour (better validation etc) in existing standard gridView/DataSource pages. just add GridView1.EnableDynamicData(typeof(MyModel.Product)) in Page_Init.
You could also add a DynamicDataManager, and in Page_Init do instead DynamicDataManager1.EnableDynamicData( EntityDataSource1).
<asp:DynamicDataManager ID="DynamicDataManager1" runat="server" AutoLoadForeignKeys="true" />
<asp:LinqDataSource ID="LinqDataSource1" runat="server" ContextTypeName="DataAccess.NorthwindDataContext"
TableName="Products" EnableUpdate="true" EnableInsert="true" EnableDelete="true" >
</asp:LinqDataSource>
<asp:GridView ID="GridView1" runat="server" DataSourceID="LinqDataSource1"
AutoGenerateEditButton="true" AutoGenerateDeleteButton="true">
</asp:GridView>
protected void Page_Init(object sender, EventArgs e)
{
//GridView1.EnableDynamicData(typeof(Category));
DynamicDataManager1.RegisterControl(GridView1);
}