Friday, 6 June 2008

DynamicData Default Values in ForegnKey_Edit & other FieldTemplates

Metadata method

Add a new class the your website called DefaultValueAttribute with the code below.

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

[AttributeUsage(AttributeTargets.Property)]
public class DefaultValueAttribute : System.Attribute
{
    // As implemented, this identifier is merely the Type of the attribute.
    // However, it is intended that the unique identifier be used to identify 
    // two attributes of the same type.
    public override object TypeId { get { return this; } }

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="value">String</param>
    public DefaultValueAttribute(String value)
    {
        this._value = value;
    }

    private String _value;
    public String Value
    {
        get { return this._value; }
    }
}

Listing 1 - DefaultValueAttribute

Add your metadata to the appropriate FK fields

[MetadataType(typeof(OrderMetadata))]
public partial class Order
{
}

public class OrderMetadata
{
    [DefaultValue("3")]
    public Object Shipper { get; set; }
}

Listing 2 - Metadata

Open your DynamicData FieldTemplates folder and edit the ForeigKey_Edit.aspx, and add an OnDataBound="DropDownList1_DataBound" entry as in Listing 3

<asp:DropDownList 
    ID="DropDownList1" 
    runat="server" 
    CssClass="droplist" 
    OnDataBound="DropDownList1_DataBound">
</asp:DropDownList>

Listing 3 - Adding the OnDataBound event handler

Next in the code behind file add the DropDownList1_DataBound event handler Listing 4a

protected void DropDownList1_DataBound(object sender, EventArgs e)
{
    // value gotten via a query string as below 
    //string value = Request.QueryString[Column.Name];

    // value gotten via metadata 
    string value = Column.GetDefaultValue();

    if (this.Mode == DataBoundControlMode.Insert && !string.IsNullOrEmpty(value))
    {
        ListItem item = this.DropDownList1.Items.FindByValue(value);
        if (item != null)
        {
            item.Selected = true;
        }
    }
}

Listing 4a - The OnDataBound event handler Metadata version

In this event handler Listing 4a the default value is aquired via the metadata from Listing 2.

Note: the this.Mode == DataBoundControlMode.Insert which makes sure that the auto-fill of default values only occurs for Inserts.

Query String Method

Open your DynamicData FieldTemplates folder and edit the ForeigKey_Edit.aspx, and add an OnDataBound="DropDownList1_DataBound" entry as in Listing 3, next in the code behind file add the DropDownList1_DataBound event handler Listing 4b see Figure 1 for the query string.

protected void DropDownList1_DataBound(object sender, EventArgs e)
{
    // value gotten via a query string as below 
    string value = Request.QueryString[Column.Name];

    //// value gotten via metadata 
    //string value = Column.GetDefaultValue();

    if (this.Mode == DataBoundControlMode.Insert && !string.IsNullOrEmpty(value))
    {
        ListItem item = this.DropDownList1.Items.FindByValue(value);
        if (item != null)
        {
            item.Selected = true;
        }
    }
}

Listing 4b - The OnDataBound event handler QueryString version

DefaultValue via QueryString

Figure 1 - Query String

In Listing 4b event handler all I'm doing is checking for a value passed in the query string see Figure 1 as in this POST from the ASP.Net DynamicData Forum

Other FieldTemplates

This same pattern can be applied to all FieldTemplates for adding default values see Listing 5 which is added to the Text_Edit.ascx FieldTemplate.

protected void TextBox1_PreRender(object sender, EventArgs e)
{
    // Adding a default value to a textbox
    // value aquired via metadata 
    string value = Column.GetDefaultValue();
    if (this.Mode == DataBoundControlMode.Insert && !string.IsNullOrEmpty(value))
    {
        TextBox1.Text = value;
    }
}

Listing 5

Again the value could be acquired either via metadata or query string Listing 6 would replace the line that contains Column.GetdefaultValue().

// value acquired via query string
string value = Request.QueryString[Column.Name];

Listing 6

My feeling on this is you could do all this in the code behind of any of the pages that perform Inserts, on the OnPreRender event however if you put this code in more than one Insert page then you create a code maintenance issue. You will need to make changes in several different places, plus each extra Insert page you create will need this same code adding. However adding this small amount of code to each FieldTemplate you wish to have auto-fill of default values will reduce then amount of maintenance etc.

10 comments:

Anonymous said...

Thank you so much for this brilliant listing. Allowed me to do exactly what I needed. Ive used your blog for lots of dynamic data tid bits and it continues to be of great use! Keep up the good work!

Anonymous said...

nice, worked like a charm

Anonymous said...

This is fantastic and works great!
Now my issue is that I need to get this information passed back to the list page after the user clicks Insert or Cancel. It would be great if I could just carry the querystring forward, but that doesn't work. The list page expects query string keys of "Table.ID=X" or on lists with multiple filters "TableID=X". I wrote a very ugly hack to accomplish this but if you had any ideas to point me in a better direction that would be much appreciated. Thanks!

Stephen J. Naughton said...

Hi Anon :) if you send me an e-mail direct (my address is at the top right of the page) we can have a chat and see what is the best way to do what you want.

Steve :D

tali said...

Hi,
page_init in VS10 looks different and I tried it in a different ways but or it is avoiding the change or I have an error:
"Exception Details: System.ArgumentNullException: Value cannot be null.
Parameter name: dictionary"

Do you know What I should do ?

Stephen J. Naughton said...

Hi Tali, move the code to the pages Pre-Render event.

Steve :D

redevolve said...

Hi Steve, I have an issue with the DynamicHyperLink's URL that was generated using the wrong column name.
In my metadata, I have my Employee class plus, a property column [int CompanyId] and another [Company Company] property.
The DynamicHyperLink generates a URL with the Query Parameter, CompanyId=xx instead of Company=xx. However, the ForeignKey field template's Column.Name is 'Company'.

Stephen J. Naughton said...

Hi redevolve, this is correct as the FK column in the DB would be CompanyId and the Navigation Property is Company.

This is easy to see in Entity Framework but not so easy in Linq to SQL.

Steve

redevolve said...

Thanks, Steve. Do you think I should intercept the NavigateURL property to have it to generate the Navigation property instead of the FK column?

Do you have any suggestion on what's the correct way to solve this in order to have the Company auto-selected in the 'add new employee' page?

Thanks.

Stephen J. Naughton said...

can you e-mail me direct my e-mail is in the top right of this site.

Steve