Thursday 4 September 2008

Dynamic Data Compound Column *** UPDATED 2008/11/08 ***

I answered this Re: Display multiple fields in custom FieldTemplateUserControl question in the Dynamic Data forum whilst away and didn’t have the time to do a full write up of it so here it is.

What the question was:

Adult wrote “I want to display multiple fields in one custom FieldTemplateUserControl. For example i have property X and Y. Currently they show on separate row when i edit or insert.
Do i need to create new property to return anonymous class with just this 2 property and in custom control,read values.What about inserting? Anyone done it?”

So here’s the partial class I added to deal with the new property Coordinate:

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

[MetadataType(typeof(TestPointMD))]
public partial class TestPoint
{
    [ScaffoldColumn(true)]
    public Point Coordinate
    {
        get
        {
            return new Point(this.X, this.Y);
        }
        set
        {
            this.X = value.X;
            this.Y = value.Y;
        }
    }

    public class TestPointMD
    {
        public object Id { get; set; }
        public object Name { get; set; }
        [ScaffoldColumn(false)]
        public object X { get; set; }
        [ScaffoldColumn(false)]
        public object Y { get; set; }
    }
}

[Serializable]
public class Point
{
    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }

    public int X { get; set; }
    public int Y { get; set; }

    public String ToString()
    {
        return X + ", " + Y;
    }
}

Listing 1 – Partial methods for my model

Listing 1 consists of three parts:

  1. The partial class with the Coordinate property added.
  2. The Metadata class setting the ScaffoldColumn attribute to false for the individual column we don’t want to show.
  3. And last the Point class that we are using in the Coordinate property Note the Serializable attribute which is needed for the FieldTemplate to be able to return values.
Note: The lack of UIHint in the metadata or partial class as we are going to create a FieldTemplate called Point and Point_Edit.

The next part are the FieldTemplates Point and Point_Edit.

<%@ Control 
    Language="C#" 
    CodeFile="Point.ascx.cs" 
    Inherits="PointField" %>

<asp:Literal 
    runat="server" 
    ID="Literal1" />

Listing 2 – Point.ascx

using System;
using System.Web.UI;

public partial class PointField : System.Web.DynamicData.FieldTemplateUserControl
{
    public override Control DataControl
    {
        get
        {
            return Literal1;
        }
    }

    protected override void OnDataBinding(EventArgs e)
    {
        var p = FieldValue as Point;
        Literal1.Text = p.ToString();
        base.OnDataBinding(e);
    }
}

Listing 3 – Point.ascx.cs

Listings 2 and 3 are based on the Text.ascx FieldTemplate the main difference is that the value of the literal is set via the Point class’s ToString() method.

<%@ Control 
    Language="C#" 
    CodeFile="Point_Edit.ascx.cs" 
    Inherits="Point_EditField" %>

<asp:TextBox 
    ID="TextBoxX" 
    runat="server" 
    CssClass="droplist">
</asp:TextBox>
<asp:TextBox 
    ID="TextBoxY" 
    runat="server" 
    CssClass="droplist">
</asp:TextBox>

Listing 4 – Point_Edit.ascx

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

public partial class Point_EditField : System.Web.DynamicData.FieldTemplateUserControl
{
    protected void Page_Load(object sender, EventArgs e)
    {
        TextBoxX.MaxLength = Column.MaxLength;
        TextBoxY.MaxLength = Column.MaxLength;
        if (Column.MaxLength < 20)
        {
            TextBoxX.Columns = Column.MaxLength;
            TextBoxY.Columns = Column.MaxLength;
        }
        TextBoxX.ToolTip = Column.Description;
        TextBoxY.ToolTip = Column.Description;
    }

    protected override void OnDataBinding(EventArgs e)
    {
        var p = FieldValue as Point;
        if (p != null)
        {
            TextBoxX.Text = p.X.ToString();
            TextBoxY.Text = p.Y.ToString();
        }
        base.OnDataBinding(e);
    }

    protected override void ExtractValues(IOrderedDictionary dictionary)
    {
        int x;
        int y;
        int.TryParse(TextBoxX.Text, out x);
        int.TryParse(TextBoxY.Text, out y);
        var p = new Point(x, y);
        dictionary[Column.Name] = p;
    }

    public override Control DataControl
    {
        get
        {
            return TextBoxX;
        }
    }
}

Listing 5 – Point_Edit.ascx.cs

Here in Listings 4 and 5 we create again from the Text_Edit FieldTemplate our Point_Edit FieldTemplate everything in this apart from the ExtractValues method is the same as the FieldTemplate it is based on but just doubled up on the two TextBoxes we have used. In the ExtractValues method we create and populate a new instance of the Point class set it’s X and Y properties and then put it in the dictionary passed into the method.

Point FieldTemplate in action

Figure 1 – Point FieldTemplate in action

This seems to work really well for this type of compound property. At a later date I would like to try this with Point being a User Defended Type in SQL Server 2005/2008, this would get rid of the need for the compound property and would mean only a new FieldTemplate was required.

UPDATED: Issue re: this post Grouping Columns into class to pass into FieldTemplate sporadic problem on the ASP.Net Dynamic Data Forum

The combined property does get assigned, but when the update check is on, it ends up being overwritten by the original values of the individual fields.  Basically, pretty random results.  I think it’s best to avoid using custom properties, especially since they’re not supported in EF.

See Scott’s sample http://www.codeplex.com/aspnet/Release/ProjectReleases.aspx?ReleaseId=14473.
UPDATED: See Rick’s post on on a simple two column display here Improving the FK field display: Showing two fields in Foreign Key columns with EF not quite a clever at this can be but if you want to display two column compunded together sutch as FirstName and LastName in the FK this this ones for you.

2 comments:

Anonymous said...

Just wanted to leave a comment.

Thank you, this is exactly what I needed. I just spent 2 days looking for an example implementation like this...Microsoft's documentation wasn't nearly as clear as this.

Stephen J. Naughton said...

Hi There I now use computed columns for this sort of thing :)

Steve