Monday, 23 March 2009

Microsoft ASP.NET 4.0 Data Access and Dynamic Data at MIX09

See David Ebbo’s great session at MIX09 for the latest on ASP.Net and Dynamic Data in .Net 4.0

Microsoft ASP.NET 4.0 Data Access: Patterns for Success with Web Forms

In here David shows off some of the new features in ASP.Net 4.0 including DomainService and DomainDataSource which are great because it gives you a layer in between your model and your site making the code in you site data provider neutral. Simply by replacing the DomainService you could change you data provider with out changing the code in you PageTemplates or FiledTemplates.

This is a really great session well worth the watch for ASP.Net classic and Dynamic Data.

it gets 5 starts from me!

   5stars

There were 6 start there before? Are you sure?

Monday, 16 March 2009

Adding Dynamic Data Previews to a New or Exiting Dynamic Data Website

What I want to do here is create a template website I can copy and reuse to build web sites based on the Preview. Hope this is of some help.

Create a new Dynamic Data Web Site:

ScreenShot248

Once created empty the DynamicData folder (keeping the folder) remove all sub folders.

Copy the contents of the following folders to the Dynamic Data Folder for:

Linq to SQL Web Sites

Linq to SQL folders

Entity Framework Web Sites

Entity Framework folders

Once you’ve copied the files

Copied folders

It’s time to do some editing of the files, we’ll use the Ctrl+Shift+H to do a search and replace of files in folders.

Setting up Look in folder

In the Find and Replace dialogue click the ellipsis (…) button at the end of the Look in: drop down list.

  1. Find the root folder of your website.
  2. Select the DynamicData folder in your site.
  3. Add to the Selected folders.

Find and Replace dialogue

Using the Search Patterns list below fill in the Find and Replace dialogue box:

  1. Search for pattern.
  2. Replace with pattern.
  3. Use regular expression.
  4. Enter the file type to search in.

Search patterns:

step Search for: Replace with: Use Regular expressions file types
1. namespace DynamicData(EF)*Project \{\n   yes *.ascx.cs
2. \}\n\} \} yes *.ascx.cs
3. Inherits="DynamicData(EF)*Project.   Yes *.ascx
4. CodeBehind=" CodeFile=" No *.ascx

