These articles are now under the title of Custom PageTemplates:
- Custom PageTemplates Part 1 - Custom PageTemplates with Ajax Control Toolkit Tabs
- Custom PageTemplates Part 2 - A variation of Part 1 with the Details and SubGrid in Tabs
- Custom PageTemplates Part 3 - Dynamic/Templated Grid with Insert (Using ListView)
- Custom PageTemplates Part 4 - Dynamic/Templated FromView
Continuing on from Part 3 the same techniques will be applied to the FormView, making the FormView Dynamic and also having the facility to dynamically load user defined Templates at runtime.
Fugure 1 – FormViewPage in action
Note the Edit, Delete and New links these all act on this page and do not redirect to other pages.
Altering the Routing
Replace the default route Listing 1
routes.Add(new DynamicDataRoute("{table}/{action}.aspx") { Constraints = new RouteValueDictionary(new { action = "List|Edit|Details|Insert" }), Model = model });
Listing 1 – original routing in Global.asax
With the new routes in Listing 2
routes.Add(new DynamicDataRoute("{table}/{action}.aspx") { Constraints = new RouteValueDictionary(new { action = "List" }), Model = model }); routes.Add(new DynamicDataRoute("{table}/{action}/FormViewPage.aspx") { Constraints = new RouteValueDictionary(new { action = "Edit|Details|Insert" }), ViewName = "FormViewPage", Model = model, });
Listing 2 – changes to routing in global.asax
Note that the action {table}/{action}/FormViewPage.aspx as a prefix to the page name, this will be used to identify the pages mode (Edit, Delete and Insert)
The FromViewPage
<%@ Page Language="C#" MasterPageFile="~/Site.master" CodeFile="FormViewPage.aspx.cs" Inherits="FormViewPage" %> <asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server"> <asp:DynamicDataManager ID="DynamicDataManager1" runat="server" AutoLoadForeignKeys="true" /> <h2><%= table.DisplayName %></h2> <asp:ScriptManagerProxy runat="server" ID="ScriptManagerProxy1" /> <asp:UpdatePanel ID="UpdatePanel1" runat="server"> <ContentTemplate> <asp:ValidationSummary ID="ValidationSummary_Edit" EnableClientScript="true" HeaderText="List of validation errors" ValidationGroup="FormView_Edit" runat="server" /> <asp:ValidationSummary ID="ValidationSummary_Insert" EnableClientScript="true" HeaderText="List of validation errors" ValidationGroup="FormView_Insert" runat="server" /> <asp:DynamicValidator ID="Validator_Edit" Display="None" ValidationGroup="FormView_Edit" ControlToValidate="FormView1" runat="server" /> <asp:DynamicValidator ID="Validator_Insert" Display="None" ValidationGroup="FormView_Insert" ControlToValidate="FormView1" runat="server" /> <asp:FormView ID="FormView1" DataSourceID="FormViewDataSource" OnItemDeleted="FormView_ItemDeleted" runat="server" onitemcommand="FormView1_ItemCommand" oniteminserted="FormView1_ItemInserted"> </asp:FormView> <asp:LinqDataSource ID="FormViewDataSource" AutoGenerateWhereClause="true" EnableDelete="true" EnableUpdate="true" EnableInsert="true" runat="server"> <WhereParameters> <asp:DynamicQueryStringParameter /> </WhereParameters> </asp:LinqDataSource> <br /> <div class="bottomhyperlink"> <asp:HyperLink ID="ListHyperLink" runat="server">Show all items</asp:HyperLink> </div> </ContentTemplate> </asp:UpdatePanel> </asp:Content>
using System; using System.IO; using System.Web.DynamicData; using System.Web.UI.WebControls; public partial class FormViewPage : System.Web.UI.Page { protected MetaTable table; protected void Page_Init(object sender, EventArgs e) { DynamicDataManager1.RegisterControl(FormView1); table = FormViewDataSource.GetTable(); // supported templates // get tamplate path var formViewTemplatePath = table.Model.DynamicDataFolderVirtualPath + "Templates/FormViewPage/" + table.Name + "/"; // load user templates if they exist if (File.Exists(Server.MapPath(formViewTemplatePath + "ItemTemplate.ascx"))) FormView1.ItemTemplate = LoadTemplate(formViewTemplatePath + "ItemTemplate.ascx"); else FormView1.ItemTemplate = new FromViewPageRowGenerator(table, FormViewTemplateType.ItemTemplate); // load user templates if they exist if (File.Exists(Server.MapPath(formViewTemplatePath + "EditItemTemplate.ascx"))) FormView1.EditItemTemplate = LoadTemplate(formViewTemplatePath + "EditItemTemplate.ascx"); else FormView1.EditItemTemplate = new FromViewPageRowGenerator(table, FormViewTemplateType.EditItemTemplate); // load user templates if they exist if (File.Exists(Server.MapPath(formViewTemplatePath + "InsertItemTemplate.ascx"))) FormView1.InsertItemTemplate = LoadTemplate(formViewTemplatePath + "InsertItemTemplate.ascx"); else FormView1.InsertItemTemplate = new FromViewPageRowGenerator(table, FormViewTemplateType.InsertItemTemplate); // load user templates if they exist if (File.Exists(Server.MapPath(formViewTemplatePath + "EmptyDataTemplate.ascx"))) FormView1.EmptyDataTemplate = LoadTemplate(formViewTemplatePath + "EmptyDataTemplate.ascx"); else FormView1.EmptyDataTemplate = new FromViewPageRowGenerator(table, FormViewTemplateType.EmptyDataTemplate); //FormView1.FooterTemplate = null; //FormView1.HeaderTemplate = null; //FormView1.PagerTemplate = null; } protected void Page_Load(object sender, EventArgs e) { table = FormViewDataSource.GetTable(); Title = table.DisplayName; // I don't know if this is // the best way to do this // get the FormViews mode from the url String path = Request.Path.Substring(0, Request.Path.LastIndexOf("/")); var qs = path.Substring(path.LastIndexOf("/") + 1, path.Length - (path.LastIndexOf("/") + 1)); switch (qs) { case "Details": FormView1.DefaultMode = FormViewMode.ReadOnly; break; case "Edit": FormView1.DefaultMode = FormViewMode.Edit; break; case "Insert": FormView1.DefaultMode = FormViewMode.Insert; break; default: break; } ListHyperLink.NavigateUrl = table.ListActionPath; } protected void FormView_ItemDeleted(object sender, FormViewDeletedEventArgs e) { if (e.Exception == null || e.ExceptionHandled) { Response.Redirect(table.ListActionPath); } } protected void FormView1_ItemCommand(object sender, FormViewCommandEventArgs e) { if (e.CommandName == "Cancel") { // option 1 go back to list //Response.Redirect(table.ListActionPath); // option 2 return to Normal ReadOnly FormView1.DefaultMode = FormViewMode.ReadOnly; } } protected void FormView1_ItemInserted(object sender, FormViewInsertedEventArgs e) { // option 1 go back to list Response.Redirect(table.ListActionPath); // option 2 return to Normal ReadOnly // note sure how to get this working at the moment //Response.Redirect(table.GetActionPath("Details", ???)); } }
Listign 4 – FormViewPage.aspx.cs
From Listings 3 and 4 you can see that this is a basic page like Details.aspx but with a FormView. The real magic goes on in the Page_Init event handler where the Templates are either loaded or dynamically generated and also in the Page_Load event handler where the default mode is detected via the URL.
The RowGenerator
This will generate a table based FormView similar to the Details.aspx generated by the wizard (which at the time of writing was still in preview) the dynamically generated layout can be overridden be defining UserControl custom Templates in the ~/DynamicData/Templates/FormViewPage/<TableName>/<templated>.
/// <summary> /// Template type for the FromViewPageRowGenerator /// </summary> public enum FormViewTemplateType { ItemTemplate, EditItemTemplate, InsertItemTemplate, EmptyDataTemplate, HeaderTemplate, FooterTemplate, PagerTemplate, } /// <summary> /// Renders templates for ListView /// </summary> public class FromViewPageRowGenerator : ITemplate { #region Member Fields, Constructor, Enums & Properties private MetaTable _table; private FormViewTemplateType _type; public FromViewPageRowGenerator(MetaTable table, FormViewTemplateType type) { _table = table; _type = type; } #endregion public void InstantiateIn(Control container) { IParserAccessor accessor = container; // get all the all scaffold columns // except Long String Columns // SubGridViewsAttribute and column order var columnDetails = from c in _table.Columns where c.Scaffold // && !c.IsLongString select new ListViewColumn() { Column = c, SubGridMetaData = c.Attributes.OfType<SubGridViewsAttribute>().FirstOrDefault(), Order = c.Attributes.OfType<SubGridViewsAttribute>().FirstOrDefault() != null && c.Attributes.OfType<SubGridViewsAttribute>().FirstOrDefault().Order > 0 ? c.Attributes.OfType<SubGridViewsAttribute>().FirstOrDefault().Order : int.MaxValue, }; // sort according to Order first and Column Name second // Note: if SubGridViewsAttribute is null or the attribute // has no value for Order then just sort but column display name columnDetails = from sg in columnDetails orderby sg.Order, sg.Column.DisplayName select sg; // call the appropriate template generator switch (_type) { case FormViewTemplateType.ItemTemplate: GetItemTemplate(accessor, columnDetails, DataBoundControlMode.ReadOnly); break; case FormViewTemplateType.EditItemTemplate: GetItemTemplate(accessor, columnDetails, DataBoundControlMode.Edit); break; case FormViewTemplateType.InsertItemTemplate: GetItemTemplate(accessor, columnDetails, DataBoundControlMode.Insert); break; case FormViewTemplateType.EmptyDataTemplate: GetEmptyDataTemplate(accessor, columnDetails); break; case FormViewTemplateType.HeaderTemplate: GetHeaderTemplate(accessor, columnDetails); break; case FormViewTemplateType.FooterTemplate: GetFooterTemplate(accessor, columnDetails); break; case FormViewTemplateType.PagerTemplate: GetPagerTemplate(accessor, columnDetails); break; default: break; } } private void GetItemTemplate(IParserAccessor accessor, IEnumerable<ListViewColumn> columnDetails, DataBoundControlMode templateMode) { // create new table var table = new HtmlTable(); table.Attributes.Add("class", "detailstable"); // add table to accessor accessor.AddParsedSubObject(table); // make sure there are some children columns if (columnDetails.Count() > 0) { // add a cell for each column in table foreach (ListViewColumn columnDetail in columnDetails) { // create new row for template var row = new HtmlTableRow(); // add row to accessor table.Rows.Add(row); // create field name cell var fieldNameCell = new HtmlTableCell(); // add cell to row row.Cells.Add(fieldNameCell); // set the title fieldNameCell.InnerText = columnDetail.Column.DisplayName; // create field cell var fieldCell = new HtmlTableCell(); // add cell to row row.Cells.Add(fieldCell); // instantiate a DynamicControl for this Children Column var lvColumn = new DynamicControl(templateMode) { ID = columnDetail.Column.Name, ValidationGroup = "FormView_" + templateMode.ToString(), // set data field to column name DataField = columnDetail.Column.Name, }; // add control to cell fieldCell.Controls.Add(lvColumn); } // create new row for template var commandRow = new HtmlTableRow(); // add row to accessor table.Rows.Add(commandRow); // create the cell to hold the command buttons var commandCell = new HtmlTableCell(); commandCell.Attributes.Add("class", "nowrap"); commandCell.ColSpan = 2; commandRow.Cells.Add(commandCell); // create a spacer var spaceLit = new Literal(); spaceLit.Text = @" "; // create cancel link var cancelLink = new LinkButton() { //ID="EditLinkButton", Text = "Cancel", CausesValidation = false, CommandName = "Cancel", }; switch (templateMode) { case DataBoundControlMode.Edit: // ceate update link var updateLink = new LinkButton() { //ID="UpdateLinkButton", Text = "Update", CausesValidation = true, CommandName = "Update", }; commandCell.Controls.Add(updateLink); commandCell.Controls.Add(spaceLit); commandCell.Controls.Add(cancelLink); break; case DataBoundControlMode.Insert: // create insert link var insertLink = new LinkButton() { //ID="InsertLinkButton", Text = "Insert", CausesValidation = true, CommandName = "Insert", }; commandCell.Controls.Add(insertLink); commandCell.Controls.Add(spaceLit); commandCell.Controls.Add(cancelLink); break; case DataBoundControlMode.ReadOnly: // create edit link var editLink = new LinkButton() { //ID="EditLinkButton", Text = "Edit", CausesValidation = false, CommandName = "Edit", }; // create delete link var deleteLink = new LinkButton() { //ID="DeleteLinkButton", Text = "Delete", CommandName = "Delete", CausesValidation = false, OnClientClick = "return confirm(\"Are you sure you want to delete this item?\");", }; // create new link var newLink = new LinkButton() { //ID="insertLinkButton", Text = "New", CausesValidation = false, CommandName = "New", }; commandCell.Controls.Add(editLink); commandCell.Controls.Add(spaceLit); commandCell.Controls.Add(deleteLink); commandCell.Controls.Add(spaceLit); commandCell.Controls.Add(newLink); break; default: break; } } // if there are no children columns don't // bother to set the accessor to anything } private void GetEmptyDataTemplate(IParserAccessor accessor, IEnumerable<ListViewColumn> columnDetails) { // create a spacer var literal = new Literal(); literal.Text = @"There are currently no items in this table."; // add row to accessor accessor.AddParsedSubObject(literal); } private void GetPagerTemplate(IParserAccessor accessor, IEnumerable<ListViewColumn> columnDetails) { throw new NotImplementedException(); } private void GetFooterTemplate(IParserAccessor accessor, IEnumerable<ListViewColumn> columnDetails) { throw new NotImplementedException(); } private void GetHeaderTemplate(IParserAccessor accessor, IEnumerable<ListViewColumn> columnDetails) { throw new NotImplementedException(); } private class ListViewColumn { /// <summary> /// Column to display /// </summary> public MetaColumn Column { get; set; } /// <summary> /// MetaData if any from the original column /// </summary> public SubGridViewsAttribute SubGridMetaData { get; set; } /// <summary> /// Holds the sort order value /// </summary> public int Order { get; set; } } }
Listing 5 - FromViewPageRowGenerator
The main difference between this implementation of ITemplate and the implementation in Part 3 is that the column title is in the same row as the DynamicControl so it produces a template similar to the output shown in Listing 6. The only difference between the different template types that are implemented are the links shown (Edit, Delete & New for ReadOnly), (Update & Cancel for Edit) and (Insert & Cancel for Insert) and the mode of the DynamicControls.
<table class="detailstable"> <tr> <th> OrderDate </th> <td> <asp:DynamicControl ID="DynamicControl1" DataField="OrderDate" Mode="ReadOnly" runat="server" /> </td> </tr> <tr> <th> RequiredDate </th> <td> <asp:DynamicControl ID="DynamicControl2" DataField="RequiredDate" Mode="ReadOnly" runat="server" /> </td> </tr>
...// shortened for brevity
<tr> <td colspan="2"> <asp:LinkButton ID="EditLinkButton" CausesValidation="false" CommandName="Edit" runat="server"> Edit </asp:LinkButton> <asp:LinkButton ID="DeleteLinkButton" CausesValidation="false" CommandName="Delete" OnClientClick='return confirm("Are you sure you want to delete this item?");' runat="server"> Delete </asp:LinkButton> <asp:LinkButton ID="InsertLinkButton" CausesValidation="false" CommandName="New" runat="server"> New </asp:LinkButton> </td> </tr> </table>
Listing 6 – Fragment of the output from FormViewTemplateType
Project Download
Again V3 contains all from the previous parts 1 - 3
14 comments:
This looks like something I can use right now but I can't seem to download the zip file of the project. I think you have spaces in the name or some special character that when it downloads, it is corrupt. Zip says "invalid." All 4 zips.
Hi WoundedEgo,
Are you using IE8 because I just found out it not working with IE8 for some reason it works with safari and firefox, not tried it with IE7 yet.
Steve :D
yep just tested it in IE7 and it works fine. I'll have a look and see if there is some setting in IE8 stopping it form working :(
If you e-mail me I'll direct I'll send you the project.
Steve :D
The problem seems to have been with FlashGet, my download manager. I disabled it and IE8 handled it no problem. Thanks.
So is the 4th download a superset of the first 3? If I download the 4th, I don't need the first 3?
Also, which AdventureWorks tables are you using? The older versions?
Hi Woundedego, It's Northwind I downloaded from MS website. I'm using SQL Server 2008 but had to convert from 2000 :D
Steve
I'm trying to implement this using Entity Framework. When I load the FormView version, ForeignKey.ascx.cs errors at:
if (String.IsNullOrEmpty(NavigateUrl)) {
return ForeignKeyPath;
ForekgnKeyPath throws an exception that the foreign key id property doesn't exist in my entity. It's as if the related entity is not being loaded. Have you tried this with Entity Framework and is there any work around. The error I get is below and occurs for all related entities application wide, it's not just isolated to this one entity and relationship.
"System.Web.HttpException: DataBinding: 'DB.Biography' does not contain a property with the name 'BiographyType.ID'."
The entities do not error using a DetailsView.
Nevermind, I fixed that error with EF. I forgot to change the LinqDataSource to an EntityDataSource in FormViewPage.aspx. I think that was causing the related entities due to the lazy loading in EF vs. L2S. Sorry :)
Hi Steve,Here One problem. FileUpload Control properly working in ie7, But that application not working in ie8.Please tell me the solution as soon as possible.
FileUpload has a number of issues and I will fix them with a new article soon and make the old article point to the new.
Steve :D
great job
Almost saved my week
Thanks
When using in Page_Load for ListDetails
FromViewPageRowGenerator creates template without CSS in item view mode and with CSS but without editable values in edit mode
using that code:
FormView1.ItemTemplate = new FromViewPageRowGenerator(table, FormViewTemplateType.ItemTemplate);
I use Studio 2010 RC and their templates.
What is the reasons of this and how to correct?
When using in Page_Load for ListDetails
FromViewPageRowGenerator creates template without CSS in view mode and with CSS but without editable values in edit mode
code:
FormView1.ItemTemplate = new FromViewPageRowGenerator(table, FormViewTemplateType.ItemTemplate);
I use Studio 2010 RC and their templates.
What is the reasons of this and how to correct?
Hi Vgrigor4 look at this article here this article cover the method you could use in DD4 and VS2010 RC.
Steve
Post a Comment