Showing posts with label Linq. Show all posts
Showing posts with label Linq. Show all posts

Monday, 31 May 2010

Part 2 – A Cascading Hierarchical Field Template & Filter for Dynamic Data

In this second part we are going to complete the field templates by adding the CascadeHierarchical_Edit field template (see Figure 1) and in the next adding a CascadeHierarchical filter.

Starting with a copy of the ForeignKey_Edit field template as our base we will end up with a field template that looks like Figure 1 allowing you to have a hierarchy ‘n’ levels deep.

Cascade Hierarchical Field Template

Figure 1 – Cascade Hierarchical Field Template in action.

Note: It turns out the the Product field on the Order is only available in insert not edit so I have moved away from Northwind and have created a very construed Vehicles sample DB, I have included as a script in the download at the end of this article.

The first we must make the copy of the ForeignKey_Edit field template, and then make sure we rename it to CascadeHierarchical_Edit, also we must change the class name all it’s files; this sample being a Web Application project there are three files:

  • CascadeHierarchical_Edit.ascx
  • CascadeHierarchical_Edit.ascx.cs
  • CascadeHierarchical_Edit.ascx.designer.cs

Each must be modified as follows (I generally follow the naming convention use in the other standard field templates i.e. Text_Edit’s class name is Text_EditField), so we will set our field templates class name to CascadeHierarchical_EditField.

Note: if you change CascadeHierarchical_Edit.ascx first then you will not need to change the CascadeHierarchical_Edit.ascx.designer.cs will happen automatically. (I don’t know why the CascadeHierarchical_Edit.ascx.cs is not also changed automatically when the ascx file’s Inherits property is changed).

In the .cs file change the class name to CascadeHierarchical_EditField and in the CascadeHierarchical_Edit.ascx change the Inherits property to  be namespace.CascadeHierarchical_EditField, namespace is usually the name of the project, in any case if will already be filled in in the template unless you are copying from one project to another.

Now we have a basis to begin, lets first start with the Page_Init and some explanation.

<%@ Control 
 Language="C#" 
 CodeBehind="CascadeHierarchical_Edit.ascx.cs" 
 Inherits="CascadeHierarchicalFieldTemplate.CascadeHierarchical_EditField" %>
<asp:RequiredFieldValidator 
 runat="server" 
 ID="RequiredFieldValidator1" 
 CssClass="DDControl DDValidator" 
 Display="Static" 
 Enabled="false"/>
<asp:DynamicValidator 
 runat="server" 
 ID="DynamicValidator1" 
 CssClass="DDControl DDValidator" 
 Display="Static"/>

Listing 1 – the aspx page.

Here in Listing 1 you can see all we have are the required and dynamic validators, this is all we need at the dropdown lists will all be created dynamically.

#region member variables
// hold the current data context to access the model
private object context;
// hold the list of filters
private SortedList<int, HierachicalListControl> filters = new SortedList<int, HierachicalListControl>();
// hold the attribute
private CascadeHierarchicalAttribute cascadeHierarchicalAttribute;
#endregion
protected void Page_Init(object sender, EventArgs e)
{
 // check we have a cascade hierarchical attribute if not throw error
 cascadeHierarchicalAttribute = Column.GetAttribute<CascadeHierarchicalAttribute>();
 if (cascadeHierarchicalAttribute == null)
 throw new InvalidOperationException("Was expecting a CascadeFilterAttribute.");
 // check we have correct column type if not throw error
 if (!(Column is MetaForeignKeyColumn))
 throw new InvalidOperationException(String.Format("Column {0} must be a foreign key column navigation property", Column.Name));
 // get current context
 context = Table.CreateContext();
 // get hierarchical cascade columns
 var parentColumns = new SortedList<int, String>();
 for (int i = 0; i < cascadeHierarchicalAttribute.Parameters.Length; i++)
 parentColumns.Add(i, cascadeHierarchicalAttribute.Parameters[i]);
 // add extra column to represent this column itself
 parentColumns.Add(cascadeHierarchicalAttribute.Parameters.Length, "");
 //get current column into a local variable
 MetaForeignKeyColumn column = ForeignKeyColumn;
 // setup list of filter definitions
 for (int i = 0; i < parentColumns.Count; i++)
 {
 // get parent column name
 var parentColumnName = parentColumns[i];
 // create dropdown list
 var ddl = new DropDownList()
 {
 ID = String.Format("ListControl{0}", i),
 Enabled = false,
 AutoPostBack = true
 };
 // create filter
 var filter = new HierachicalListControl(ddl) { Column = column };
 // check for last parent filter
 if (!String.IsNullOrEmpty(parentColumnName))
 {
 // set parent column from parent table
 filter.ParentColumn = (MetaForeignKeyColumn)column.ParentTable.GetColumn(parentColumnName);
 // set current column to parent column
 column = filter.ParentColumn;
 }
 else
 {
 // this is the last parent and has
 // no parent itself so set to null
 filter.ParentColumn = null;
 column = null;
 }
 // add filter to list of filters
 filters.Add(i, filter);
 }
 // add dropdown list to page in correct order 2, 1, 0
 // last parent, parent<N>, child
 for (int i = parentColumns.Count - 1; i >= 0; i--)
 {
 // setup dropdown list
 filters[i].ListControl.Items.Clear();
 filters[i].ListControl.Items.Add(new ListItem("------", ""));
 // add parent list controls event handler
 if (i > 0)
 filters[i].ListControl.SelectedIndexChanged += ListControls_SelectedIndexChanged;
 // add control to place holder
 this.Controls.Add(filters[i].ListControl);
 }
 if (Mode == DataBoundControlMode.Insert)
 {
 // fill last parent filter
 var lastParentIndex = filters.Count - 1;
 var parentTable = filters[lastParentIndex].Column.ParentTable;
 var parentQuery = parentTable.GetQuery(context);
 // set next descendant list control
 PopulateListControl(lastParentIndex, parentQuery);
 }
}

Listing 2 -  Page_Init

The reason for using Page_Init (see Listing 2) is that we are going to create the dropdown lists dynamically, these need to be instantiated in the OnInit event to be fully involved in post back.

The first thing we need to do in the Page_Init is to make sure we have the correct Column Type (MetaForeignKeyColumn) and that the column has a CascadeHierarchicalAttribute assigned, if either of these in not present then we throw an error.

