Tuesday, 22 July 2008

Dynamic Data and Field Templates - Your First FieldTemplate *** UPDATED ***

  1. The Anatomy of a FieldTemplate.
  2. Your First FieldTemplate.
  3. An Advanced FieldTemplate.
  4. A Second Advanced FieldTemplate.
  5. An Advanced FieldTemplate with a GridView.
  6. An Advanced FieldTemplate with a DetailsView.
  7. An Advanced FieldTemplate with a GridView/DetailsView Project.

Your First FieldTemplate

We’ll create a simple FieldTemplate that displays an enumeration as RadioButtonList.

Create Database and Table

We will need a database to run against so in Solution Explorer create a new ASP.Net folder App_Data in the root of the website and create a new Database in there called Animals. Now create a new table called Pets with the following columns.

Pets Table

Figure 1 – Pets Table

Create the Enumeration Type

/// <summary>
/// My not very exhaustive set of animals
/// </summary>
public enum AnimalType
{
    Budgie,
    Cat,
    Dog,
    Gerbil,
    Hamster,
    Lizard,
    Mouse,
    Parrot,
    Rat,
    Snake
}
public enum Gender // Added
{
    Male = 0,
    Female = 1
}

Listing 1 – AnimalType & Gender ***UPDATED***

Create the Model

Add a new Linq to SQL to the App_Code folder and and call it Animals. Copy the Pets table to it. Now we need to set the type of the Pets.Type to AnimalTypes and also do the same for the sex column setting its type to Gender.

Setting the Pets.Type to AnimalType enum

Figure 2 - Setting the Pets.Type to the AnimalType enum

Save the and Close the Model.

In the same class file as the enums add your metadata.

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

[MetadataType(typeof(PetMetaData))]
public partial class Pet
{
    public class PetMetaData
    {
        [UIHint("Enumeration")]
        public object Animal { get; set; }

        [UIHint("Enumeration")]
        public object sex { get; set; }
    }
}

Listing 2 – Pet metadata ***UPDATED***

Creating a New FieldTemplate

Expand the DynamicData folder and right-click on the FieldTemplates folder, choose Add New Item… and choose Web User Control from the list of items. Give it the name Enumeration_Edit.ascx

Change the base class that the user control inherits from System.Web.UI.UserControl to System.Web.DynamicData.FieldTemplateUserControl. and a new using System.Web.DynamicData. Change the class name to Enumeration_EditField from DynamicData_FieldTemplates_Enumeration_Edit in both the ascx and code behind files.

<%@ Control Language="C#"
    AutoEventWireup="true"
    CodeFile="Enumeration.ascx.cs"
    Inherits="Enumeration_EditField" %>

Listing 3 – Enumeration_Edit.ascx

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

public partial class Enumeration_EditField : System.Web.DynamicData.FieldTemplateUserControl
{

}

Listing 4 – Enumeration_Edit.ascx.cs code behind

The UserControl is now ready for us to add our code to make it show a DropDownList control populated with AnimalTypes.

Building Our Custom FieldTemplate

Add a DropDownList to the UserControl leave it as DropDownList1 and switch to the code behind.

In the code behind body of the class type protected override On and as you type On you will notice the auto-complete intellisense offer you some options use the arrow keys to select OnDataBinding and hit the tab key (but not too hard or you will break your keyboard :D).

‌auto-complete intellisense

Figure 3 - Auto-Complete Intellisense

Now you should have:

public partial class Enumeration_EditField : System.Web.DynamicData.FieldTemplateUserControl
{
    protected override void OnDataBinding(EventArgs e)
    {
        base.OnDataBinding(e);
    }
}

Listing 5 – OnDataBinding event handler

I mentioned the above because I think it’s really neat smile_teeth.

Note: When overriding OnDataBinding be sure to call the base class's OnDataBinding method so that registered delegates receive the event.

After the base.OnDataBinding(e); line we need to add our code to populate the DropDownList.

<%@ Control Language="C#"
    AutoEventWireup="true"
    CodeFile="Enumeration_Edit.ascx.cs"
    Inherits="Enumeration_EditField" %>
   
<asp:DropDownList
    runat="server"
    ID="DropDownList1"
    CssClass="droplist"
    OnDataBound="DropDownList1_DataBound">
</asp:DropDownList>

Listing 6 – Finished Enumeration_Edit.ascx note the OnDataBound

