- David Ebbo's Blog
- Using Dynamic Data with multiple databases
- Handling database exceptions in Dynamic Data
- Fun with T4 templates and Dynamic Data
- Using User Controls as Page Templates in Dynamic Data
- Creating a ControlBuilder for the page itself
- ProcessGeneratedCode: A hidden gem for Control Builder writers
- A ‘Many To Many’ field template for Dynamic Data
- Scott Hunter’s Blog
- Rick Anderson
ASP.Net, Dynamic Data and c# stuff: Focused on what new and cool, code tutorials and useful tricks and tips on getting the most out of ASP.Net. All in C#
Saturday, 13 December 2008
Great Links for Dynamic Data
Sunday, 7 December 2008
Dynamic Data – Registering Multiple Models
There are two way to get multiple model into your site:
- Register Multiple DataContexts with the default Model.
- Register each DataContext with it’s own Model.
My Models:
Register Multiple DataContexts with the default Model.
This is the simplest approach you have multiple EDMX or DBML files, all you need to do is lines like so in the Global.ascx file:
model.RegisterContext(typeof(Table1DataContext), new ContextConfiguration() { ScaffoldAllTables = true }); model.RegisterContext(typeof(Table2DataContext), new ContextConfiguration() { ScaffoldAllTables = true });
Listing 1 – Adding a Context to the Model
Figure 1 – Both Contexts in one model.
Register each DataContext with it’s own Model.
This is slightly more work, you are basically duplicating each line of code for one model to two.
public static void RegisterRoutes(RouteCollection routes) { // Model1 ====================================================== MetaModel model = new MetaModel(); model.RegisterContext(typeof(Table1DataContext), new ContextConfiguration() { ScaffoldAllTables = true }); routes.Add(new DynamicDataRoute("{table}/{action}.aspx") { Constraints = new RouteValueDictionary(new { action = "List|Details|Edit|Insert" }), Model = model }); // Model2 ====================================================== MetaModel model1 = new MetaModel(); model1.RegisterContext(typeof(Table2DataContext), new ContextConfiguration() { ScaffoldAllTables = true }); routes.Add(new DynamicDataRoute("Model1/{table}/{action}.aspx") { Constraints = new RouteValueDictionary(new { action = "List|Details|Edit|Insert" }), Model = model1 }); }
Listing 2 – Creating multiple models
Figure 2 - Model route
Figure 3 - Model1 route
As you can see from Figures 1 & 2 just by adding a differentiating route you can guarantee that there will be no table name conflicts between you models.
That get your Models registered now how to access them:
<h2>My first set of tables</h2> <br /><br /> <asp:GridView ID="Menu1" runat="server" AutoGenerateColumns="false" CssClass="gridview" AlternatingRowStyle-CssClass="even"> <Columns> <asp:TemplateField HeaderText="Table Name" SortExpression="TableName"> <ItemTemplate> <asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl='<%#Eval("ListActionPath") %>'><%#Eval("DisplayName") %></asp:HyperLink> </ItemTemplate> </asp:TemplateField> </Columns> </asp:GridView> <br /><br /> <h2>My Second set of tables</h2> <br /><br /> <asp:GridView ID="Menu2" runat="server" AutoGenerateColumns="false" CssClass="gridview" AlternatingRowStyle-CssClass="even"> <Columns> <asp:TemplateField HeaderText="Table Name" SortExpression="TableName"> <ItemTemplate> <asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl='<%#Eval("ListActionPath") %>'><%#Eval("DisplayName") %></asp:HyperLink> </ItemTemplate> </asp:TemplateField> </Columns> </asp:GridView>
Listing 3 – Add a second Menu to the Default.aspx
protected void Page_Load(object sender, EventArgs e) { // Model 1 System.Collections.IList visibleTables =
MetaModel.Default.VisibleTables;
if (visibleTables.Count == 0) { throw new InvalidOperationException("There are no accessible tables."); } Menu1.DataSource = visibleTables; Menu1.DataBind(); // Model 2 System.Collections.IList visibleTables2 =
MetaModel.GetModel(typeof(Table2DataContext)).VisibleTables;
if (visibleTables.Count == 0) { throw new InvalidOperationException("There are no accessible tables."); } Menu2.DataSource = visibleTables2; Menu2.DataBind(); }
Listing 4 – Default.aspx.cs code behind
Model 1 is normal but Model 2 you are required to get the model using GetModel(typeof(DataContextName)) so what you should now see when you run the site is:
Figure 2 – Both Models shown
I know this is simple stuff and I know I have answered questions be for butI found I hadn’t an article on my blog
And here’s the download
Tuesday, 2 December 2008
Dynamic Data - Updated URL FieldTemplate
Sorry I’ve been quiet for such a long time but towards the end of October I was helping out with a local charity and in early November I started some Freelance Web Development in DD, so from now on the posts will still come but will be a bit more infrequent (except when there is some new stuff to get our teeth into)
So improving the Url FieldTemplate, here’s what you see if you use the URL FieldTemplate as is from DD Futures:
Figure 1 – Standard Url FieldTemplate
And here’s what I want to see
Figure 2 – Improved Url FieldTemplate
Here’s the code:
<%@ Control Language="C#" CodeFile="Url.ascx.cs" Inherits="Url" %> <asp:HyperLink ID="HyperLinkUrl" runat="server" Target="_blank" />
public partial class Url : System.Web.DynamicData.FieldTemplateUserControl { protected override void OnDataBinding(EventArgs e) { string url = FieldValueString; if (!(url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
url.StartsWith("https://", StringComparison.OrdinalIgnoreCase))) { url = "http://" + url; } HyperLinkUrl.NavigateUrl = url; // set display text of the url HyperLinkUrl.Text = String.IsNullOrEmpty(Column.GetUrl())?
FieldValueString : Column.GetUrl(); } public override Control DataControl { get { return HyperLinkUrl; } } }
public class UrlAttribute : Attribute { public String Text { get; private set; } public UrlAttribute(string text) { Text = text; } public static UrlAttribute Default = new UrlAttribute(String.Empty); } public static partial class HelperExtansionMethods { public static String GetUrl(this MetaColumn column) { return column.Attributes.OfType<UrlAttribute>().DefaultIfEmpty(UrlAttribute.Default).First().Text; } }
Listing 3 – UrlAttribute and Helper method.
[Url("Insert New Products")] public Object InsertProducts
Listing 4 – Sample use of attributes
Now if there’s no UrlAttribute then you get the standard format but when there is a UrlAttribute present then you get the display text of the HyperLink as that of the UrlAttribute.
I though in a future article I could assign the display text of the Url FieldTemplate to another field thus making it a bit more dynamic, but that’s for another time.
Wednesday, 26 November 2008
DynamicData - Automatic Column Update For Entity Framework
Update to DynamicData - Automatic Column Update
See also the MSDN referance here:
How to: Execute Business Logic During Property Changes (Entity Framework)
How to: Execute Business Logic When Saving Changes (Entity Framework)
How to: Change Relationships Between Objects (Entity Framework)
namespace NorthwindModel { public partial class NorthwindEntities { partial void OnContextCreated() { this.SavingChanges += new EventHandler(OnSavingChanges); } private static void OnSavingChanges(object sender, EventArgs e) { // This will populate the PasswordHash and PasswordSalt fields var stateManager = ((NorthwindEntities)sender).ObjectStateManager; var insertedEntities = stateManager.GetObjectStateEntries(EntityState.Added); foreach (ObjectStateEntry stateEntryEntity in insertedEntities) { if (stateEntryEntity.Entity is Customers) { Customers cust = (Customers)stateEntryEntity.Entity; cust.CreatedBy = HttpContext.Current.User.Identity.Name; cust.CreatedOn = DateTime.Now; cust.UpdatedBy = HttpContext.Current.User.Identity.Name; cust.UpdatedOn = DateTime.Now; } } var modifiedEntities = stateManager.GetObjectStateEntries(EntityState.Modified); foreach (ObjectStateEntry stateEntryEntity in modifiedEntities) { if (stateEntryEntity.Entity is Customers) { Customers cust = (Customers)stateEntryEntity.Entity; cust.UpdatedBy = HttpContext.Current.User.Identity.Name; cust.UpdatedOn = DateTime.Now; } } } } }
In the above code the CreatedBy, CreatedOn, UpdateBy and UpdatedOn properties are automatically updated every time a record is modified or UpdateBy and UpdatedOn are modified when a record is updated.
Originally posted on ASP.Net Forum by Rick Anderson here Re: Setting a scaffolded column value
Sunday, 23 November 2008
Useful link from Rick Anderson
Have a look at this cool article by Rick here: Improving the FK field display: Showing two fields in Foreign Key columns with EF
Friday, 24 October 2008
Dynamic Data - Hiding Tables on the Default.aspx page
This is just a write up of an answer to a question on the Dynamic Data forum, so here's the solution.
First of all we need an attribute class:
[AttributeUsage(AttributeTargets.Class)] public class HideTableInDefaultAttribute : Attribute { public Boolean Hide { get; private set; } public HideTableInDefaultAttribute(Boolean hide) { Hide = hide; } // this will allow us to have a default set to false public static HideTableInDefaultAttribute Default = new HideTableInDefaultAttribute(false); }
Listing 1 - HideTableIndefaultAttribute
And now the code in the Default.aspx:
protected void Page_Load(object sender, EventArgs e) { System.Collections.IList visibleTables = Global.DefaultModel.VisibleTables; if (visibleTables.Count == 0) throw new InvalidOperationException( @"There are no accessible tables. Make sure that at least one data model is registered in Global.asax and scaffolding is enabled or implement custom pages."); //Menu1.DataSource = visibleTables; //Menu1.DataBind(); // Hiding tables var tablesMain = (from t in Global.DefaultModel.VisibleTables where !t.Attributes.OfType<HideTableInDefaultAttribute>(). DefaultIfEmpty(HideTableInDefaultAttribute.Default).First().Hide orderby t.DisplayName select t).ToList(); Menu1.DataSource = tablesMain; Menu1.DataBind(); }
Listing 2 - Default.aspx Page_Load event handler
Note in the where clause the DefaultIfEmpty which allows you to specify the default value to return if no value found. This means that you will always get a value and the default value will have the Hide property set to false because of the Default keyword in the Attribute definition.
Now some sample metadata:
// note the differance in the result of using // ScaffoldTable(false) over HideTableInDefault(true) [ScaffoldTable(false)] public partial class Category { } [HideTableInDefault(true)] public partial class EmployeeTerritory { } [HideTableInDefault(true)] public partial class Product { }
Figure 1 - Limited Tables list from Northwind
Figure 2 - See how the Products column has a foreign key link
Figure 3 - Note that the Category column has no link
If you look at each of the above Figures you will see the differences between a ScaffoldTable(false) and a HideTableInDefault(true). It's also worth noting that if you omit a table from the Model you either get a ForeignKey column of no column if it was a children column, so this makes using the above two methods the best way of hiding tables in the Default.aspx page.
Download Project
Monday, 20 October 2008
Dynamic Data - Hiding Columns in selected PageTemplates
This is a return to IAutoFieldGenrators, what we are trying to do is specify what page template to hide columns on.
First the Attribute class:
[AttributeUsage(AttributeTargets.Property)] public class HideColumnInAttribute : Attribute { public PageTemplate[] PageTemplates { get; private set; } public HideColumnInAttribute() { PageTemplates = new PageTemplate[0]; } public HideColumnInAttribute(params PageTemplate[] lookupTable) { PageTemplates = lookupTable; } public static HideColumnInAttribute Default = new HideColumnInAttribute(); } public enum PageTemplate { Details, Edit, Insert, List, ListDetails, // add any custom page templates here }
Listing 1 - HideColumnIn attribute
here you pass an array of pages that the column should be hidden in.
[MetadataType(typeof(OrderMetadata))] public partial class Order { public class OrderMetadata { [DisplayName("Order Date")] [HideColumnIn ( PageTemplate.Details, PageTemplate.Edit, PageTemplate.Insert )] public Object OrderDate { get; set; } } }
Listing 2 - sample metadata for Northwind's Orders table
In Listing 2 we are saying that the OrderDate column should NOT be shown on the Details, Edit and Insert pages.
Next we need an IAutoFieldGenerator class for this feature.
public class HideColumnFieldsManager : IAutoFieldGenerator { protected MetaTable _table; protected PageTemplate _currentPage; public HideColumnFieldsManager(MetaTable table, PageTemplate currentPage) { _table = table; _currentPage = currentPage; } public ICollection GenerateFields(Control control) { var oFields = new List<DynamicField>(); foreach (var column in _table.Columns) { // carry on the loop at the next column // if scaffold table is set to false or DenyRead if (!column.Scaffold || column.IsLongString || column.IsHidden(_currentPage)) continue; var f = new DynamicField(); f.DataField = column.Name; oFields.Add(f); } return oFields; } } public static class ExtensionMethods { public static Boolean IsHidden(this MetaColumn column, PageTemplate currentPage) { var hideIn = column.Attributes.OfType<HideColumnInAttribute>().DefaultIfEmpty(new HideColumnInAttribute()).First() as HideColumnInAttribute; return hideIn.PageTemplates.Contains(currentPage); } }
Listing 3 - the IAutoFieldGenerator class
This IAutoFieldGenerator class and Extension methods class in Listing 3 simply test to see if the column is NOT to be shown on the current page passed in the constructor.
A bit more of an explanation is required for what is going on in the Extension method IsHidden. I tried using the DefaultIfEmpty with the Default method but could not get it to work so I added the if null statement and set the hideIn to the defalut value if null.
protected void Page_Init(object sender, EventArgs e) { DynamicDataManager1.RegisterControl(DetailsView1); table = DetailsDataSource.GetTable(); DetailsView1.RowsGenerator = new HideColumnFieldsManager(table, PageTemplate.Details); }
Listing 4 - sample implementation on the Details page
protected void Page_Init(object sender, EventArgs e) { DynamicDataManager1.RegisterControl(GridView1, true /*setSelectionFromUrl*/); DynamicDataManager1.RegisterControl(DetailsView1); MetaTable table = GridDataSource.GetTable(); GridView1.ColumnsGenerator = new HideColumnFieldsManager(table, PageTemplate.ListDetails); DetailsView1.RowsGenerator = new HideColumnFieldsManager(table, PageTemplate.ListDetails); }
Listing 5 - sample implementation in the ListDetails page
Now each page needs to have a RowsGenerator or ColumnGenerator added as in Listings 4 & 5.
Download Project
Hope this helps
Steve
Sunday, 12 October 2008
Dynamic Data – Custom Metadata Providers *** UPDATED 20081110 ***
This is really just an addition to Matt Berseth's article Dynamic Data And Custom Metadata Providers from August 24, 2008, all I wanted to do was add the same features to the Table/Class not just the Columns/Properties. So you can see the full explanation over at Matt Berseth's blog. So here are the listings:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Web; public class DefaultAttributesTypeDescriptionProvider : System.ComponentModel.TypeDescriptionProvider { private Type Type { get; set; } public DefaultAttributesTypeDescriptionProvider(Type type) : this(type, TypeDescriptor.GetProvider(type)) { this.Type = type; } public DefaultAttributesTypeDescriptionProvider(Type type, TypeDescriptionProvider parentProvider) : base(parentProvider) { this.Type = type; } public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) { return new DefaultAttributesTypeDescriptor(base.GetTypeDescriptor(objectType, instance), this.Type); } }
Listing 1 – TypeDescriptorProvider
No change in Listing 1 from what Matt has already done just some renaming to make it a bit clearer to me what's going on.
using System; using System.Linq; using System.Text; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Reflection; public class DefaultAttributesTypeDescriptor : CustomTypeDescriptor { private Type Type { get; set; } public DefaultAttributesTypeDescriptor(ICustomTypeDescriptor parent, Type type) : base(parent) { this.Type = type; } /// <summary> /// Returns the collection of attributes for a given table /// </summary> /// <returns>AttributeCollection</returns> public override AttributeCollection GetAttributes() { AttributeCollection baseAttributes = base.GetAttributes(); if (baseAttributes.OfType<DisplayNameAttribute>().FirstOrDefault() == null) { List<Attribute> extraAttributes = new List<Attribute>(); // generate the display name String friendlyDisplayName = base.GetClassName().ToTitleFromPascal(); // add it to the list extraAttributes.Add(new DisplayNameAttribute(friendlyDisplayName)); // only create a new collection if it is necessary return AttributeCollection.FromExisting(baseAttributes, extraAttributes.ToArray()); } else { return baseAttributes; } } /// <summary> /// Returns a collection of properties (columns) for the type, /// each with attributes for that table /// </summary> /// <returns>PropertyDescriptorCollection</returns> public override PropertyDescriptorCollection GetProperties() { List<PropertyDescriptor> propertyDescriptors = new List<PropertyDescriptor>(); foreach (PropertyDescriptor propDescriptor in base.GetProperties()) { List<Attribute> newAttributes = new List<Attribute>(); // Display Name Rules ... // If the property doesn't already have a DisplayNameAttribute defined // go ahead and auto-generate one based on the property name if (!propDescriptor.HasAttribute<DisplayNameAttribute>()) { // generate the display name String friendlyDisplayName = propDescriptor.Name.ToTitleFromPascal(); // add it to the list newAttributes.Add(new DisplayNameAttribute(friendlyDisplayName)); } // Display Format Rules ... // If the property doesn't already have a DisplayFormatAttribute defined // go ahead and auto-generate one based on the property type if (!propDescriptor.HasAttribute<DisplayFormatAttribute>()) { // get the default format for the property type String displayFormat = propDescriptor.PropertyType.GetDisplayFormat(); // add it to the list newAttributes.Add(new DisplayFormatAttribute() { DataFormatString = displayFormat }); } propertyDescriptors.Add(new WrappedPropertyDescriptor(propDescriptor, newAttributes.ToArray())); } // return the descriptor collection return new PropertyDescriptorCollection(propertyDescriptors.ToArray(), true); } private class WrappedPropertyDescriptor : PropertyDescriptor { private PropertyDescriptor _wrappedPropertyDescriptor; public WrappedPropertyDescriptor(PropertyDescriptor wrappedPropertyDescriptor, Attribute[] attributes) : base(wrappedPropertyDescriptor, attributes) { _wrappedPropertyDescriptor = wrappedPropertyDescriptor; } public override bool CanResetValue(object component) { return _wrappedPropertyDescriptor.CanResetValue(component); } public override Type ComponentType { get { return _wrappedPropertyDescriptor.ComponentType; } } public override object GetValue(object component) { return _wrappedPropertyDescriptor.GetValue(component); } public override bool IsReadOnly { get { return _wrappedPropertyDescriptor.IsReadOnly; } } public override Type PropertyType { get { return _wrappedPropertyDescriptor.PropertyType; } } public override void ResetValue(object component) { _wrappedPropertyDescriptor.ResetValue(component); } public override void SetValue(object component, object value) { _wrappedPropertyDescriptor.SetValue(component, value); } public override bool ShouldSerializeValue(object component) { return _wrappedPropertyDescriptor.ShouldSerializeValue(component); } } }
Listing 2 – CustomTypeDescriptor
Here all I’ve done is again some renaming and add a GetAttributes method which returns the attributes on the class/table. The section is marked in BOLD ITALIC for emphasis. What it does is check to see if the DisplayNameAttriburte is set and if not then auto generates it.
using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Web; using System.Web.DynamicData; using Microsoft.Web.DynamicData; public static class HelperExtansionMethods { public static ColumnOrderAttribute ColumnOrdering(this MetaColumn column) { return column.Attributes.OfType<ColumnOrderAttribute>().DefaultIfEmpty(ColumnOrderAttribute.Default).First(); } public static Boolean IsHidden(this MetaColumn column, PageTemplate currentPage) { var hideIn = column.Attributes.OfType<HideColumnInAttribute>().DefaultIfEmpty(new HideColumnInAttribute()).First() as HideColumnInAttribute; return hideIn.PageTemplates.Contains(currentPage); } /// <summary> /// Converts a Pascal type Name to Title /// i.e. MyEmployees becomes My Employees /// OrderID becomes Order ID etc /// </summary> /// <param name="s">String to convert</param> /// <returns>Title String</returns> public static String ToTitleFromPascal(this String s) { // remove name space String s0 = Regex.Replace(s, "(.*\\.)(.*)", "$2");Listing 3 – Helper extension methods
// add space before Capital letter String s1 = Regex.Replace(s0, "[A-Z]", " $&"); // replace '_' with space String s2 = Regex.Replace(s1, "[_]", " "); // replace double space with single space String s3 = Regex.Replace(s2, " ", " "); // remove and double capitals with inserted space String s4 = Regex.Replace(s3, "(?<before>[A-Z])\\s(?<after>[A-Z])", "${before}${after}"); // remove and double capitals with inserted space String sf = Regex.Replace(s4, "^\\s", ""); // force first character to upper case return sf.ToTitleCase(); } public static String ToTitleCase(this String text) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < text.Length; i++) { if (i > 0) { if (text.Substring(i - 1, 1) == " " || text.Substring(i - 1, 1) == "\t" || text.Substring(i - 1, 1) == "/") sb.Append(text.Substring(i, 1).ToString().ToUpper()); else sb.Append(text.Substring(i, 1).ToString().ToLower()); } else sb.Append(text.Substring(i, 1).ToString().ToUpper()); } return sb.ToString(); } public static Boolean HasAttribute<T>(this PropertyDescriptor descriptor) where T : Attribute { Boolean value = false; for (int i = 0; i < descriptor.Attributes.Count && !value; i++) { value = (descriptor.Attributes[i] is T); } return value; } public static String GetDisplayFormat(this Type type) { string defaultFormat = "{0}"; if (type == typeof(DateTime) || type == typeof(Nullable<DateTime>)) { defaultFormat = "{0:d}"; } else if (type == typeof(decimal) || type == typeof(Nullable<decimal>)) { defaultFormat = "{0:c}"; } return defaultFormat; } }
EntitiesModel.MyEntity
or
tbl_name_of_table
I’ve moved my helper extension methods out of the class and rewritten the ToHumanFromPascal as ToTitleFromPascal and used Regex to do my converting.
I hope Matt doesn't mind.
Friday, 10 October 2008
ASP.NET Dynamic Data Links
Some great posts by Matt Berseth
-
Simple 5 Table Northwind Example: Matt Berseth kicks off his ASP.NET Dynamic Data tutorial series with a nice post that shows how to build a simple 5 table application using ASP.NET Dynamic Data with .NET 3.5 SP1.
-
Dynamic Data And Custom Metadata Providers: Matt continues the series and covers the MetadataType attribute, and how you can use it to annotate your entities with additional metadata.
-
Dynamic Menu for your Dynamic Data: Matt continues and covers how to add a data-driven menu to the site.
-
Customizing the Delete Confirmation Dialog: Matt continues and demonstrates how to build a nice UI experience when deleting records in a dynamic data application.
-
Experimenting with YUI's DataTable and DataSource Controls: Matt experiments with how to use client-side AJAX components together with dynamic data.
Wednesday, 8 October 2008
Dynamic Data Custom Pages: Dynamic/Templated FromView
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