There are three for loops in the Page_Init, the first for loop we are building a list of parent columns to help with the next for loop which builds the filters list (by having two loops we make it easy to put the list controls onto the page in the most logical order, last parent to the left and child to the right).

Note: We also set each dropdown list’s SelectedIndexChanged event to post back to the same ListControls_SelectedIndexChanged handler.

This list of  filters is used throughout the rest of the field template to build each list control and made up of a dropdown list the current column and it’s parent column (see Listing 3).

/// <summary>
/// Class to contains information about cascading dropdown lists
/// </summary>
protected internal class HierachicalListControl
{
 /// <summary>
 /// Returns a <see cref="System.String"/> that represents this instance.
 /// </summary>
 /// <returns>
 /// A <see cref="System.String"/> that represents this instance.
 /// </returns>
 public override string ToString()
 {
 var parentColumn = ParentColumn != null ? ParentColumn.Name : "null";
 return String.Format("{0}.{1}", Column.Name, parentColumn);
 //return this.Column.Name;
 }
 /// <summary>
 /// Initializes a new instance of the <see cref="HierachicalListControl"/> class.
 /// </summary>
 /// <param name="column">This column.</param>
 /// <param name="parentColumn">This column's parent column.</param>
 public HierachicalListControl(ListControl listControl)
 {
 ListControl = listControl;
 }
 /// <summary>
 /// Gets or sets the filter column.
 /// </summary>
 /// <value>The column.</value>
 public MetaForeignKeyColumn Column { get; set; }
 /// <summary>
 /// Gets or sets the filter column's parent column.
 /// </summary>
 /// <value>The parent column.</value>
 public MetaForeignKeyColumn ParentColumn { get; set; }
 /// <summary>
 /// Gets or sets the list control.
 /// </summary>
 /// <value>The list control.</value>
 public ListControl ListControl { get; set; }
}

Listing 3 – HierachicalListControl used in list of filters .

Note: I have overridden the ToString method to make the filters list more readable in debug mode, in Column.ParentColumn format, this is not required for the sample to function correctly.

The final for loop is use to initialise each dropdown list with it’s default item, hook-up the SelectionIndexChanged event and then add it to the page.

The final segment of code in Page_Init is the if statement for when we are in insert mode as the OnDataBound event will not fire, so we populate the last filter the (highest in the hierarchy).

protected void Page_Load(object sender, EventArgs e)
{
 if (filters[0] != null &&
 filters[0].ListControl != null)
 {
 RequiredFieldValidator1.ControlToValidate = "ListControl0";
 DynamicValidator1.ControlToValidate = "ListControl0";
 SetUpValidator(RequiredFieldValidator1);
 SetUpValidator(DynamicValidator1);
 }
}

Listing 4 – Page_Load event handler

The Page_Load event just makes sure that we have some filter[0] and then sets up the validators. The next event handler is the OnDataBinding handler here we make sure we are in Edit mode and that the current value is not null and then we call the main method for setting up the filters SetupListControls.

protected override void OnDataBinding(EventArgs e)
{
 base.OnDataBinding(e);
 // Set initial value
 if (Mode == DataBoundControlMode.Edit && FieldValue != null)
 PopulateAllListControls(FieldValue);
}
/// <summary>
/// Sets the default values.
/// </summary>
/// <param name="fieldValue">The value.</param>
private void PopulateAllListControls(object fieldValue)
{
 var displayStrings = new SortedList<int, String>();
 #region Get list of propert values
 // get property values
 var propertyValues = new SortedList<int, Object>();
 propertyValues.Add(0, fieldValue);
 for (int i = 0; i < filters.Count - 1; i++)
 {
 var parentName = filters[i].ParentColumn.Name;
 object pv = propertyValues[i].GetPropertyValue(parentName);
 propertyValues.Add(i + 1, pv);
 }
 #endregion
 // stating at the first filter and work way up to the last filter
 for (int i = 0; i < filters.Count; i++)
 {
 var parentTable = filters[i].Column.ParentTable;
 var parentQuery = parentTable.GetQuery(context);
 IQueryable listItemsQuery;
 if (i == cascadeHierarchicalAttribute.Parameters.Length)
 {
 listItemsQuery = parentQuery.GetQueryOrdered(parentTable);
 }
 else
 {
 var pcol = filters[i + 1].Column;
 var selectedValue = filters[i].ParentColumn.GetForeignKeyString(propertyValues[i]);
 listItemsQuery = parentQuery.GetQueryFilteredFkColumn(pcol, selectedValue);
 }
 // set next descendant list control
 PopulateListControl(i, listItemsQuery);
 // set initial values
 var selectedValueString = filters[i].Column.Table.GetPrimaryKeyString(propertyValues[i]);
 ListItem item = filters[i].ListControl.Items.FindByValue(selectedValueString);
 if (item != null)
 filters[i].ListControl.SelectedValue = selectedValueString;
 }
}

Listing 5 – OnDataBinding and PopulateAllListControls

The first thing SetupListControls does is get a list of actual values for each filter in the cascade of filters using fieldValue as a starting point, working back from the passed in fieldValue, it calls the method GetPropertyValue which uses reflection to get the value of the next entity using the current value and the parent column’s name. These values are in turn used to fill each filter with appropriately filtered values. The last parent getting an unfiltered list of items as it has no parent itself.

/// <summary>
/// Gets the property value.
/// </summary>
/// <param name="sourceObject">The source object.</param>
/// <param name="propertyName">Name of the property.</param>
/// <returns>The named properties value.</returns>
public static Object GetPropertyValue(this Object sourceObject, 
 string propertyName)
{
 if (sourceObject != null)
 return sourceObject.GetType()
 .GetProperty(propertyName)
 .GetValue(sourceObject, null);
 else
 return null;
}

Listing 6 – GetPropertyValue

The two extension methods used to get the list of items are called GetQueryOrdered Listing 7 or GetQueryFilteredFkColumn Listing 8.

/// <summary>
/// Gets the query ordered.
/// </summary>
/// <param name="sourceQuery">The source query.</param>
/// <param name="table">The table.</param>
/// <returns></returns>
public static IQueryable GetQueryOrdered(this IQueryable sourceQuery, MetaTable table)
{
 // get display column attribute
 var displayColumnAttribute = table.GetAttribute<DisplayColumnAttribute>();
 // check to see if sort is assigned
 if (displayColumnAttribute == null || displayColumnAttribute.SortColumn == null)
 return sourceQuery;
 // {row.OrderBy(row => row.Name)}
 var orderByCall = GetOrderByCallExpression(
 sourceQuery, 
 table, 
 displayColumnAttribute.SortColumn, false);
 // create and return query
 return sourceQuery.Provider.CreateQuery(orderByCall);
}