using System;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class Enumeration_EditField : System.Web.DynamicData.FieldTemplateUserControl
{
    protected override void OnDataBinding(EventArgs e)
    {
        // When overriding OnDataBinding be sure to call the base class's
        // OnDataBinding method so that registered delegates receive the event.
        base.OnDataBinding(e);

        // get a data bindable list of permissions for the DDL
        var enumList = Enum.GetValues(Column.ColumnType);

        DropDownList1.DataSource = enumList;
        DropDownList1.DataBind();
    }

    protected override void ExtractValues(System.Collections.Specialized.IOrderedDictionary dictionary)
    {
        dictionary[Column.Name] = ConvertEditedValue(DropDownList1.SelectedValue);
    }

    protected void DropDownList1_DataBound(object sender, EventArgs e)
    {
        // make sure we are in edit mode
        if (Mode == DataBoundControlMode.Edit)
        {
            // try to get an item in the list that matched the FieldValueString
            ListItem item = DropDownList1.Items.FindByValue(FieldValueString);
            if (item != null)
            {
                // if we get the value set the drop down list to FieldValueString
                DropDownList1.SelectedValue = FieldValueString;
            }
        }
    }
    // This is one of those needed things I think it allows
    // access to the actual control through Controls property
    public override Control DataControl
    {
        get
        {
            return DropDownList1;
        }
    }
}

Listing 7 – Finished Enumeration_Edit.ascx.cs

Now we need an Enumeration.ascx file for this FieldTemplate all we need is to create a copy of the Text.ascx FieldTemplate control.

<%@ Control
    Language="C#"
    CodeFile="Enumeration.ascx.cs"
    Inherits="EnumerationField" %>

<asp:Literal
    runat="server"
    ID="Literal1"
    Text="<%# FieldValueString %>" />

Listing 8 – Enumeration.ascx

using System.Web.UI;

public partial class EnumerationField : System.Web.DynamicData.FieldTemplateUserControl
{
    public override Control DataControl
    {
        get
        {
            return Literal1;
        }
    }
}
Listing 9 – Enumeration.ascx.cs

This blatant copy if the Text.ascx FieldTemplate is due to the fact that our enumeration types don’t have a direct conversion to text and so don’t automatically drop through to the Text.ascx FieldTemplate.

Now we can edit and view our data.

Note: And yes I know that there is an Enumeration control in the Dynamic Data Futures and it doesn’t need the UIHint, but I just wanted something that was not too trivial :D

26 comments:

Anonymous said...

Great Post... Would this work the same way with Entity Framework ?

Steve said...

I don't see why not i've not done anything that is excluded by the EF
Steve

maher said...

Would you please post a ZIP of the project thanks

Anonymous said...

Once again, Stephen pulls through! Your blog on all things Dynamic Data has saved me countless hours of pulling my hair out! THANK YOU!

Anonymous said...

