Dynamic Data Custom Pages: Dynamic/Templated FromView (original) (raw)

These articles are now under the title of Custom PageTemplates:

  1. Custom PageTemplates Part 1 - Custom PageTemplates with Ajax Control Toolkit Tabs
  2. Custom PageTemplates Part 2 - A variation of Part 1 with the Details and SubGrid in Tabs
  3. Custom PageTemplates Part 3 - Dynamic/Templated Grid with Insert (Using ListView)
  4. 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.

FormViewPage in action

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>

Listing 3 – FormViewPage.aspx

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.

Note: I’m not sure this is the best or most elegant way of doing this, but this will do for now and we will have a look at this again at a later date.

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//.

///

/// Template type for the FromViewPageRowGenerator /// public enum FormViewTemplateType { ItemTemplate, EditItemTemplate, InsertItemTemplate, EmptyDataTemplate, HeaderTemplate, FooterTemplate, PagerTemplate, }

///

/// Renders templates for ListView /// 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 = @"&nbsp;";

        // 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.

...// 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>
OrderDate
RequiredDate

Listing 6 – Fragment of the output from FormViewTemplateType

Project Download

Again V3 contains all from the previous parts 1 - 3