In step 1 & 3 you may encounter any of the following name spaces to match and remove:

  1. DynamicDataProject.DynamicData.Filters {
  2. DynamicDataProject.DynamicData.EntityTemplates {
  3. DynamicDataProject.DynamicDataL2S.EntityTemplates {
  4. DynamicDataProject {
  5. DynamicDataEFProject {

I would attempt to remove them in the order shown above (4 & 5 are covered by the pattern namespace DynamicData(EF)*Project \{\n

 Finally Add a Reference to the Preview DLL’s

  1. Right click the root of the website, and choose “Add Reference…”
    Add reference to web site
  2. In the “Add Reference” dialogue browse to the Common Files folder in the location where you unzipped the Preview. Then select all the dll’s and click OK to add a reference to them in your web site.
    Add reference dialogue

Finally copy the:

File Copy to Folder
AutocompleteFilter.asmx.cs App_Code folder (create if you have not already).
AutocompleteFilter.asmx ~/ (root of website)
AjaxToolkitFixes.css ~/ (root of website)
AutocompleteStyle.css ~/ (root of website)
Site.css ~/ (root of website)
Site.master ~/ (root of website)
Site.master.cs ~/ (root of website)
web.config ~/ (root of website)

 

Similar editing is now required of these files see the Search Patterns above.

Edit the web.config and remove the contents of the connection strings node in the web.config

remove connection strings from web.config

Finally tweak the GridView  on the Default.aspx page.

<asp:GridView ID="Menu1" 
    runat="server" 
    AutoGenerateColumns="false" 
    CssClass="DDGridView"
    RowStyle-CssClass="td" 
    HeaderStyle-CssClass="th" 
    CellPadding="6">

See the BOLD ITALIC text this is to take advantage of the friendlier CSS now supplied with the Preview it won’t stamp on other CSS you may want to use in your web site as the CSS in the .Net 3.5 SP1 version does.

Now you have a sample that you can use over an over like a template

Your next step is the same as if you had just done file new “Dynamic Data Web Site” but I would copy to a new folder first so I can use this as a template each time I want a Preview DD website.

Happy coding

I’m a an MVP and I’m a PC

Friday, 13 February 2009

AJAX History in a Dynamic Data Website

If you want to know a little more about AJAX History have a look at this video on MSDN Screencasts (used to be MSDN .Net Nuggets) Managing Browser History with ASP.NET AJAX and the ASP.NET 3.5 Extensions Preview it is slightly out of date in that some of the properties have been renamed but its essentially correct and a good foundation for AJAX History’s use. There is a second article Managing Browser History on the Client with ASP.NET AJAX and the ASP.NET 3.5 Extensions Preview which may be of interest for some.

The Problem

To appreciate the solution you need to understand the problem. So here’s what happens you have a standard ASP.Net Dynamic Data V1 website with several filters on each list page, there are two things that will irritate your users to do with AJAX partial post back:

1. You make several selections and click back you will go to the previous page not the last selection (try it with and without partial render enabled in site.master EnablePartialRendering="true" see Listing 1).

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Dynamic Data Site</title>
    <link href="~/Site.css" rel="stylesheet" type="text/css" />
</head>
<body class="template">
    <h1><span class="allcaps">Dynamic Data Site</span></h1>
    <div class="back">
        <a runat="server" href="~/"><img alt="Back to home page" runat="server" src="DynamicData/Content/Images/back.gif" />Back to home page</a>
    </div>

    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="ScriptManager1" runat="server" EnablePartialRendering="true"/>
        <asp:ContentPlaceHolder id="ContentPlaceHolder1" runat="server">
        </asp:ContentPlaceHolder>
    </div>
    </form>
</body>
</html>

Listing 1 – the default Site.master

2. You click a link from a List page on which you have made several filter selections view the sub page and then click back you reach the page with no filtering applied.

Both of these issues can be really irritating for the user, because A it’s not the expected behaviour and B it’s a pain to have to redo all those filters (after all users are really lazy smile_omg)

Have you watched the video? well if you have no prior knowledge of AJAX History I strongly suggest you have a look at the video after all it's only 13mins 27secs long.

The Solution

First of all this is only my first attempt at this I think once I get into the Dynamic Data V2.0 Preview/Release I will redo this as a class that inherits from UserFilterControlBase to easily give this functionality in filter templates.

<asp:ScriptManager 
    ID="ScriptManager1" 
    runat="server" 
    EnableHistory="true"
    EnableSecureHistoryState="false"
    EnablePartialRendering="true"/>

Listing 2 – the ScriptManager on Site.master alterations

As you can see from the Listing 2 enabling AJAX History is quite easy you just need to add the EnableHistory="true" property to the control. The other property is to disable encryption of the history information that is added to the URL EnableSecureHistoryState="false" this is one of the methods that have been renamed for the RTM version.

Now we need to edit the ~/DynamicData/Content/FilterUserControl.ascx.cs file see Listing 3 for the code all alteration are in BOLD italic.

using System;
using System.Collections.Specialized;
using System.Linq;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class FilterUserControl : System.Web.DynamicData.FilterUserControlBase
{
    // global variable for holding the History Point value
    private String HistoryPointValue;

    public event EventHandler SelectedIndexChanged
    {
        add
        {
            DropDownList1.SelectedIndexChanged += value;
        }
        remove
        {
            DropDownList1.SelectedIndexChanged -= value;
        }
    }

    public override string SelectedValue
    {
        get
        {
            return DropDownList1.SelectedValue;
        }
    }

    protected void Page_Init(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
        {
            PopulateListControl(DropDownList1);

            // Set the initial value if there is one
            if (!String.IsNullOrEmpty(InitialValue))
                DropDownList1.SelectedValue = InitialValue;
        }

        // add event handler to capture history point.
        this.SelectedIndexChanged += ThisSelectedIndexChanged;

        // Add OnNavigate handler to restore History points.
        var scriptManager = ScriptManager.GetCurrent(Page);
        if (scriptManager != null)
            scriptManager.Navigate += ScriptManagerOnNavigate;
    }

    protected void ThisSelectedIndexChanged(object sender, EventArgs e)
    {
        var filterRepeater = this.GetParentFilterRepeater();
        if (filterRepeater != null)
        {
            // add History point for the curret state of
            // all filters so we can restore them later.
            var scriptManager = ScriptManager.GetCurrent(Page);
            if (scriptManager != null && scriptManager.IsInAsyncPostBack)
            {
                var nvcHistory = new NameValueCollection();

                var filterControls = filterRepeater.GetFilterControls();
                if (filterControls.Count() > 0)
                {
                    foreach (var filter in filterControls)
                    {
                        var f = filter as FilterUserControl;
                        if (f != null)
                            nvcHistory.Add(f.DataField, f.SelectedValue);
                    }
                    // if we get some filter states add the history point.
                    if (nvcHistory.Count > 0)
                        scriptManager.AddHistoryPoint(nvcHistory, Page.Title);
                }
            }
        }
    }

    protected void ScriptManagerOnNavigate(object sender, HistoryEventArgs e)
    {
        HistoryPointValue = e.State[DataField];
        if (e.State[DataField] == null || e.State[DataField] == "")
            DropDownList1.SelectedIndex = 0;
        else if(HistoryPointValue != null)
        {
            // check if history point is in the DDL and set History Point value
            ListItem item = DropDownList1.Items.FindByValue(HistoryPointValue);
            if (item != null)
                DropDownList1.SelectedValue = HistoryPointValue;
        }
    }
}

Listing 3 – FilterUserControl.ascx.cs file

And finally here are the extension methods use in Listing 3.

using System.Collections.Generic;
using System.Linq;
using System.Web.DynamicData;
using System.Web.UI;
using System.Web.UI.WebControls;

/// <summary>
/// Summary description for ExtensionMethods
/// </summary>
public static class ExtensionMethods
{
    /// <summary>
    /// Get the parent FilterRepeater of the control it's used on.
    /// </summary>
    /// <param name="control">The control to find the parent FilterRepeater of.</param>
    /// <returns>The parent FilterRepeater.</returns>
    public static FilterRepeater GetParentFilterRepeater(this Control control)
    {
        var parentControl = control.Parent;
        while (parentControl != null)
        {
            var FilterRepeater = parentControl as FilterRepeater;
            if (FilterRepeater != null)
                return FilterRepeater;
            else
                parentControl = parentControl.Parent;
        }
        return null;
    }

    /// <summary>
    /// Get a List of FilterUserControlBase filters from the FilterRepeater.
    /// </summary>
    /// <param name="filterRepeater">The FilterRepeater to get a List of filter from</param>
    /// <returns>returns a List of type FilterUserControlBase.</returns>
    public static IEnumerable<UserControl> GetFilterControls(this FilterRepeater filterRepeater)
    {
        var filters = new List<UserControl>();

        foreach (RepeaterItem item in filterRepeater.Items)
        {
            var filter = item.Controls.OfType<UserControl>().FirstOrDefault();
            filters.Add(filter);
        }

        return filters.AsEnumerable();
    }
}

Listing 4 – Extension methods

Now when you skip back and forth through pages you should find it works just like you and your users were expecting.

Wednesday, 28 January 2009

Making Individual Tables Read Only – Dynamic Data

These are two thing you can do to make your table Read Only.

  1. Add an attribute for instance the ReadOnlyAttribute or your own custom attribute
  2. Using Routing to restrict access to the Edit or Insert pages

The first solution I covered in this article Writing Attributes and Extension Methods for Dynamic Data so I’ll just show the Routing example here.

// Setting a table to Read only
var visibleTables = from t in MetaModel.Default.Tables
                    where t.Scaffold == true
                    select t;

var readOnlyTables = new StringBuilder();
foreach (var table in visibleTables)
{
    var isReadOnly = table.Attributes.OfType<ReadOnlyAttribute>().
DefaultIfEmpty(new ReadOnlyAttribute(false)).
FirstOrDefault();
    if (isReadOnly.IsReadOnly)
        readOnlyTables.Append(table.Name + "|");
}

routes.Add(new DynamicDataRoute("{table}/{action}.aspx")
{
    Constraints = new RouteValueDictionary(new
    {
        action = "Edit|Insert",
        table = readOnlyTables.ToString().Substring(0, readOnlyTables.Length - 1)
    }),
    ViewName = "Details",
    Model = model
});

Listing 1 – Routing example.

[ReadOnly(true)]
public partial class Employee {}

Listing 2 – sample attribute.

In Listing 1 we see three things going on:

  1. We get a list of visible tables using the Scaffold attribute to find out if the table should be shown.
  2. Next we loop through the visible tables and if the table is set to ReadOnly then we add it to the readOnlyTables StringBuilder to build a list of read only tables (e.g. “Products|Customers”).
  3. Build a route that constrains the these tables to only get to the Details page.
Note: That this route must appear before the default routes.

Happy coding and remember your a PC HappyWizard but for us coders I prefer the short lived “Live to Code and Code to Live”

Important: The only caveat here is that the Delete button still works. :( But you could catch that in Business logic if you didn’t want to edit each page.

Monday, 26 January 2009

Disallow Navigation on ForeignKey FieldTemplate – Dynamic Data

This article originates from a post I ding on the ASP.NET Dynamic Data Forum here Re: A few problems with my dynamic data website, Rick Anderson suggesting that it would be a useful post to link to in his FAQ. So here it is for easy access rather than having to search the forum for it.

HappyWizard

Firstly the idea is that you want to show the name of the entity but not link to if from the ForeignKey FieldTemplate.

Category has Navigation disabled

Figure 1 - Category has Navigation disabled

The code we need for this comes in three parts

  1. The Attribute
  2. The modification the the FieldTemplate
  3. The Metadata.

The Attribute

[AttributeUsage(AttributeTargets.Property)]
public class AllowNavigationAttribute : Attribute
{
    public Boolean Show { get; private set; }

    public AllowNavigationAttribute(Boolean show)
    {
        Show = show;
    }
    // this will allow us to have a default set to false
    public static AllowNavigationAttribute Default = new 
AllowNavigationAttribute
(true); }

Listing 1 – AllowNavigation attribute

Note the use of the Default see Writing Attributes and Extension Methods for Dynamic Data for the details on why.

The modification the the FieldTemplate

Now we need to add a little change to the ForeignKey.aspx.cs file which can be found in the ~/DynamicData/FieldTemplates folder of your Dynamic Data site.

protected string GetNavigateUrl()
{
    var allow = Column.Attributes.OfType<AllowNavigationAttribute>()
                .DefaultIfEmpty(new AllowNavigationAttribute(true))
                .FirstOrDefault().Show;
    if (!AllowNavigation || !allow)
    {
        return null;
    }

    if (String.IsNullOrEmpty(NavigateUrl))
    {
        return ForeignKeyPath;
    }
    else
    {
        return BuildForeignKeyPath(NavigateUrl);
    }
}

Listing 2 – ForeignKey FieldTemplate modifications

All we’ve done here is get the attribute into the allow variable and the add it to the checks in the if statement so that:

  • If the internal AllowNavigation is set then null is returned.
  • If the custom attribute is set null is returned.

The Metadata

[MetadataType(typeof(ProductMD))]
public partial class Product
{
    public partial class ProductMD
    {
       //EntityRef
        [AllowNavigation(false)]
        public object Category {get;set;}
    }
}

Listing 3 – sample Metadata

Now if you attribute a foreign key column up with the AllowNavigationAttribute you can turn off the hyperlink.

Happy Coding and remember You’re a PC HappyWizard

Saturday, 24 January 2009

Setting the Initial Sort Order – Dynamic Data

This is just a quick note on a post I made on the Dynamic Data Forum to answer a question where setting the DisplayColumnAttribute should set the default sort order of the GridView on the List page but didn’t. Here’s how I solved it with a hint or two from Marcin Dobosz.

[MetadataType(typeof(Orders_Metadata ))]
[DisplayColumn("OrderID", "OrderID", true)]
public partial class Orders
{
    public class Orders_Metadata
    {
... }
}

Listing 1 – Example Metadata

Assuming you have a table with the DisplayColumnAttribute set you could put this on your List page: 

var displayColumn = table.GetAttribute<DisplayColumnAttribute>();
if (displayColumn != null && displayColumn.SortColumn != null)
{
    GridView1.Sort(displayColumn.SortColumn,
        displayColumn.SortDescending ? SortDirection.Descending : SortDirection.Ascending);
}

Listing 2 – Edit the Page_Load event handler and add the following code

public static T GetAttribute<T>(this MetaTable table) where T : Attribute
{
    return table.Attributes.OfType<T>().FirstOrDefault();
}

Listing 3 – you will also need this extension method see Writing Attributes and Extension Methods for Dynamic Data

Add your DisplayColumnAttribute (see Listing 1) to the table you want sorted (note: it must have the second string constant even if it’s the same name, as the second string it the one that the sort is done on, and the third value if true causes the sort to be descending). Then in Listing 2 you get the attribute using the extension method from Listing 3 and apply the relevant sort.

This is here mainly so I can find it again! HappyWizard

Friday, 23 January 2009

Making a Field Read-Only via the ReadOnlyAttribute – Dynamic Data

This article is as a result of this thread not editable attribute on the Dynamic Data Forum:

What you need to do is:

  1. Use the ReadOnlyAttribute in System.ComponentModel and attribute up the column you want as read only in edit mode

  2. Create an class that implements IAutoFieldGenrator.

  3. A custom DynamicField.

And here's the sample:

using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Web.DynamicData;
using System.Web.UI;
using System.Web.UI.WebControls;

public class FieldsManager : IAutoFieldGenerator
{
    protected MetaTable _table;

    public FieldsManager(MetaTable table)
    {
        _table = table;
    }

    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)
                continue;

            DynamicField f;

            // here we check to dee
            if (column.Attributes.OfType<ReadOnlyAttribute>().
DefaultIfEmpty(new ReadOnlyAttribute(false)).
FirstOrDefault().IsReadOnly) f = new DynamicReadonlyField(); else f = new DynamicField(); f.DataField = column.Name; oFields.Add(f); } return oFields; } } // special thanks to david Ebbo for this public class DynamicReadonlyField : DynamicField { public override void InitializeCell( DataControlFieldCell cell, DataControlCellType cellType, DataControlRowState rowState, int rowIndex) { if (cellType == DataControlCellType.DataCell) { var control = new DynamicControl() { DataField = DataField }; // Copy various properties into the control control.UIHint = UIHint; control.HtmlEncode = HtmlEncode; control.NullDisplayText = NullDisplayText; // this the default for DynamicControl and has to be // manually changed you do not need this line of code // its there just to remind us what we are doing. control.Mode = DataBoundControlMode.ReadOnly; cell.Controls.Add(control); } else { base.InitializeCell(cell, cellType, rowState, rowIndex); } } }

And in the Edit page add:

protected void Page_Init(object sender, EventArgs e)
{
    DynamicDataManager1.RegisterControl(DetailsView1);
    table = DetailsDataSource.GetTable();
    DetailsView1.RowsGenerator = new FieldsManager(table);
}

In the Page_Init add the BOLD ITALIC lines

This is a direct copy of the answer I gave in the thread HappyWizard

Getting Default Values from List page Filters for Insert – Dynamic Data

The Idea here is that when you are navigating through your pages you will select some filters on the way as here in Figure 1 where we have selected a Customer an Employee and a Shipper so what I wanted was not to throw away that information when I clicked insert and then get Figure 2.

Filtered Liast page

Figure 1 – Filtered List page

Values Defaulted on Insert

Figure 2 – Values Defaulted on Insert

Steps to Accomplish this:

  1. Modify ForeignKey_Edit.aspx.cs to check for query string parameters relating the column.
  2. Create a couple of extension methods to get the filters and then return a string of key, value query string.
  3. Hook up the Insert hyperlink to have these parameters if available to the insert URL.

Hope that makes sense.

Modify ForeignKey_Edit to check for query string parameters relating the column

This is relatively simple we just add an event handler to the DropDownList1’s PreRender event ForeignKey_Edit FieldTemplate code behind file.

protected void DropDownList1_PreRender(object sender, EventArgs e)
{
    // Adding a default value to a textbox
    // value aquired via query string
    string value = Request.QueryString[Column.Name];
    if (this.Mode == DataBoundControlMode.Insert && !string.IsNullOrEmpty(value))
    {
        ListItem item = DropDownList1.Items.FindByValue(value);
        if (item != null)
            DropDownList1.SelectedValue = value;
    }
}

Listing 1 – DropDownList’s PreRender event handler

Note: Don’t forget to wire up the event hander after pasting in the code.

What this does is check to see if there is a QueryString parameter that matches the column name and if this matches a value in the DropDownList’s Items collection it set’s this value as the DropDownList’s SelectedValue.

Creating Extension Methods that get the FilterUserControls and Return a String of Key, Value query string

There are two extension methods required:

  1. Gets a collection of the filters from the FilterRepeater that have a value.
  2. Take the filters and extract the key values pairs and return them as a QueryString to be appended to the Insert URL

Here’s the first one:

public static IEnumerable<FilterUserControlBase> GetFilterControls(this FilterRepeater filterRepeater)
{
    var filters = new List<FilterUserControlBase>();

    foreach (RepeaterItem item in filterRepeater.Items)
    {
        var filter = item.Controls.OfType<FilterUserControlBase>().FirstOrDefault();
        if (filter != null)
            filters.Add(filter);
    }

    return filters.AsEnumerable();
}

Listing 2 – GetFilterControls extension method.

This extension method shown in Listing 2 just loops through the FilterRepeater Items collection and using the OfType<T>() Linq extension method gets the FilterUserControl from it’s controls collection. Then adds this to the the list of found filters and finally returns the list.

public static String GetQueryStringParameters(this FilterRepeater filterRepeater)
{
    var filterControls = filterRepeater.GetFilterControls();
    if (filterControls.Count() > 0)
    {
        var queryParameters = new StringBuilder();
        queryParameters.Append("?");
        foreach (var filter in filterControls)
        {
            if (filter.SelectedValue != "")
                queryParameters.Append(filter.DataField + "=" + filter.SelectedValue + "&");
        }
        return queryParameters.ToString().Substring(0, queryParameters.Length - 1);
    }
    else
        return "";
}

Listing 3 – GetQueryStringParameters extensionmethod.

The second extension method shown in Listing 3 takes the output of the first and then loops through building the query parameters string for passing back to the caller.

Hook up the Insert hyperlink to the Returned Query Parameters

Now in the List page all you need to do is add the following code:

// setup the insert hyperlink
InsertHyperLink.NavigateUrl = table.GetActionPath(PageAction.Insert) 
    + FilterRepeater.GetQueryStringParameters();

Listing 4 – Putting the extension methods to use.

And this is why I like c# 3.0 extension methods so much HappyWizard

Tuesday, 20 January 2009

Writing Attributes and Extension Methods for Dynamic Data

This is just a short article on some useful tips I’ve found for writing Attributes for Dynamic Data.

I say Tips there really isn’t that much to this.

Let me show you an attribute I just created for a post on the Dynamic Data Forum:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class DisableEditingAttribute : Attribute
{
    public Boolean Disable { get; set; }

    public static DisableEditingAttribute Default = new DisableEditingAttribute();

    public DisableEditingAttribute() : this(false) { }

    public DisableEditingAttribute(Boolean disable)
    {
        Disable = disable;
    }
}

Listing 1 – Disable Editing Attribute

And here’s it being used in the List page:

protected void Page_Load(object sender, EventArgs e)
{
    table = GridDataSource.GetTable();
    Title = table.DisplayName;

    //GridView1.Sort("ColumnName, ColumnName", SortDirection.Descending);

    InsertHyperLink.NavigateUrl = table.GetActionPath(PageAction.Insert);

    // Disable various options if the table is readonly
    var editingDisabled = table.Attributes.OfType<DisableEditingAttribute>().DefaultIfEmpty(new DisableEditingAttribute()).FirstOrDefault();
    if (table.IsReadOnly || editingDisabled.Disable)
    {
        GridView1.Columns[0].Visible = false;
        InsertHyperLink.Visible = false;
    }
}

Listing 2 – DisableEditingAttribute in use BOLD ITALIC

The first ting to note is a Linq thing; the DefautIfEmpty extension method. This is used to specify a default value if the returned value is null (From the OfType FirstOrDefault combination) this means I don’t have to test for null like:

// Disable various options if the table is readonly
var editingDisabled = table.Attributes.OfType<DisableEditingAttribute>().FirstOrDefault();
if (table.IsReadOnly || (editingDisabled != null && editingDisabled.Disable))
{
    GridView1.Columns[0].Visible = false;
    InsertHyperLink.Visible = false;
}

Listing 3 – Having to test for null

Another way of doing this would be to use a Default static method to provide the default value.

// Disable various options if the table is readonly
var editingDisabled = table.Attributes.OfType<DisableEditingAttribute>().
    DefaultIfEmpty(DisableEditingAttribute.Default).FirstOrDefault();
if (table.IsReadOnly || editingDisabled.Disable)
{
    GridView1.Columns[0].Visible = false;
    InsertHyperLink.Visible = false;
}

Listing 4 – using the Default static method

Either method works but my preference is to have a default constructor Attribute() and I’ll explain why. But first let me show you an Extension method I found in the Dynamic Data Futures project extension methods:

public static T GetAttribute<T>(this MetaTable table) where T : Attribute
{
    return table.Attributes.OfType<T>().FirstOrDefault();
}

Listing 5 – the GetAttribute Generic extension method.

And it’s use:

// Disable various options if the table is readonly
var editingDisabled = table.GetAttribute<DisableEditingAttribute>();
if (table.IsReadOnly || (editingDisabled != null && editingDisabled.Disable))
{
    GridView1.Columns[0].Visible = false;
    InsertHyperLink.Visible = false;
}

Listing 6 – the GetAttribute extension method in use

This is better more readable code but the test for null makes it a bit messy. So here are my interpretations of the GetAttribute extension method:

public static T GetAttributeOrDefault<T>(this MetaTable table) where T : Attribute, new()
{
    return table.Attributes.OfType<T>().DefaultIfEmpty(new T()).FirstOrDefault();
}

public static T GetAttributeOrDefault<T>(this MetaColumn column) where T : Attribute, new()
{
    return column.Attributes.OfType<T>().DefaultIfEmpty(new T()).FirstOrDefault();
}

Listing 7 – My GetAttributeOrDefault extension methods

All I’ve done here is use the DefaultIfEmpty Linq extension method and added an extra constraint to the generic class the make the extension method only work with attributes that have an explicit default Constructor.

And just for completeness here are all the generic extension methods together:

public static T GetAttributeOrDefault<T>(this MetaTable table) where T : Attribute, new()
{
    return table.Attributes.OfType<T>().DefaultIfEmpty(new T()).FirstOrDefault();
}

public static T GetAttribute<T>(this MetaTable table) where T : Attribute
{
    return table.Attributes.OfType<T>().FirstOrDefault();
}

public static T GetAttributeOrDefault<T>(this MetaColumn column) where T : Attribute, new()
{
    return column.Attributes.OfType<T>().DefaultIfEmpty(new T()).FirstOrDefault();
}

public static T GetAttribute<T>(this MetaColumn column) where T : Attribute
{
    return column.Attributes.OfType<T>().FirstOrDefault();
}

Listing 8 – All Generic Attributes together

The one in BOLD ITALIC is the original from Dynamic Data Futures project.

 

I think C# 3.0 is real neat HappyWizard

Monday, 19 January 2009

Dynamic Data – Cascading FieldTemplates

So before I dive into an explanation of this feature I’d better explain what I’m doing here. Firstly have a look at Figure 1.

Cascading ForeignKey_Edit like FieldTemplates

Figure 1 – Cascading ForeignKey_Edit like FieldTemplates

In Figure 1 you can see two DropDownLists Category and Products is this form you are only allowed to choose a product the matches the selected category. So what is required, is when Category is changes then the contents of  Product should be regenerated to be filtered by the chosen Category. I hope that make some sense.

The requirements for the above to work are:

  1. A way to get the parent DetailsView from inside a FieldTemplate.
  2. Some way of finding the FieldTemplate from the parent control tree of the dependant FileTemplate.
  3. Some way of getting the dependee FieldTemplate to fire an event on the dependant FieldTemplate when a change is made on dependee.
  4. An Attribute to tell the control which control to use as the dependee control.
Note: I am using the words dependant for the field that depends upon another for filtering and dependee for the field that is depended upon.

1. Getting the parent control

The first step would be to climb the parent tree using a generic extension method.

/// <summary>
/// Get a parent control of T from the parent control tree of the control
/// </summary>
/// <typeparam name="T">Control of type T</typeparam>
/// <param name="control">Control to search in for control type T</param>
/// <returns>The found control of type T or null if not found</returns>
public static T GetParent<T>(this Control control) where T : Control
{
    var parentControl = control.Parent;
    while (parentControl != null)
    {
        var formView = parentControl as T;
        if (formView != null)
            return formView;
        else
            parentControl = parentControl.Parent;
    }
    return null;
}

Listing 1 – Get Parent generic extension method

I decided that a generic method was required as I had not only the DetailsView but also the FormView to deal with. The logic is simple here all that happens is that the parent of the current cointrol is cast as the T type and if it is not null then we have the control we are looking for, if it is null then the loop continues and the parent of the parent is tested and so on until a match is found or a parent is null. This get’s us the hosting control of the type we want.

2. Finding the Dependee FieldTemplate

At first I thought I could use the handy extension method FindFieldTemplate but it always returned null. So I had to go my own way and came up with this:

/// <summary>
/// Get the control by searching recursively for it.
/// </summary>
/// <param name="Root">The control to start the search at.</param>
/// <param name="Id">The ID of the control to find</param>
/// <returns>The control the was found or NULL if not found</returns>
public static Control FindDynamicControlRecursive(this Control root, string dataField)
{
    var dc = root as DynamicControl;
    if (dc != null)
    {
        if (dc.DataField == dataField)
            return dc;
    }

    foreach (Control Ctl in root.Controls)
    {
        Control FoundCtl = FindDynamicControlRecursive(Ctl, dataField);

        if (FoundCtl != null)
            return FoundCtl;
    }
    return null;
}

Listing 2 – FindDynamicControlRecusive

This is based on a function I found here How to find a control when using Master Pages the change is simple all I do is cast the control as DynamicControl and then test for null if not null then I can test the DataField which I know had the column name in it.

This however only gets us the DynamicControl next we have to extract the field which is held in the DynamicControl’s Controls collection

// Get Parent FieldTemplate
var dependeeDynamicControl = detailsView.FindDynamicControlRecursive(dependeeColumn.ColumnName) as DynamicControl;

AdvancedFieldTemplate dependeeField = null;

// setup the selection event
if (dependeeDynamicControl != null)
    dependeeField = dependeeDynamicControl.Controls[0] as AdvancedFieldTemplate;

Listing 3 – Fragment: Extracting the FieldTEmplate from the DynamicControl

Now providing that the control we are after in in slot [0] of the DynamicControl’s Controls collection we are away smile_teeth.

Note: proved to be the most difficult due to differences between DetailsView and FormView which was what my original was working with.

3. Getting an event fired on the Dependant FieldTemplate when the Dependee’s DropDownList changes

Ok for this to work we need to expose the DropDownList’s OnSelectedIndexChanged event and also the SelectedValue property, here’s the code for that in Listing 4.

public override event EventHandler SelectionChanged
{
    add
    {
        DropDownList1.SelectedIndexChanged += value;
    }
    remove
    {
        DropDownList1.SelectedIndexChanged -= value;
    }
}

public override string SelectedValue
{
    get
    {
        return DropDownList1.SelectedValue;
    }
}

Listing 4 – Exposing the OnSelectedIndexChanged event and the SelectedValue property

This is fine but when I get a copy of the control from the earlier code I need to get access to this exposed event and property. So here's how we will do that we’ll create a class that inherits

/// <summary>
/// A class to add some extra features to the standard
/// FieldTemplateUserControl
/// </summary>
public class AdvancedFieldTemplate : FieldTemplateUserControl
{
    /// <summary>
    /// Handles the adding events to the drop down list
    /// </summary>
    public virtual event EventHandler SelectionChanged
    {
        add { }
        remove { }
    }

    /// <summary>
    /// Returns the selected value of the drop down list
    /// </summary>
    public virtual string SelectedValue
    {
        get
        {
            return null;
        }
    }
}

Listing 5 – AdvancedFieldTemplate class

As you can see from Listing 5 this class inherits the FieldTemplateUserControl class the a FieldTemplate inherits, so we inherit that and then on the custom FieldTemplates we want to cascade we set them to inherit the AdvancedFieldTemplate.

Next we need an event handler to handle the event passed to this the dependant control.

protected void SelectedIndexChanged(object sender, EventArgs e)
{
    var ddl = sender as DropDownList;
    if(ddl != null)
    PopulateListControl(DropDownList1, ddl.SelectedValue);
}

Listing 6 – Event handler

4. The Attribute

Attribute have been cover a lot on this site so I’m just going to post the code and comment a very little.

[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
public class DependeeColumnAttribute : Attribute
{
    public static DependeeColumnAttribute Default = new DependeeColumnAttribute();

    public DependeeColumnAttribute() : this("") { }

    public DependeeColumnAttribute(String columnName)
    {
        ColumnName = columnName;
    }

    public String ColumnName { get; set; }
}

Listing 7 – DependeeColumnAttribute

I could of course hard coded the dependee column in to each custom FieldTemplate but of course that assumes that every time you use the FieldTEmplate the dependee column will have the same name, which is not always the case.

5. Putting it All Together

Firstly we need the extension method that get us the dependee control:

public static AdvancedFieldTemplate GetDependeeField<T>(this Control control, MetaColumn column) where T : Control
{
    // get value of dev ddl (Community)
    var detailsView = control.GetParent<T>();
    // get parent column attribute
    var dependeeColumn = column.GetAttribute<DependeeColumnAttribute>();

    if (dependeeColumn != null)
    {
        // Get Parent FieldTemplate
        var dependeeDynamicControl = detailsView.FindDynamicControlRecursive(dependeeColumn.ColumnName) as DynamicControl;

        AdvancedFieldTemplate dependeeField = null;

        // setup the selection event
        if (dependeeDynamicControl != null)
            dependeeField = dependeeDynamicControl.Controls[0] as AdvancedFieldTemplate;

        return dependeeField;
    }
    return null;
}

Listing 7 – GetDependeeField

In Listing 7 we return the FieldTemplate extracted from the found DynamicControl  so in the Page_Load event we get the dependee control and assign it the event handler so that we can capture the SelectedIndexChanged event of the dependee DropDpownList.

protected void Page_Load(object sender, EventArgs e)
{
    if (DropDownList1.Items.Count == 0)
    {
        if (!Column.IsRequired)
            DropDownList1.Items.Add(new ListItem("[Not Set]", ""));

        PopulateListControl(DropDownList1, "");
    }

    // get dependee field
    var dependeeField = this.GetDependeeField<DetailsView>(Column);

    // add event handler if dependee exists
    if (dependeeField != null)
        dependeeField.SelectionChanged += SelectedIndexChanged;
}

Listing 8 – Page_Load event

The lines in BOLD ITALIC are the added lines and demonstrates the use of GetDependeeField extension method.

protected override void OnDataBinding(EventArgs e)
{
    base.OnDataBinding(e);

    if (Mode == DataBoundControlMode.Edit)
    {
        var dependeeField = this.GetDependeeField<DetailsView>(Column);
        if (dependeeField != null)
            PopulateListControl(DropDownList1, dependeeField.SelectedValue);

        string foreignkey = ForeignKeyColumn.GetForeignKeyString(Row);
        ListItem item = DropDownList1.Items.FindByValue(foreignkey);
        if (item != null)
        {
            DropDownList1.SelectedValue = foreignkey;
        }
    }
}

Listing 9 – OnDataBinding event handler

Again in BOLD ITALIC are line are are added.

An finally some metadata

[MetadataType(typeof(Order_DetailMD))]
public partial class Order_Detail
{
    public class Order_DetailMD
    {
        public object OrderID {get;set;}
        public object ProductID {get;set;}
        public object UnitPrice {get;set;}
        public object Quantity {get;set;}
        public object Discount {get;set;}
        public object CategoryID {get;set;}
        // EntityRefs
        [DependeeColumn("Category")]
        [UIHint("Product")]
        [ColumnOrder(2)]
        public object Product {get;set;}

        [UIHint("Category")]
        [ColumnOrder(1)]
        public object Category {get;set;}
    }
}

Listing 10 – Metadata

In the attached project I've also implemented some column ordering to arrange the columns in the order that makes sense if they cascade.

There are two part to this solution which I didn’t make clear:

  1. The properties that allow a the control’s OnSelectedIndexChanges event to be captured.
  2. Finding the dependee control and capturing it’s OnSelectedIndexChanges  event and reading it’s SelectedValue.

It must be noted that if both controls implement all the features there won’t be a problem but if you do it in only the dependant control the you won’t be able to capture the OnSelectedIndexChanges event of the dependee control or read it’s SelectedValue. So you must at least implement 1. in the dependee control and 2. in the dependant control.

I hope this clarifies things.

Setting up Northwind to work with the sample

Here’s a diagram showing the changes I made to Northwind to facilitate this example (as there were no columns that fitted this scenario).

Diagram of changes to Order_Details table

Figure 2 – Diagram of changes to Order_Details table

Added Relationship in the Model

Figure 3 – Added Relationship in the Model

And here’s the T-SQL to update the CategoryID column in Order_Details once you've added it.

USE [Northwind]
GO
UPDATE [Order Details] 
SET  [Order Details].[CategoryID] = p.[CategoryID]
FROM [Products] p 
WHERE p.[ProductID] = [Order Details].[ProductID]

SELECT * FROM [Order Details]

Listing 11 – SQL to update Oder_Details

Note: This listing is just to make changes to Northwind so it fits this scenario.

And that's it HappyWizard

Oh and the Download

Wednesday, 14 January 2009

Dynamic Data – Default Value in ForeignKey Edit FieldTemplate

This article simply show how to get the DefaultValueAttribute to affect the ForeignKey Edit FieldTemplate.

So the first thing is to set some Metadata up.

[MetadataType(typeof(Order_DetailMD))]
public partial class Order_Detail
{
    public class Order_DetailMD
    {
        [DefaultValue("15")] // Genen Shouyu
        public object Product { get; set; }
    }
}

Figure 1 – Metadata

Now all we need to do is add a small amount of code to the OnDataBinding event handler of the ForeignKey Edit FieldTemplate.

protected override void OnDataBinding(EventArgs e)
{
    base.OnDataBinding(e);

    if (Mode == DataBoundControlMode.Edit)
    {
        string foreignkey = ForeignKeyColumn.GetForeignKeyString(Row);
        ListItem item = DropDownList1.Items.FindByValue(foreignkey);
        if (item != null)
        {
            DropDownList1.SelectedValue = foreignkey;
        }
    }
    // set the default value
    if (Mode == DataBoundControlMode.Insert)
    {
        var defaultValue = Column.Attributes.OfType<DefaultValueAttribute>().FirstOrDefault();
        if (defaultValue != null)
        {
            ListItem item = DropDownList1.Items.FindByValue(defaultValue.Value.ToString());
            if (item != null)
                DropDownList1.SelectedValue = defaultValue.Value.ToString();
        }
    }
}

Figure 2 – Setting the default value

Here the line in BOLD ITALIC font are to be added.

Here what happens is when the template is in Insert mode the an attempt is made to get the DefaultValueAttribute from the Column property, if this is not null then the value is retrieved and then an attempt is made at finding it in the DropDownList1 . If it is found then the drop down list is set to it.

I hope that's not too much explanation.