Listing 7 – GetQueryOrdered

/// <summary>
/// Gets a list of entities from the source IQueryable 
/// filtered by the MetaForeignKeyColumn's selected value
/// </summary>
/// <param name="sourceQuery">The query to filter</param>
/// <param name="fkColumn">The column to filter the query on</param>
/// <param name="fkSelectedValue">The value to filter the query by</param>
/// <returns>
/// An IQueryable of the based on the source query 
/// filtered but the FK column and value passed in.
/// </returns>
public static IQueryable GetQueryFilteredFkColumn(this IQueryable sourceQuery, 
 MetaForeignKeyColumn fkColumn, String fkSelectedValue)
{
 // if no filter value return the query
 if (String.IsNullOrEmpty(fkSelectedValue))
 return sourceQuery;
 // order query
 sourceQuery = GetQueryOrdered(sourceQuery, fkColumn.Table);
 // {RequiredPlots}
 var parameterExpression = Expression.Parameter(sourceQuery.ElementType, fkColumn.Table.Name);
 // {(RequiredPlots.Builders.Id = 1)}
 var body = BuildWhereClause(fkColumn, parameterExpression, fkSelectedValue);
 // {RequiredPlots => (RequiredPlots.Builders.Id = 1)}
 var whereLambda = Expression.Lambda(body, parameterExpression);
 // {Developers.Where(RequiredPlots => (RequiredPlots.Builders.Id = 1))}
 var whereCall = Expression.Call(typeof(Queryable),
 "Where",
 new Type[] { sourceQuery.ElementType },
 sourceQuery.Expression,
 Expression.Quote(whereLambda));
 // create and return query
 return sourceQuery.Provider.CreateQuery(whereCall);
}

Listing 8 – GetQueryFilteredFkColumn

Listings 7 & 8 are form the IQueriableExtensionMethods.cs class file in the NotAClue.Web.DynamicData class library, and are a part of my dynamic Linq Expression extension methods that originally came from the ASP.NET July 2007 Futures Source Code sample on ASP.Net on CodePlex.com, I continue to expand and modify them as I need to; so I wont go into detail here as there better posts on Linq Expression than any I could write.

Note: There is a useful sample in the C# samples see Dynamic LINQ (Part 1: Using the LINQ Dynamic Query Library on Scott Guthrie's blog, “Dynamic Linq” allows you to easily build Linq queries on the fly.
/// <summary>
/// Handles the SelectedIndexChanged event of the parentListControls control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
/// <summary>
/// Setups the parent list control.
/// </summary>
/// <param name="table">The table.</param>
/// <param name="filterIndex">The parent id.</param>
/// <param name="items">The items.</param>
public void PopulateListControl(int filterIndex, IQueryable items)
{
 // clear the list controls list property
 filters[filterIndex].ListControl.Items.Clear();
 // enable list control
 filters[filterIndex].ListControl.Enabled = true;
 // add unselected value showing the column name
 // [Styles]
 filters[filterIndex].ListControl.Items.Add(
 new ListItem(String.Format("[{0}]", 
 filters[filterIndex].Column.DisplayName), ""));
 foreach (var row in items)
 {
 // populate each item with the display string and key value
 filters[filterIndex].ListControl.Items.Add(
 new ListItem(filters[filterIndex].Column.ParentTable.GetDisplayString(row),
 filters[filterIndex].Column.ParentTable.GetPrimaryKeyString(row)));
 }
}

Listing 9 – PopulateListControl

Once we have the query filtered and ordered we then set the list control’s items up by calling PopulateListControl (Listing 9), then finally we set the initial value in of the list control.

/// <summary>
/// Handles the SelectedIndexChanged event for each List control, 
/// and populates the next list control in the hierarchy.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">
/// The <see cref="System.EventArgs"/> instance containing the event data.
/// </param>
void ListControls_SelectedIndexChanged(object sender, EventArgs e)
{
 // get list control
 var listControl = (ListControl)sender;
 // get the sending list controls id as an int
 var id = ((Control)sender).ID;
 // use regular expression to find list control index
 var regEx = new Regex(@"\d+");
 var parentIndex = int.Parse(regEx.Match(id).Value);
 if (!String.IsNullOrEmpty(listControl.SelectedValue))
 {
 if (parentIndex > 0)
 {
 // set child index
 var childIndex = parentIndex - 1;
 // get parent table
 var parentTable = filters[childIndex].Column.ParentTable;
 // get query from table
 var query = parentTable.GetQuery(context);
 // get items for list control
 var itemQuery = query.GetQueryFilteredFkColumn(
 filters[parentIndex].Column,
 listControl.SelectedValue);
 // populate list control
 PopulateListControl(childIndex, itemQuery);
 // reset all descendant list controls
 ResetAllDescendantListControls(childIndex);
 }
 }
 else
 {
 // reset all descendant list controls
 ResetAllDescendantListControls(parentIndex);
 }
}

Listing 10 – parentListControls_SelectedIndexChanged

The next thing we have to deal with is SelectedIndexChanged event on each dynamically created filter, we do this in the ListControls_SelectedIndexChanged handler (Listing 10) we first get the posting list control and then extract the filter index from it’s name. Remember all the filters are named ListControl{N} where N is the index into the filters list for the filter.

Note: We use a regular expression “\d+” to match one or more digits in the list control’s ID

Then we check to see if the list control has a SelectedValue if not we reset it and all it’s children to the default of "----" and Enabled to false. If it has a SelectedValue then we get a query on the child column filtered by the current SelectedValue and populate the next list control in line.

We then call SetupParentListControl to populate the control and finally ResetAllDescendantListControls to set any descendant controls to their default value of "----" and disabled.

The last two methods as two of the standard field template methods just slightly change to avoid exceptions seen here in Listings 11.