A few things:
Even when changing the database field "Type" to a type of Animal, the properties in the code-behind still remain as Integers, at least when using VB.NET. So I had to find:
private _Type As Integer
and change to:
private _Type As Animal
and correct the properties as necessary (VB.NET doesn't have the nice generic { get; set; } functionality)

Even so, the dropdown I get has only the enumeration name, not the value. I.E.
<option value="Budgie">Budgie</option>
<option value="Cat">Cat</option>
<option value="Dog">Dog</option>
I'm not sure, but I think because the value isn't an integer, it is blowing up for me when I try to update it. Should that be the case, or when it saves is it magically getting the value for the enum and saving it?
The error I get is:
"Microsoft JScript runtime error: Sys.WebForms.PageRequestManagerServerErrorException: Row not found or changed."

What am I doing wrong?

Steve said...

~I'm really sorry but VB.Net makes my head ache, I have had no problems with this setting the type in the Designer.

Steve :D

Thiago said...

Please....
Anyone know how to create a new field template for EntityModel??? In LinqModel seens easy.... but in EntityModel? Is it possible? How? I create my field values, but i cant use it by puting uihint on property in design.cs class.

Steve said...

Hi Thiago, send me an e-mail and we can have a chat about your requirements and see what can be done.

Steve :D

Kel said...

Do you have a simple sample like above that you could share? I've been attempting to get this working for the last couple days. I keep getting this error:

Type provided must be an Enum.
Parameter name: enumType

I've set the Type of the column in question in the data model and followed the rest of your example. Looking at a sample that works, may help me out.

Thanks,
Kel

Steve said...

Hi Kel, I can provid the sample if you send me an e-mail to the e-mail address I have on this blog.

Steve :D

Nam said...

Hi

I've got the same error Type provided must be an Enum.
Parameter name: enumType

In Linq to SQL datacontext, i do the same as above with Gender and AnimalType and leave Server Data Type as Int not Null

Nam Vo.

Steve said...

Hi Nam, this will only work with Linq to SQL and you must have the Enum in the same namespace as the dbml or fully qualify it.

Steve

Gareth Thomas Hill said...

Hi Steve, thanks for the excellent Dynamic Data resources you are providing here - saved me so many headaches. I have just implemented this Enum FieldTemplate, which works great. I was wondering however how to implement something similar to an Enum but permits spaces i.e. the Animal example works ok, but if I was wanting to set the Status of an Order to 'Awaiting Processing' rather than 'AwaitingProcessing'?

Steve said...

HiGareth, try this:
// get a data bindable list of permissions for the DDL
var enumList = Enum.GetValues(enumType);

foreach (var enumItem in enumList)
{
listControl.Items.Add(new ListItem()
{
Text = enumItem.ToString().ToTitleFromPascal(),
Value = enumItem.ToString()
});
}
and here is the extension method ToTitleFromPascal:
public static String ToTitleFromPascal(this String s)
{
// remove name space
String s0 = Regex.Replace(s, "(.*\\.)(.*)", "$2");
// add space before Capital letter
String s1 = Regex.Replace(s0, "[A-Z]", " $&");

// replace '_' with space
String s2 = Regex.Replace(s1, "[_]", " ");

// replace double space with single space
String s3 = Regex.Replace(s2, " ", " ");

// remove and double capitals with inserted space
String s4 = Regex.Replace(s3, "(?[A-Z])\\s(?[A-Z])", "${before}${after}");

// remove and double capitals with inserted space
String sf = Regex.Replace(s4, "^\\s", "");

// force title case i.e. 'this is a title' becomes 'This Is A Title'
return sf.ToTitleCase();
}

public static String ToTitleCase(this String text)
{
StringBuilder sb = new StringBuilder();

for (int i = 0; i < text.Length; i++)
{
if (i > 0)
{
if (text.Substring(i - 1, 1) == " " ||
text.Substring(i - 1, 1) == "\t" ||
text.Substring(i - 1, 1) == "/")
sb.Append(text.Substring(i, 1).ToString().ToUpper());
else
sb.Append(text.Substring(i, 1).ToString().ToLower());
}
else
sb.Append(text.Substring(i, 1).ToString().ToUpper());
}

return sb.ToString();
}

public static String ToSentenceCase(this String text)
{
return text.Substring(0, 1).ToUpper() + text.Substring(1, text.Length - 1).ToLower();
}

public static String GetDisplayFormat(this Type type)
{
string defaultFormat = "{0}";

if (type == typeof(DateTime) || type == typeof(Nullable))
{
defaultFormat = "{0:d}";
}
else if (type == typeof(decimal) || type == typeof(Nullable))
{
defaultFormat = "{0:c}";
}

return defaultFormat;
}

Hope this helps:

Anonymous said...

thanks for the post ... I tried it and it works great for inserting, but it ends up an error on editing and deleting. :-(

Please help.

Steve said...

Hi there I'll look into that as soon as I get a chance, I just got back of vacation and I have a major backlog to attend to :)

Steve

giribabu said...

Hi Steve, i want to create RadioButtonList in FieldTemplate, can you tell me how to do that, data should be retrieved from Database not from Enum

Steve said...

Hi Giribabu, can you email me directly e-mail is at the top of the page.

Steve

giribabu said...

Hi Steave, I had sent you an e-mail from my company e-mail id.

giribabu said...

I want to know the Positives and Negatives of ASP.NET Web Forms with Dynamics.

giribabu said...

After creating this sample i inserted values from backend and executed the sample its showing with 3coloumns from DB [ NAME, Gender & Age] when i try to edit that info then error is comming

Type provided must be an Enum.
Parameter name: enumType

even i checked the namespace everything is fine.
Created this sample in VS2008

YOU MENTIONED IN TITLE
"We’ll create a simple FieldTemplate that displays an enumeration as RadioButtonList."

i didn't found any RADIOBUTTONLIST control is used in the code.

Steve said...

Hi giribabu, this is an old sample in VS2010 and .Net 4 DD already has a way of doing Enum field templates this way is out of date.

Steve

Adi said...

Hi Steve,

i tried this in Entity Framework but doesn't seems to work. Do you know the work around fix for entity framework?

Thxu.

Steve said...

Hi Adi, you can't do it this way in EF, what version of DD are you running VS20008 & .Net 3.5 SP1 or VS2010 & .Net 4?

Steve

Anonymous said...

VB.NET has had the 'generic get set' properties for quite awhile.

It's termed the explicit property setting. If you declare the property in an interface, it has to be explicit. if you declare in the class, you type 'End Property' and than type 'Get' or 'Set' within the property declaration. You'll end up seeing something like this.

Private _Name as DataType
Property PropertyName As DataType
Get
return me._Name 'Private Variable
End Get
Set(value as DataType)
me._Name = value
End Set
End Property

Though, I took the liberty of including the Private variable declaration, return statement, and value assignment.

Jai V said...

How to apply below two query in my DD using query extender

select regno, count(regno) as cnt INTO #TEMP1 from MainTable
group by regno having count(regno) >= 2

select * from MainTable where regno in (select regno from #TEMP1) order by regno