protected override void ExtractValues(IOrderedDictionary dictionary)
{
 // If it's an empty string, change it to null
 string value;
 if (filters[0] != null && filters[0].ListControl != null)
 value = filters[0].ListControl.SelectedValue;
 else
 value = String.Empty;
 if (String.IsNullOrEmpty(value))
 value = null;
 ExtractForeignKey(dictionary, value);
}
public override Control DataControl
{
 get
 {
 if (filters[0] != null && filters[0].ListControl != null)
 return filters[0].ListControl;
 else
 return null;
 }
}

Listing 11 – ExtractValues method and DataControl property.

These two methods merely check to see if there are nay controls before processing.

Download

[フレーム]

The sample is a Visual Studio 2010 and .Net 4.0 sample but the code and field template should work with Visual Studio 2008 SP1 and .Net 3.5 SP1 DD Web Application.

Note: The script for creating the Vehicles database is in the zip file.

Thursday, 25 September 2008

Dynamic Data Custom PageTemplates with Ajax Control Toolkit Tabs UPDATED: 2008年09月27日

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

I’ve been working for a little while now to get a generic page with a DetailsView showing the parent record and a set of tabs showing all the child records similar to Figure 1.

[画像:A Detail/Edit page with all ChildrenColumn as sub grids in a tab control

Figure 1 – A Detail/Edit page with all ChildrenColumn as sub grids in a tab control

I wanted something that was easy to implement on your own custom page and reused my existing FieldTemplate ParentGrid, I tried several methods to achieve this and I’m going to show the two most successful methods.

Both methods use a FormView as the control to embed the tabs and grids:

  • Creating an external ItemTemplate and load it into the FormView at runtime
  • Have a dynamic ItemTemplate that implements ITemplate.

Making the Custom Page with a FormView

For this sample I’m going to make a custom generic Edit page, so first we need to make a copy of the Edit.aspx page and rename it to EditSubGridViews.aspx rename the class as well to EditSubGridViews in both the aspx file and the code behind.

<asp:UpdatePanel ID="UpdatePanel2" runat="server">
 <ContentTemplate>
 
 <h3>Sub GridViews</h3>
 
 <asp:FormView 
 ID="FormView1" 
 runat="server" 
 DataSourceID="DetailsDataSource">
 <ItemTemplate>
 </ItemTemplate>
 </asp:FormView>
 
 </ContentTemplate>
</asp:UpdatePanel>

Listing 1 – The added the FormView and UpdatePanel

Add Listing 1’s code after the end of the current UpdatePanel.

Note: The DataSourceID="DetailsDataSource" is set to the same data source as the DetailsView1

In the code behind add the following line to the Page_Load event handler.

DynamicDataManager1.RegisterControl(FormView1);

This just registers the FormView with the DynamicDataManager no real magic here.

// load item template
table = DetailsDataSource.GetTable();
String itemTemplate = table.Model.DynamicDataFolderVirtualPath + "Templates/" + table.Name + ".ascx";
if (File.Exists(Server.MapPath(itemTemplate)))
 FormView1.ItemTemplate = LoadTemplate(itemTemplate);
// note if no template is loaded then the FormView will show nothing

Listing 2 – code to load the ItemTemplate

This is the magic, you can create your own custom template as a UserControl to include just the ChildrenTables you want. Here we first of all get a reference to the table so that we reference the Model.DynamicDataFolderVirtualPath (which hold the virtual path to the Dynamic Data folder normally ~/DynamicData/) to build the path to our template. And if the template exists then load it into the FormView’s ItemTemplate. Obviously you could load any of the other FormView templates this way.

Modifying the Route for Edit

To use our new PageTemplate EditSubGridViews we could just have edited the current Edit page, but I thought it would be good to leave that as is and change the route.

// The following statement supports separate-page mode, where the List, Detail, Insert, and 
// Update tasks are performed by using separate pages. To enable this mode, uncomment the following 
// route definition, and comment out the route definitions in the combined-page mode section that follows.
routes.Add(new DynamicDataRoute("{table}/{action}.aspx") {
 Constraints = new RouteValueDictionary(new { action = "List|Details|Insert" }),
 Model = model
});
// add a route to EditSubGridViews
routes.Add(new DynamicDataRoute("{table}/EditSubGridViews.aspx")
{
 Action = PageAction.Edit,
 ViewName = "EditSubGridViews",
 Model = model
});

Listing 3 – Route to EditSubGridViews.aspx added to Global.asax 

In the default root in the Global.asax file remove Edit from action = "List|Details|Edit|Insert" and add the route to EditSubGridViews from Listing 3.

Create the FormView ItemTemplate

To create a template file for FormView all you need to do is create a new UserControl in the folder. The first thing to do is create the Templates folder under the DynamicData folder.

To create the folder from Visual Studio 2008 right click the DynamicData folder and click New Folder

[画像:Select New Folder from the context menu]

Figure 2 – Select New Folder from the context menu

Now right click the new folder and click Add New Item…

[画像:Add New Item]

Figure 3 – Add New Item

From the Add New Item dialogue box choose Web User Control and name it Employees.

Create the Web User Control

Figure 4 – Create the Web User Control

[画像:Templates folder under the DynamicData folder with the Employees template]

Figure 5 – Templates folder under the DynamicData folder with the Employees template

Add the following mark-up to the new template:

<ajaxToolkit:TabContainer ID="TabContainer1" runat="server">
 <ajaxToolkit:TabPanel ID="TabPanel1" HeaderText="My Employees" runat="server">
 <ContentTemplate>
 <asp:DynamicControl ID="DynamicControl1" DataField="Employees" UIHint="ChildrenGrid" Mode="Edit" runat="server">
 </asp:DynamicControl>
 </ContentTemplate>
 </ajaxToolkit:TabPanel>
 <ajaxToolkit:TabPanel ID="TabPanel3" HeaderText="My Orders" runat="server">
 <ContentTemplate>
 <asp:DynamicControl ID="DynamicControl3" DataField="Orders" UIHint="ChildrenGrid" Mode="Edit" runat="server">
 </asp:DynamicControl>
 </ContentTemplate>
 </ajaxToolkit:TabPanel>
</ajaxToolkit:TabContainer>

Listing 4 – ItemTemplate showing Employees and Orders ChildrenGrids

Note: See this post series Dynamic Data and Advanced Field Templates for the ChildrenGrid details.

The important setting here in the DynamicControls are UIHint and DataField, (ChildrenGrid is a FieldTemplate from a previous series of articles on FieldTemplates on my blog) in these parameters we are setting:

  • UIHint setting the FieldTemplate to be used.
  • DataField which field from the table to bind the FieldTemplate to.

When you run the sample you will see something like Figure 6.

Example output from EditSubGridViews.aspx

Figure 6 – Example output from EditSubGridViews.aspx

Creating a Dynamic ItemTemplate that Implements ITemplate

ITemplate documentation can be found on the MSDN website here Creating Web Server Control Templates Programmatically.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.DynamicData;
using System.Web.UI.WebControls;
using AjaxControlToolkit;
/// <summary>
/// Creates an item template that renders any children columns in the passed in table as GridViews
/// </summary>
public class SubGridViewItemTemplate : ITemplate
{
 private MetaTable _table;
 private Page _page;
 public SubGridViewItemTemplate(MetaTable table, Page page)
 {
 _table = table;
 _page = page;
 }
 public void InstantiateIn(Control container)
 {
 IParserAccessor acessor = container;
 // get all the children columns
 var subGridTables = from c in _table.Columns.OfType<MetaChildrenColumn>()
 select new SubDetails()
 {
 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 the according to Order first and column name second
 // note if SubGridViewsAttribute is not allied or the attrivute
 // has no value for Order then just sort but column name
 subGridTables = from sg in subGridTables
 orderby sg.Order, sg.Column.Name
 select sg;
 // make sure there are some children columns
 if (subGridTables.Count() > 0)
 {
 // check if more than one children column present
 if (subGridTables.Count() > 1)
 {
 // create tab container to hold each children column
 var tabContainer = new TabContainer();
 tabContainer.ID = "tabContainer";
 // add event handler
 tabContainer.EnableViewState = true; // ***UPDATED 2008年09月27日***
 // add the tab container to the page
 acessor.AddParsedSubObject(tabContainer);
 // add a tab panel for each children table
 foreach (SubDetails SubGridDetails in subGridTables)
 {
 var tabPanel = new AjaxControlToolkit.TabPanel();
 tabPanel.ID = "tp" + SubGridDetails.Column.Name;
 // add the tab panel
 tabContainer.Tabs.Add(tabPanel);
 var subGridAttributes = SubGridDetails.Column.Attributes.OfType<SubGridViewsAttribute>().SingleOrDefault();
 // set the Tab's name to be the tables display name 
 // or table Name if no attribute is present
 if (subGridAttributes != null && subGridAttributes.TabName.Length > 0)
 tabPanel.HeaderText = subGridAttributes.TabName;
 else
 tabPanel.HeaderText = SubGridDetails.Column.ChildTable.DisplayName;
 //Instantiate a DynamicControl for this Children Column
 var childrenGrid = new DynamicControl(DataBoundControlMode.Edit)
 {
 ID = SubGridDetails.Column.Name,
 // set UIHint
 UIHint = "ChildrenGrid",
 // set data field to column name
 DataField = SubGridDetails.Column.Name
 };
 // add the DynamicControl to the tab panel
 tabPanel.Controls.Add(childrenGrid);
 }
 // set the tab pannels index to 0 which
 // forces the first tab to be selected
 if (!_page.IsPostBack)
 tabContainer.ActiveTabIndex = 0;
 }
 else
 {
 // if only one sub grid then don't bother with tabs
 SubDetails SubGridDetails = subGridTables.FirstOrDefault();
 var childrenGrid = new DynamicControl(DataBoundControlMode.Edit)
 {
 ID = SubGridDetails.Column.Name,
 UIHint = "ChildrenGrid",
 DataField = SubGridDetails.Column.Name
 };
 // add the grid to the page
 acessor.AddParsedSubObject(childrenGrid);
 }
 }
 else
 {
 // if no children columns
 // add label to show no grids
 var label = new Label();
 label.Text = "There are no SubGrids";
 label.CssClass = "droplist";
 // add the label to the page
 acessor.AddParsedSubObject(label);
 }
 }
 private class SubDetails
 {
 /// <summary>
 /// Column to display
 /// </summary>
 public MetaChildrenColumn 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 - SubGridViewItemTemplate

Listing 5 is the class that implements ITemplate – SubGridViewItemTemplate, to summarise this generates a template in code at runtime. In this case the template consists of a TabContainer from the Ajax Control Toolkit with a TabPanel for each ChildrenColumn found in the _table passed in to the class in the constructor. The TabPanel in turn contains a DynamicControl with it’s UIHint set to ChildrenGrid.

Personal Note: For me the key thing that Dynamic Data gives us is the DynamicField and DynamicControl which use FieldTemplates, these FielTemplates encapsulate all our field functionality in a central place. This is apart from all the other Dynamic Data goodness that we get from scaffolding etc. For this ITemplate class it remove ALL the complexity from the class and also makes it future proof, because who knows what fields my ChildrenGrid will face in the future? but I don’t need to worry be cause DynamicField and DynamicControl will handle it all.

So an explanation of the code seems appropriate here.

var subGridTables = from c in _table.Columns.OfType<MetaChildrenColumn>()
 select new SubDetails()
 {
 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 the according to Order first and column name second
// note if SubGridViewsAttribute is not allied or the attrivute
// has no value for Order then just sort but column name
subGridTables = from sg in subGridTables
 orderby sg.Order, sg.Column.Name
 select sg;

Listing 6 – Getting the ChildrenColumns

These two Linq to Objects statements here firstly get a list of new objects and secondly sorts this list. It’s clear what the first two properties are in the SubDetails class but Order looks a little complex. What’s happening in setting Order is; if the SubGridViewsAttribute is not present or the Order property of SubGridViewsAttribute not set then the Order property of the new SubDetails object is set to int.MaxValue (this is because int can’t be null and by default is initialised to zero).

The next chunk of code is pretty straight forward it just loops over the subGridTables collection and create the appropriate TabContainer and fills it with TabPanels each with it’s own DynamicControl. If however there is only one ChilcrenColumn in the _table then it just adds a single DynamicControl and finally if there are no ChildrenColumns in the _table then it adds a Label that says There are no SubGrids.

Finally at the end of the class is the definition of the SubDetails class.

Adding the SubGridViewItemTemplate a FormView

The code to SubGridViewItemTemplate to a FormView is straight forward:

FormView1.ItemTemplate = new SubGridViewItemTemplate(table);
So to finish off the EditSubGridViews.aspx page we need to add a little more code:
protected void Page_Init(object sender, EventArgs e)
{
 DynamicDataManager1.RegisterControl(DetailsView1);
 DynamicDataManager1.RegisterControl(FormView1);
 // load item template
 table = DetailsDataSource.GetTable();
 String itemTemplate = table.Model.DynamicDataFolderVirtualPath + "Templates/" + table.Name + ".ascx";
 if (File.Exists(Server.MapPath(itemTemplate)))
 {
 FormView1.ItemTemplate = LoadTemplate(itemTemplate);
 }
 else
 {
 // generate the sub grid views if no template available
 table = DetailsDataSource.GetTable();
 FormView1.ItemTemplate = new SubGridViewItemTemplate(table);
 }
}

Listing 7 – adding the SubGridViewItemTemplate to EditSubGridViews.aspx

Now if you supply a custom ItemTemplate for a table then that will be used to format the FormView otherwise it will auto generate the ItemTemplate for the table.

I seem to have missed out the attribute class SubGridViewsAttribute so I'll list it here:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class SubGridViewsAttribute : Attribute
{
 public int Order { get; set; }
 public String TabName { get; set; }
 public SubGridViewsAttribute()
 {
 }
}

Listing 7 - SubGridViewsAttribute

And now some sample metadata:

[MetadataType(typeof(EmployeeMD))]
public partial class Employee 
{ public class EmployeeMD { [SubGridViews( Order = 2, TabName = "My Employees")] public object Employees { get; set; } [SubGridViews( Order = 3, TabName = "My Territories")] public object EmployeeTerritories { get; set; } [SubGridViews( Order = 1, TabName = "My Orders")] public object Orders { get; set; } public object Employee1 { get; set; } } }

Listing 8 - sample metadata showing order and tab name

Now I think that’s neat smile_teeth

Project Download

[フレーム]

*** UPDATES ***

UPDATED 2008年09月26日 :I’ve just discovered an issue which shows itself when you click on a tab other than the first tab and then click edit on a grid item. When you do this after postback the tab index has been reset back to 0, if you click the tab you had previously selected the row you clicked edit on is in edit mode.
This is just to let you know I know about this BUG and am working on it.
I’ll be back soon with an update smile_teeth
(削除) UPDATED 2008年09月26日 : Added tabContainer.AutoPostBack = true; to fix issue with tabs smile_teeth  (削除ここまで)
UPDATED 2008年09月27日 : A better fix is to add tabContainer.EnableViewState = true; to fix issue with tabs smile_teeth 

Monday, 15 September 2008

Dynamic Data: Part 3-FileUpload FieldTemplates

  1. Part 1 - FileImage_Edit FieldTemplate.
  2. Part 2 - FileImage_Edit FieldTemplate.
  3. Part 3 - FileUpload FiledTemplate.

FileUpload and FileUpload_Edit FiledTemplates

I thought this would complement the DBImage and FileImage FieldTemplates and so I thought what would you want to be able to do:

  • Upload a file to a specified folder.
  • Download the said file once uploaded.
  • Display an image for the file.
  • Control the download capability via attributes and user Roles.
  • Handle errors such as wrong file type or when file is missing from upload folder.

The FileUpload Attributes

In this example I’m creating on attribute to hold all the parameters to do with FileUpload.

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class FileUploadAttribute : Attribute
{
 /// <summary>
 /// where to save files
 /// </summary>
 public String FileUrl { get; set; }
 /// <summary>
 /// File tyoe to allow upload
 /// </summary>
 public String[] FileTypes { get; set; }
 /// <summary>
 /// image type to use for displaying file icon
 /// </summary>
 public String DisplayImageType { get; set; }
 /// <summary>
 /// where to find file type icons
 /// </summary>
 public String DisplayImageUrl { get; set; }
 /// <summary>
 /// If present user must be a member of one
 /// of the roles to be able to download file
 /// </summary>
 public String[] HyperlinkRoles { get; set; }
 /// <summary>
 /// Used to Disable Hyperlink (Enabled by default)
 /// </summary>
 public Boolean DisableHyperlink { get; set; }
 /// <summary>
 /// helper method to check for roles in this attribute
 /// the comparison is case insensitive
 /// </summary>
 /// <param name="role"></param>
 /// <returns></returns>
 public bool HasRole(String[] roles)
 {
 if (HyperlinkRoles.Count() > 0)
 {
 var hasRole = from hr in HyperlinkRoles.AsEnumerable()
 join r in roles.AsEnumerable()
 on hr.ToLower() equals r.ToLower()
 select true;
 return hasRole.Count() > 0;
 }
 return false;
 }
}
Listing 1 – FileUploadAttribute

You will notice in the Listing 1 that all the properties are using c# 3.0’s new Automatic Properties feature less typing; just type prop and hit tab twice and there is you property ready to be filled in.

The second thing you will see is the HasRoles method on this attribute, which takes an array of roles and checks to see if HyperlinkRoles property has any matches. It does this by joining the two arrays together in a Linq to Object query and then selects true for each match in the join. I’m sure this is more readable that the traditional nested foreach loops, it’s certainly neater :D.

The FileUpload FieldTemplate

This FiledTemplate will show the filename and associated icon.

FileUpload with icon

Figure 1- File and associated Icon

<%@ Control
 Language="C#"
 AutoEventWireup="true"
 CodeFile="FileUpload.ascx.cs"
 Inherits="FileImage" %>
<asp:Image ID="Image1" runat="server" />&nbsp;
<asp:Label ID="Label1" runat="server" Text="<%# FieldValueString %>"></asp:Label>
<asp:HyperLink ID="HyperLink1" runat="server"></asp:HyperLink>&nbsp;
<asp:CustomValidator
 ID="CustomValidator1"
 runat="server"
 ErrorMessage="">
</asp:CustomValidator>

Listing 2 – FileUpload.ascx file

As you can see from Listing 1 there are Image, Label and Hyperlink controls on the page. The Label and Hyperlink are mutually exclusive if the conditions are right then a Hyperlink will show so that the file can be downloaded else just a Label will show with the filename.

using System;
using System.IO;
using System.Linq;
using System.Web.DynamicData;
using System.Web.Security;
using System.Web.UI;
using Microsoft.Web.DynamicData;
public partial class FileImage : FieldTemplateUserControl
{
 public override Control DataControl
 {
 get
 {
 return Label1;
 }
 }
 protected override void OnDataBinding(EventArgs e)
 {
 base.OnDataBinding(e);
 //check if field has a value
 if (FieldValue == null)
 return;
 // get the file extension
 String extension = FieldValueString.Substring(
 FieldValueString.LastIndexOf(".") + 1,
 FieldValueString.Length - (FieldValueString.LastIndexOf(".") + 1));
 // get attributes
 var fileUploadAttributes = MetadataAttributes.OfType<FileUploadAttribute>().FirstOrDefault();
 String fileUrl = fileUploadAttributes.FileUrl;
 String displayImageUrl = fileUploadAttributes.DisplayImageUrl;
 String displayImageType = fileUploadAttributes.DisplayImageType;
 // check the file exists else throw validation error
 String filePath;
 if (fileUploadAttributes != null)
 filePath = String.Format(fileUrl, FieldValueString);
 else
 // if attribute not set use default
 filePath = String.Format("~/files/{0}", FieldValueString);
 // show the relavent control depending on metadata
 if (fileUploadAttributes.HyperlinkRoles.Length > 0)
 {
 // if there are roles then check: 
 // if user is in one of the roles supplied
 // or if the hyperlinks are disabled 
 // or if the file does not exist
 // then hide the link
 if (!fileUploadAttributes.HasRole(Roles.GetRolesForUser()) fileUploadAttributes.DisableHyperlink !File.Exists(Server.MapPath(filePath)))
 {
 Label1.Text = FieldValueString;
 HyperLink1.Visible = false;
 }
 else
 {
 Label1.Visible = false;
 HyperLink1.Text = FieldValueString;
 HyperLink1.NavigateUrl = filePath;
 }
 }
 else
 {
 // if either hyperlinks are disabled or the
 // file does not exist then hide the link
 if (fileUploadAttributes.DisableHyperlink !File.Exists(Server.MapPath(filePath)))
 {
 Label1.Text = FieldValueString;
 HyperLink1.Visible = false;
 }
 else
 {
 Label1.Visible = false;
 HyperLink1.Text = FieldValueString;
 HyperLink1.NavigateUrl = filePath;
 }
 }
 // check file exists on file system
 if (!File.Exists(Server.MapPath(filePath)))
 {
 CustomValidator1.ErrorMessage = String.Format("{0} does not exist", FieldValueString);
 CustomValidator1.IsValid = false;
 }
 // show the icon
 if (!String.IsNullOrEmpty(extension))
 {
 // set the file type image
 if (!String.IsNullOrEmpty(displayImageUrl))
 {
 Image1.ImageUrl = String.Format(displayImageUrl, extension + "." + displayImageType);
 }
 else
 {
 // if attribute not set the use default
 Image1.ImageUrl = String.Format("~/images/{0}", extension + "." + displayImageType);
 }
 Image1.AlternateText = extension + " file";
 // if you apply dimentions from DD Futures
 var imageFormat = MetadataAttributes.OfType<ImageFormatAttribute>().FirstOrDefault();
 if (imageFormat != null)
 {
 // if either of the dims is 0 don't set it
 // this will mean that the aspect will remain locked
 if (imageFormat.DisplayWidth != 0)
 Image1.Width = imageFormat.DisplayWidth;
 if (imageFormat.DisplayHeight != 0)
 Image1.Height = imageFormat.DisplayHeight;
 }
 }
 else
 {
 // if file has no extension then hide image
 Image1.Visible = false;
 }
 }
}

Listing 3 – FileUpload.ascx.cs file

In Listing 3 you can see that everything goes on in the OnDataBinding event handler.

The FileUpload_Edit FieldTemplate

<%@ Control
 Language="C#"
 AutoEventWireup="true"
 CodeFile="FileUpload_Edit.ascx.cs"
 Inherits="FileImage_Edit" %>
 
<asp:PlaceHolder ID="PlaceHolder1" runat="server" Visible="false">
 <asp:Image ID="Image1" runat="server" />&nbsp;
 <asp:Label ID="Label1" runat="server" Text="<%# FieldValueString %>"></asp:Label>
 <asp:HyperLink ID="HyperLink1" runat="server"></asp:HyperLink>&nbsp;
</asp:PlaceHolder>
<asp:FileUpload ID="FileUpload1" runat="server" />&nbsp;
<asp:CustomValidator
 ID="CustomValidator1"
 runat="server"
 ErrorMessage="">
</asp:CustomValidator>

Listing 4 – FileUpload_Edit.ascx file

In Listing 4 the PlaceHolder control is used to hide the Image, Label and Hyperlink when in insert mode or when there is no value to be shown.

using System;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Web.DynamicData;
using System.Web.Security;
using System.Web.UI;
using Microsoft.Web.DynamicData;
public partial class FileImage_Edit : FieldTemplateUserControl
{
 public override Control DataControl
 {
 get
 {
 return Label1;
 }
 }
 protected override void OnDataBinding(EventArgs e)
 {
 base.OnDataBinding(e);
 //check if field has a value
 if (FieldValue == null)
 return;
 // when there is already a value in the FieldValue
 // then show the icon and label/hyperlink
 PlaceHolder1.Visible = true;
 // get the file extension
 String extension = FieldValueString.Substring(
 FieldValueString.LastIndexOf(".") + 1,
 FieldValueString.Length - (FieldValueString.LastIndexOf(".") + 1));
 // get attributes
 var fileUploadAttributes = MetadataAttributes.OfType<FileUploadAttribute>().FirstOrDefault();
 String fileUrl = fileUploadAttributes.FileUrl;
 String displayImageUrl = fileUploadAttributes.DisplayImageUrl;
 String displayImageType = fileUploadAttributes.DisplayImageType;
 String filePath;
 // check the file exists else throw validation error
 if (fileUploadAttributes != null)
 filePath = String.Format(fileUrl, FieldValueString);
 else
 // if attribute not set use default
 filePath = String.Format("~/files/{0}", FieldValueString);
 // show the relavent control depending on metadata
 if (fileUploadAttributes.HyperlinkRoles.Length > 0)
 {
 // if there are roles then check: 
 // if user is in one of the roles supplied
 // or if the hyperlinks are disabled 
 // or if the file does not exist
 // then hide the link
 if (!fileUploadAttributes.HasRole(Roles.GetRolesForUser()) fileUploadAttributes.DisableHyperlink !File.Exists(Server.MapPath(filePath)))
 {
 Label1.Text = FieldValueString;
 HyperLink1.Visible = false;
 }
 else
 {
 Label1.Visible = false;
 HyperLink1.Text = FieldValueString;
 HyperLink1.NavigateUrl = filePath;
 }
 }
 else
 {
 // if either hyperlinks are disabled or the
 // file does not exist then hide the link
 if (fileUploadAttributes.DisableHyperlink !File.Exists(Server.MapPath(filePath)))
 {
 Label1.Text = FieldValueString;
 HyperLink1.Visible = false;
 }
 else
 {
 Label1.Visible = false;
 HyperLink1.Text = FieldValueString;
 HyperLink1.NavigateUrl = filePath;
 }
 }
 // check file exists on file system
 if (!File.Exists(Server.MapPath(filePath)))
 {
 CustomValidator1.ErrorMessage = String.Format("{0} does not exist", FieldValueString);
 CustomValidator1.IsValid = false;
 }
 // show the icon
 if (!String.IsNullOrEmpty(extension))
 {
 // set the file type image
 if (!String.IsNullOrEmpty(displayImageUrl))
 {
 Image1.ImageUrl = String.Format(displayImageUrl, extension + "." + displayImageType);
 }
 else
 {
 // if attribute not set the use default
 Image1.ImageUrl = String.Format("~/images/{0}", extension + "." + displayImageType);
 }
 Image1.AlternateText = extension + " file";
 // if you apply dimentions from DD Futures
 var imageFormat = MetadataAttributes.OfType<ImageFormatAttribute>().FirstOrDefault();
 if (imageFormat != null)
 {
 // if either of the dims is 0 don't set it
 // this will mean that the aspect will remain locked
 if (imageFormat.DisplayWidth != 0)
 Image1.Width = imageFormat.DisplayWidth;
 if (imageFormat.DisplayHeight != 0)
 Image1.Height = imageFormat.DisplayHeight;
 }
 }
 else
 {
 // if file has no extension then hide image
 Image1.Visible = false;
 }
 }
 protected override void ExtractValues(IOrderedDictionary dictionary)
 {
 // get attributes
 var fileUploadAttributes = MetadataAttributes.OfType<FileUploadAttribute>().FirstOrDefault();
 String fileUrl;
 String[] extensions;
 if (fileUploadAttributes != null)
 {
 fileUrl = fileUploadAttributes.FileUrl;
 extensions = fileUploadAttributes.FileTypes;
 if (FileUpload1.HasFile)
 {
 // get the files folder
 String filesDir = fileUrl.Substring(0, fileUrl.LastIndexOf("/") + 1);
 // resolve full path c:\... etc
 String path = Server.MapPath(filesDir);
 // get files extension without the dot
 String fileExtension = FileUpload1.FileName.Substring(
 FileUpload1.FileName.LastIndexOf(".") + 1).ToLower();
 // check file has an allowed file extension
 if (extensions.Contains(fileExtension))
 {
 // try to upload the file showing error if it fails
 try
 {
 FileUpload1.PostedFile.SaveAs(path + "\\" + FileUpload1.FileName);
 Image1.ImageUrl = String.Format(fileUploadAttributes.DisplayImageUrl, fileExtension + ".png");
 Image1.AlternateText = fileExtension + " file";
 dictionary[Column.Name] = FileUpload1.FileName;
 }
 catch (Exception ex)
 {
 // display error
 CustomValidator1.IsValid = false;
 CustomValidator1.ErrorMessage = ex.Message;
 }
 }
 else
 {
 CustomValidator1.IsValid = false;
 CustomValidator1.ErrorMessage = String.Format("{0} is not a valid file to upload", FieldValueString);
 }
 }
 }
 }
}
Listing 5 - FileUpload_Edit.ascx.cs file

In Listing 5 the OnDataBinding event handler is pretty much the same as the FileUpload.ascs.cs file. Here its the ExtractValues method that does the work of uploading and displaying errors, i.e. if the file type of the file to be uploaded does not match a file type specified in the metadata or there is an error during the upload.

Helper Class FileUploadHelper

public static class FileUploadHelper
{
 /// <summary>
 /// If the given table contains a column that has a UI Hint with the value "DbImage", finds the ScriptManager
 /// for the current page and disables partial rendering
 /// </summary>
 /// <param name="page"></param>
 /// <param name="table"></param>
 public static void DisablePartialRenderingForUpload(Page page, MetaTable table)
 {
 foreach (var column in table.Columns)
 {
 // TODO this depends on the name of the field template, need to fix
 if (String.Equals(
 column.UIHint, "DBImage", StringComparison.OrdinalIgnoreCase)
 String.Equals(column.UIHint, "FileImage", StringComparison.OrdinalIgnoreCase)
 String.Equals(column.UIHint, "FileUpload", StringComparison.OrdinalIgnoreCase))
 {
 var sm = ScriptManager.GetCurrent(page);
 if (sm != null)
 {
 sm.EnablePartialRendering = false;
 }
 break;
 }
 }
 }
}

Listing 6 - FileUploadHelper

This is just a modified version of the Dynamic Data Futures DisablePartialRenderingForUpload method the only difference is that I’ve added support for both my file upload capable FieldTemplates FileImage and FileUpload.

Finally Some Sample Metadata

[MetadataType(typeof(FileImageTestMD))]
public partial class FileImageTest : INotifyPropertyChanging, INotifyPropertyChanged
{
 public class FileImageTestMD
 {
 public object Id { get; set; }
 public object Description { get; set; }
 [UIHint("FileUpload")]
 [FileUpload(
 FileUrl = "~/files/{0}",
 FileTypes = new String[] { "pdf", "xls", "doc", "xps" },
 DisplayImageType = "png",
 DisableHyperlink = false,
 HyperlinkRoles=new String[] { "Admin", "Accounts" },
 DisplayImageUrl = "~/images/{0}")]
 [ImageFormat(22, 0)]
 public object filePath { get; set; }
 }
}

Listing 7 – sample metadata

The FileUpload Project

[フレーム]

Note: Please note that the ASPNETDB.MDF supplied in this website is SQL 2008 Express and will not work with SQL 2005 and earlier, you will need to set your own up. Or you can just strip out the login capability from the site.master and web.config.

Enjoy smile_teeth

Subscribe to: Comments (Atom)

AltStyle によって変換されたページ (->オリジナル) /