Showing posts with label FieldTemplate. Show all posts
Showing posts with label FieldTemplate. Show all posts

Wednesday, 13 July 2011

Monday, 11 January 2010

Required Field Highlighting in Dynamic Data Field Templates

I’ve been using this for some time and thought I should post a small article on it.

RequiredField RequiredFKField

 Figure 1 – Highlighted fields

The idea is to give the user some prior knowledge the some field are required and these are have their backgrounds tinted yellow, so lets look at the code for this:

protected void Page_Load(object sender, EventArgs e)
{
    TextBox1.ToolTip = Column.Description;

    SetUpValidator(RequiredFieldValidator1);
    SetUpValidator(RegularExpressionValidator1);
    SetUpValidator(DynamicValidator1);

    // add 'validationRequired' class to the TextBox
    if (Column.IsRequired)
        TextBox1.CssClass += " validationRequired";
}

Listing 1 – the highlighter code.

It’s the last if statement that does the work if the the column is required then the “validationRequired” class is added to the fields class.

Note: All you need to do is add this code to each Edit field template that has a required field validator.
/* required field class */
.validationRequired
{
    background-color: #FFFEBD;
}

Listing 2 – required field css class

Making ForeignKey Field Template have Required Validation

The next thing I like is to add validation the FK field templates so there is a select [type] as the first entry in the FK field if the field is required and validation is enabled.

<asp:DropDownList 
    ID="DropDownList1" 
    runat="server" 
    CssClass="droplist">
</asp:DropDownList>
<asp:RequiredFieldValidator 
    runat="server" 
    ID="RequiredFieldValidator1" 
    CssClass="droplist" 
    ControlToValidate="DropDownList1" 
    Display="Dynamic" 
    Enabled="false" />
<asp:DynamicValidator 
    runat="server" 
    ID="DynamicValidator1" 
    CssClass="droplist" 
    ControlToValidate="DropDownList1" 
    Display="Dynamic" />

Listing 3 – ForeignKey_Edit.aspx

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

        PopulateListControl(DropDownList1);
    }
    DropDownList1.ToolTip = Column.Description;

    SetUpValidator(RequiredFieldValidator1);
    SetUpValidator(DynamicValidator1);

    // add 'validationRequired' class to the TextBox
    if (Column.IsRequired)
        DropDownList1.CssClass += " validationRequired";
}

Listing 4 – ForeignKey_Edit.aspx.cs

In Listing 3 I have added the standard RequiredFieldValidator and DynamicValidator to the ForeignKet_Edit field template and in Listing 4 I have added the setup for both validators and also changed the if statement that adds the “[Not Set]” entry to the dropdown list; this is make sure the user picks a value when the column is required and it is in insert mode as opposed to just accepting the default value.

Like I said only a short article but neatOpen-mouthed

Thursday, 24 December 2009

A Popup Insert control for Dynamic Data (UPDATED)

Well the Popup Control can be used for used for any Web Forms application but for this sample we will use it to give ForeignKey_Edit FieldTemplates a popup Insert page. This will allow you to insert a new item in to the ForeignKeyColumns ParentTable and then set the ForeignKey DropdownList to the new item.

First of all since we are going to be creating a couple of Server Controls may I make a recommendation for Nikhil Kothari and Vandana Datye’s book:

Developing Microsoft ASP.NET Server Controls and Components

Developing Microsoft ASP.NET Server Controls and Components

I found the book very thorough and detailed.

The Popup Controls

I found several articles that offered a popup solution with a return value(s) but decided that none of then offered the solution I wanted for using with Dynamic Data. This is what I wanted:

  • A simple control to drop onto the page that would show a button.
  • Send a return value back to the page that created the popup.
  • and then act on the value returned.

Seems simple enough doesn't it but as always if someone else has done it they want money for it Surprised so I thought I’ll build one and here we go.

Note: I don’t plan to say what is already said in Nikhil and Vandana’s book so this won’t be a Server Controls Tutorial just an explanation of what these controls do (after all the book does it very well).

First of all lets explain what we want to build:

  • PopupButton control
  • PopupClient control
  • JavaScript to do the popup
function NAC_OpenPopup(popupUrl, returnParameter, returnControl, title, width, height) {
    ///<summary>Open a new window in the centre of the screen using the passed in URL.</summary>
    ///<field name="popupUrl" type="string">The popup window's URL as a string.</field>
    ///<field name="returnParameter" type="string">The QueryString Field.</field>
    ///<field name="returnControl" type="string">The name of the control to pass in the url.</field>
    ///<field name="title" type="string">The name of the window as a string.</field>
    ///<field name="width" type="Number" integer="true">The width of the the window as an int.</field>
    ///<field name="height" type="Number" integer="true">The height of the the window as an int.</field>
    
    // pass the id of the return control
    popupUrl = popupUrl + "?" + returnParameter + "=" + returnControl;
    
    // calculate the x and y positions to center the popup.
    var x = 0;
    var y = 0;
    x = (screen.availWidth - 12 - width) / 2;
    y = (screen.availHeight - 48 - height) / 2;

    // setup the features
    var features =
        "'screenX=" + x +
        ",screenY=" + y +
        ",width=" + width +
        ",height=" + height +
        ",top=" + y +
        ",left=" + x +
        ",status=no" +
        ",resizable=no" +
        ",scrollbars=yes" +
        ",toolbar=no" +
        ",location=no" +
        ",modal=yes" + 
        ",menubar=no'";
    
    // open the new window
    var NewWindow = window.open(popupUrl, title, features);
    // set focus to the new window
    NewWindow.focus();
    // return the new window so we can detect the close
    return NewWindow;
}

Listing 1 – NAC_OpenPopup JavaScript function

function NAC_PopupButton1func() {
    // get new popup window
    var NAC_PopupButton1Var = NAC_OpenPopup(
        'Page.aspx',
        'ReturnValue',
        'PopupButton1',
        'Popup',
        '600',
        '600');
    // wait till windows closes
    while (!NAC_PopupButton1Var.closed) { }
    // do postback
    __doPostBack('PopupButton1', '');
}

Listing 2 – This JavaScript function is generated on the page for each PopupButton

// get the return value saved in client
var value = document.getElementById("PopupClient1").value;
// if the value is not empty return it to calling page
if (value != "") {
    // pass value to returnControl
    window.opener.document.getElementById("PopupButton1").value = value
    // call postback on parent window
    window.opener.__doPostBack('PopupButton1', '');
    // close the window
    window.close();
}

Listing 3 – This JavaScript function is generated on the page for each PopupClient

So this is how the these scripts work together; we call the NAC_OpenPopup function from the PopupButton control when the button is clicked and then wait for the popped up window to close and then do a postback to the OnTextChanged event handler. Meanwhile in the client window we have hooked code on the form changed to save the value to the calling window’s PopupButton and then close the window.

using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Security.Permissions;
using System.Text;
using System.Web;
using System.Web.UI;

namespace NotAClue.Web.UI.WebControls
{
    [AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal)]
    [AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    [ParseChildren(true)]
    [PersistChildren(false)]
    [DefaultEvent("TextChanged")]
    [ToolboxData("<{0}:PopupButton runat=\"server\"></{0}:PopupButton>")]
    public class PopupButton : System.Web.UI.WebControls.Button, IPostBackDataHandler
    {
        private String linkButtonId;

        private static readonly object EventTextChanged = new object();

        /// <summary>
        /// Occurs when [text changed].
        /// </summary>
        [Category("Action")]
        [Description("Raised when text changes")]
        public event EventHandler TextChanged
        {
            add { Events.AddHandler(EventTextChanged, value); }
            remove { Events.RemoveHandler(EventTextChanged, value); }
        }

        /// <summary>
        /// Gets or sets the width of the window.
        /// </summary>
        /// <value>The width of the window.</value>
        [Browsable(true)]
        [Bindable(true)]
        [Localizable(false)]
        [Category("Appearance")]
        [DefaultValue(300)]
        public int WindowWidth { get; set; }

        /// <summary>
        /// Gets or sets the height of the window.
        /// </summary>
        /// <value>The height of the window.</value>
        [Browsable(true)]
        [Bindable(true)]
        [Localizable(false)]
        [Category("Appearance")]
        [DefaultValue(300)]
        public int WindowHeight { get; set; }

        /// <summary>
        /// Gets or sets the popup window's title.
        /// </summary>
        /// <value>The popup window title.</value>
        [Browsable(true)]
        [Bindable(true)]
        [Localizable(false)]
        [Category("Appearance")]
        public String Title { get; set; }

        /// <summary>
        /// Gets or sets the values.
        /// </summary>
        /// <value>The values.</value>
        [Browsable(true)]
        [Bindable(true)]
        [Localizable(false)]
        [Category("Appearance")]
        [Description("The values passed back from the popup.")]
        public String ReturnValues { get; set; }

        /// <summary>
        /// Gets or sets the query string field.
        /// </summary>
        /// <value>The query string field.</value>
        [Browsable(true)]
        [Bindable(true)]
        [Localizable(false)]
        [Category("Appearance")]
        [Description("Gets or sets the QueryString Field string")]
        [DefaultValue("ReturnValue")]
        public String QueryStringField { get; set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="PopupButton"/> class.
        /// </summary>
        public PopupButton()
        {
            QueryStringField = "ReturnValue";
            WindowHeight = 300;
            WindowWidth = 300;
        }

        protected override void OnInit(EventArgs e)
        {
            linkButtonId = this.UniqueID + "_Button";

            // Define the name and type of the client scripts on the page.
            string scriptName = "NotAClue.Web.UI.WebControls.ResourceFiles.NAC_OpenPopup.js";
            Type scriptType = this.GetType();

            // Get a ClientScriptManager reference from the Page class.
            ClientScriptManager csm = Page.ClientScript;

            // Check to see if the Client Script Include is already registered.
            if (!csm.IsClientScriptIncludeRegistered(scriptType, scriptName))
            {
                // include main Flash Content script
                string urlJS = csm.GetWebResourceUrl(scriptType, scriptName);
                csm.RegisterClientScriptInclude(scriptType, scriptName, urlJS);
            }

            base.OnInit(e);
        }

        // override RenderBeginTag so no tag is output
        public override void RenderBeginTag(HtmlTextWriter writer)
        {
            //base.RenderBeginTag(writer);
        }

        /// <summary>
        /// Renders the control to the specified HTML writer.
        /// </summary>
        /// <param name="writer">
        /// The <see cref="T:System.Web.UI.HtmlTextWriter"/> 
        /// object that receives the control content.
        /// </param>
        protected override void Render(HtmlTextWriter writer)
        {
            //base.Render(writer);
            //declare the function and variable names
            var popupId = this.UniqueID.Replace("$", "");
            var functionName = "NAC_" + popupId + "Func";
            var variableName = "NAC_" + popupId + "Var";

            StringBuilder script = new StringBuilder();
            script.Append("\n<script language=\"JavaScript\" type=\"text/javascript\">\n");
            script.Append("//<![CDATA[\n");
            script.Append("function " + functionName + "() {\n");
            script.Append("var " + variableName + " = NAC_OpenPopup('" +
                PostBackUrl + "', '" +
                QueryStringField + "', '" +
                this.UniqueID + "', '" +
                Title.Replace(" ", "") + "', '" +
                WindowWidth + "', '" +
                WindowHeight + "');\n");
            //script.Append("while (!" + variableName + ".closed) { }\n");
            //script.Append(Page.ClientScript.GetPostBackEventReference(this, "") + ";\n");
            script.Append("//]]>\n");
            script.Append("}\n");
            script.Append("\n</script>\n");

            writer.Write(script.ToString());

            // render button
            writer.AddAttribute(HtmlTextWriterAttribute.Id, linkButtonId);
            writer.AddAttribute(HtmlTextWriterAttribute.Name, linkButtonId);

            // set CSS styling
            if (!String.IsNullOrEmpty(CssClass))
                writer.AddAttribute(HtmlTextWriterAttribute.Class, CssClass);

            writer.AddAttribute(HtmlTextWriterAttribute.Onclick, "javascript:" + functionName + "();");
            writer.RenderBeginTag(HtmlTextWriterTag.Button);
            writer.Write(Text);
            writer.RenderEndTag();

            // hidden field
            //<input type="hidden" name="HiddenField1" id="HiddenField1" />
            writer.AddAttribute(HtmlTextWriterAttribute.Id, this.UniqueID);
            writer.AddAttribute(HtmlTextWriterAttribute.Name, this.UniqueID);
            writer.AddAttribute(HtmlTextWriterAttribute.Type, "hidden");
            writer.RenderBeginTag(HtmlTextWriterTag.Input);
            writer.RenderEndTag();
        }

        // override RenderEndTag so no tag is output
        public override void RenderEndTag(HtmlTextWriter writer)
        {
            //base.RenderEndTag(writer);
        }

        #region IPostBackDataHandler Members
        /// <summary>
        /// When implemented by a class, processes 
        /// postback data for an ASP.NET server control.
        /// </summary>
        /// <param name="postDataKey">
        /// The key identifier for the control.
        /// </param>
        /// <param name="postCollection">
        /// The collection of all incoming name values.
        /// </param>
        /// <returns>
        /// true if the server control's state 
        /// changes as a result of the postback; 
        /// otherwise, false.
        /// </returns>
        public bool LoadPostData(string postDataKey, NameValueCollection postCollection)
        {
            // get the return value into the property
            ReturnValues = postCollection[this.UniqueID];
            return true;
        }

        /// <summary>
        /// When implemented by a class, signals the 
        /// server control to notify the ASP.NET application
        /// that the state of the control has changed.
        /// </summary>
        public void RaisePostDataChangedEvent()
        {
            // raise the OnTextChanged event
            OnTextChanged(EventArgs.Empty);
        }

        /// <summary>
        /// Raises the <see cref="E:TextChanged"/> event.
        /// </summary>
        /// <param name="e">
        /// The <see cref="System.EventArgs"/> 
        /// instance containing the event data.
        /// </param>
        protected virtual void OnTextChanged(EventArgs e)
        {
            EventHandler textChangedHandler = (EventHandler)Events[EventTextChanged];
            // raise the event if a handler is attached.
            if (textChangedHandler != null)
                textChangedHandler(this, e);
        }
        #endregion
    }
}

Listing 4 – PopupButton class

using System;
using System.ComponentModel;
using System.Security.Permissions;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace NotAClue.Web.UI.WebControls
{
    [AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal)]
    [AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    [Designer("NotAClue.Web.UI.WebControls.PopupClientDesigner, NotAClue.Web.UI.WebControls")]
    [ToolboxData("<{0}:PopupClient runat=\"server\"></{0}:PopupClient>")]
    public class PopupClient : WebControl
    {
        /// <summary>
        /// holds the name of the control 
        /// that we retrun a value to
        /// </summary>
        private String returnValuesControl;

        /// <summary>
        /// Gets or sets the values.
        /// </summary>
        /// <value>The values.</value>
        [Browsable(true)]
        [Bindable(true)]
        [Localizable(false)]
        [Category("Appearance")]
        [Description("Gets or sets the return values")]
        public String ReturnValues { get; set; }

        /// <summary>
        /// Gets or sets the query string field.
        /// </summary>
        /// <value>The query string field.</value>
        [Browsable(true)]
        [Bindable(true)]
        [Localizable(false)]
        [Category("Appearance")]
        [Description("Gets or sets the QueryString Field string")]
        [DefaultValue("ReturnValue")]
        public String QueryStringField { get; set; }

        /// <summary>
        /// Initializes a new instance of the 
        /// <see cref="PopupClient"/> class.
        /// </summary>
        public PopupClient()
        {
            QueryStringField = "ReturnValue";
        }

        protected override void OnInit(EventArgs e)
        {
            // get return values control
            try
            {
                returnValuesControl = Page.Request.QueryString[QueryStringField];
            }
            catch (Exception)
            {
                returnValuesControl = String.Empty;
            }
            base.OnInit(e);
        }

        // override the RenderBeginTag so no tag is output
        public override void RenderBeginTag(HtmlTextWriter writer)
        {
            //base.RenderBeginTag(writer);
        }

        /// <summary>
        /// Renders the control to the 
        /// specified HTML writer.
        /// </summary>
        /// <param name="writer">
        /// The <see cref="T:System.Web.UI.HtmlTextWriter"/> 
        /// object that receives the control content.
        /// </param>
        protected override void Render(HtmlTextWriter writer)
        {
            // hidden field
            writer.AddAttribute(HtmlTextWriterAttribute.Id, this.UniqueID);
            writer.AddAttribute(HtmlTextWriterAttribute.Name, this.UniqueID);
            writer.AddAttribute(HtmlTextWriterAttribute.Type, "hidden");
            writer.AddAttribute(HtmlTextWriterAttribute.Value, ReturnValues);
            writer.RenderBeginTag(HtmlTextWriterTag.Input);
            writer.RenderEndTag();

            StringBuilder script = new StringBuilder();
            script.Append("\n<script language=\"JavaScript\" type=\"text/javascript\">\n");
            script.Append("//<![CDATA[\n");
            script.Append("    var value = document.getElementById(\"" +
                this.UniqueID + "\").value;\n");

            script.Append("    if(value != \"\") {\n");
            script.Append("        window.opener.document.getElementById(\"" + returnValuesControl + "\").value = value\n");
            script.Append("        window.opener.__doPostBack('" + returnValuesControl + "', '');\n");
            script.Append("        window.close();\n");
            script.Append("//]]>\n");
            script.Append("    }\n");
            script.Append("\n</script>\n");

            writer.Write(script.ToString());

            //base.Render(writer);
        }

        // override the RenderEndTag so no tag is output
        public override void RenderEndTag(HtmlTextWriter writer)
        {
            //base.RenderEndTag(writer);
        }
    }
}

Listing 5 – PopupClient

Note: Just explaining the two controls would take an age so get the book mentioned above Open-mouthed

Integrating it into a Dynamic Data site

We will add an attribute to set whether the PopupButton is displayed and what size the popup windows should be.

/// <summary>
/// An attribute used to specify the filtering behaviour for a column.
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class PopupInsertAttribute : Attribute
{
    public int WindowWidth { get; set; }
    
    public int WindowHeight { get; set; }

    public PopupInsertAttribute(int width, int height)
    {
        WindowWidth = width;
        WindowHeight = height;
    }
}

Listing 6 – the PopupInsertAttribute

First we will add the PopupButton to the ForeignKey_Edit field template.

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);
    }
    // setup popup
    var popup = MetadataAttributes.GetAttribute<PopupInsertAttribute>();
    if (popup != null)
    {
        PopupButton1.PostBackUrl = ForeignKeyColumn.ParentTable.GetActionPath("PopupInsert");
        PopupButton1.Visible = true;
        PopupButton1.WindowWidth = popup.WindowWidth;
        PopupButton1.WindowHeight = popup.WindowHeight;
        PopupButton1.Title = ForeignKeyColumn.ParentTable.DisplayName;
    }
}

protected void PopupButton1_TextChanged(object sender, EventArgs e)
{
    string foreignkey = PopupButton1.ReturnValues;
    //if no value just return don't do anything
    if (String.IsNullOrEmpty(PopupButton1.ReturnValues))
        return;

    // reset the DDL
    DropDownList1.Items.Clear();
    PopulateListControl(DropDownList1);
    if (!Column.IsRequired)
        DropDownList1.Items.Add(new ListItem("[Not Set]", ""));

    ListItem item = DropDownList1.Items.FindByValue(foreignkey);
    if (item != null)
        DropDownList1.SelectedValue = foreignkey;
}

Listing 7 – Modified Page_Load and the TextChange event handlers for the ForeignKey_Edit field.

Now for the PopupInsert page first make a copy of the standard Insert page (if using a Web Application Project you must change the pages class name also)

public partial class PopupInsert : System.Web.UI.Page
{
    protected MetaTable table;

    protected void Page_Init(object sender, EventArgs e)
    {
        DynamicDataManager1.RegisterControl(DetailsView1);

        // turn partial render off.
        var scriptManager = ScriptManager.GetCurrent(Page);
        if (scriptManager != null)
            scriptManager.EnablePartialRendering = false;
    }

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

    protected void DetailsView1_ItemCommand(object sender, DetailsViewCommandEventArgs e)
    {
        if (e.CommandName == DataControlCommands.CancelCommandName)
        {
            // Response.Redirect(table.ListActionPath);
            // close popup cleanly making sure the return value is an empty string
            PopupClient1.ReturnValues = "";
            Response.Write("<script language='javascript'> { self.close() }</script>");
        }
    }

    protected void DetailsView1_ItemInserted(object sender, DetailsViewInsertedEventArgs e)
    {
        // don't respond to the DetailsView1_ItemInserted here anymore
        //if (e.Exception == null || e.ExceptionHandled)
        //{
        //    Response.Redirect(table.ListActionPath);
        //}
    }

    protected void DetailsDataSource_Inserted(object sender, LinqDataSourceStatusEventArgs e)
    {
        if (e.Exception == null || e.ExceptionHandled)
        {
            // get the PK and return it to the parent page
            PopupClient1.ReturnValues = table.GetPrimaryKeyString(e.Result);
        }
    }
}

Listing 8 – PopupInsert page.

Sample Metadata

[MetadataType(typeof(ProductMetadata))]
partial class Product
{
    partial class ProductMetadata
    {
        public Object ProductID { get; set; }
        public Object ProductName { get; set; }
        public Object SupplierID { get; set; }
        public Object CategoryID { get; set; }
        public Object QuantityPerUnit { get; set; }
        public Object UnitPrice { get; set; }
        public Object UnitsInStock { get; set; }
        public Object UnitsOnOrder { get; set; }
        public Object ReorderLevel { get; set; }
        public Object Discontinued { get; set; }
        // Entity Set 
        public Object Order_Details { get; set; }
        // Entity Ref 
        [PopupInsert(600, 500)]
        public Object Category { get; set; }
        // Entity Ref 
        public Object Supplier { get; set; }
    }
}

Listing 9 – sample metadata

An Explanation of how it all works

We add the PopupInsertAttribute to the FK column we want to be able to do an insert on, this causes the ForeignKey_Edit to display the New PopupButton which opens the PopupInsert page.

When you click either the Insert or cancel button this will cause the PopupInsert page to close wither returning an empty string or the ID of the new entity.

The TextChanged event is fired on the PopupButton in the ForeignKey_Edit field template where if there is a new value the drop down list is repopulated and the new item is selected.

Download

Full source for the PopupButton and PopupClient server controls is provided in the sample.

Wednesday, 20 May 2009

Password FieldTemplates for Dynamic Data (UPDATED)

See this thread on the Dynamic Data Forum where I suggested you could use [DataType(DataType.Password)] attribute to mark the field as a password field and alternative would be to create a custom FieldTemplate and use [UIHint("Password")] to set it.

This just means the extra logic I implement for the password is only used on password fields and not all Text fields.

<%@ Control 
    Language="C#" 
    CodeBehind="Password.ascx.cs" 
    Inherits="DD_EF4_PasswordField.PasswordField" %>

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

Listing 1 – Password.ascx

using System;
using System.Web.UI;

namespace DD_EF4_PasswordField
{
    public partial class PasswordField : System.Web.DynamicData.FieldTemplateUserControl
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            // add a string of '*' the max length of the field
            var length = Column.MaxLength > 20 ? 20 : Column.MaxLength;
            Literal1.Text = new String('*', length);
        }

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

Listing 2 – Password.ascx.cs

Listing 1 & 2 represent the Password (Normal) FieldTemplate.

This is FieldTemplate just displays a list of '*' the max length of the field instead of the actual text.

<%@ Control 
    Language="C#" 
    CodeBehind="Password_Edit.ascx.cs" 
    Inherits="DD_EF4_PasswordField.Password_EditField" %>

<asp:TextBox 
    ID="TextBox1" 
    runat="server" 
    TextMode="Password"
    CssClass="DDTextBox" >
</asp:TextBox>

<asp:RequiredFieldValidator 
    runat="server" 
    ID="RequiredFieldValidator1"
    CssClass="DDControl" 
    ControlToValidate="TextBox1" 
    Display="Dynamic" 
    Enabled="false" />
<asp:RegularExpressionValidator 
    runat="server" 
    ID="RegularExpressionValidator1" 
    CssClass="DDControl" 
    ControlToValidate="TextBox1" 
    Display="Dynamic" 
    Enabled="false" />
<asp:DynamicValidator 
    runat="server" 
    ID="DynamicValidator1" 
    CssClass="DDControl" 
    ControlToValidate="TextBox1" 
    Display="Dynamic" />

Listing 3 – Password_Edit.ascx

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

namespace DD_EF4_PasswordField
{
    public partial class Password_EditField : FieldTemplateUserControl
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (Column.MaxLength < 20)
            {
                TextBox1.Columns = Column.MaxLength;
            }
            TextBox1.ToolTip = Column.Description;

            SetUpValidator(RequiredFieldValidator1);
            SetUpValidator(RegularExpressionValidator1);
            SetUpValidator(DynamicValidator1);
        }

        protected override void OnDataBinding(EventArgs e)
        {
            base.OnDataBinding(e);
            TextBox1.MaxLength = Math.Max(FieldValueEditString.Length, Column.MaxLength);
        }

        protected override void ExtractValues(IOrderedDictionary dictionary)
        {
            if (TextBox1.Text.Length > 0)
                dictionary[Column.Name] = ConvertEditedValue(TextBox1.Text);
        }

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

Listing 4 – Password_Edit.ascx.cs

Listings 3 & 4 are for the Edit version Password_Edit FieldTemplate.

Updated: Listing 4 has been updated with the line if (TextBox1.Text.Length > 0) so that in edit mode if you do not enter a value it will not return an empty string.

Here the TextBox’s TextMode is set to Password so that any password you type in is hidden.

Note: The TextBox does not show any text in the TextBox.Text property when in Password mode this is normal behaviour.

Friday, 8 May 2009

Communicating Between FieldTemplates in Dynamic Data (UPDATED)

A question that is asked a lot on the Dynamic Data Forum is how can I get a reference to a FieldTemplate, the reason people ask this is because they are used to doing things this was from classic ASP.Net code; the problem with this is that it leads to specialised code in the page, which means custom page and I always go for custom FieldTemplate over custom page.

The problem with most of the custom FieldTemplates I’ve written for production code is that they are not generic which can be ok, but I tend to find myself writing the same sort of things again and again. So with question on the Dynamic Data Forum and on this blog I thought I’d tackle one of these types of problem in a more generic reusable way. This solution come from the previous cascading articles I’ve culminating with Cascading Filters and Fields – Dynamic Data Entity Framework Version which allows fields and filters to cascade. Here I’m going to use the same event model so one control can alert other controls to a change in it’s state thus facilitating say a checkbox hiding or disabling other field on the form depending upon its state. In this article I’m going to look at Checkbox as parent controls enabling other controls to change there status.

What we will need to Build This.

  • Event Interface
  • Event Delegate
  • EventArgs
  • Implementations
    • Parent
    • Child

The Code

Here I will quickly layout the code (each listing is fully commented) we are going to use it is not majorly different form the Cascading FieldTemplate mentioned  here

/// <summary>
/// The interface for parent controls to implement.
/// </summary>
public interface IChangeNotifyingFieldTemplate
{
    /// <summary>
    /// Gets the parent column.
    /// </summary>
    /// <value>The parent column.</value>
    MetaColumn ParentColumn { get;}

    /// <summary>
    /// Gets the state.
    /// </summary>
    /// <value>The state.</value>
    String State { get; }

    /// <summary>
    /// Occurs when [state changed].
    /// </summary>
    event ChangingAwareEventHandler StateChanged;
}

Listing 1 – the IChangingAware event interface

In Listing 1 we have our interface which has an event and three properties we will need top implement in our FieldTemplates. Now we will need a way of sending the current status of the parent control to the child control for this will will use an EventArgs class.

/// <summary>
/// Event Arguments for Changing Aware Event
/// </summary>
public class ChangingAwareEventArgs : EventArgs
{
    /// <summary>
    /// Custom event arguments for SelectionChanged 
    /// event of the ParentChangingAwareFieldTemplate control
    /// </summary>
    /// <param name="value">
    /// The value of the currently selected 
    /// value of the parent control
    /// </param>
    public ChangingAwareEventArgs(String state)
    {
        State = state;
    }
    /// <summary>
    /// The values from the control of the parent control
    /// </summary>
    public String State { get; set; }
}

Listing 2 – Changing Aware EventArgs

As you can see in Listing 2 Changing Aware EventArgs has only one property which is a string for simplicity. We will use Value to pass the current value of the parent control to the child.

/// <summary>
/// Delegate for the changing aware Interface
/// </summary>
/// <param name="sender">Parent Control</param>
/// <param name="e">An instance of the ChangingAwareEventArgs</param>
public delegate void ChangingAwareEventHandler(
    object sender,
    ChangingAwareEventArgs e);

Listing 3 – The delegate for our parent and child controls

In Listing 3 you can see the delegate for our controls event.

public class ParentChangeNotifyingFieldTemplate 
: FieldTemplateUserControl, IChangeNotifyingFieldTemplate { /// <summary> /// Gets or sets the value. /// </summary> /// <value>The state.</value> public virtual String State { get; private set; } /// <summary> /// Gets or sets the parent column. /// </summary> /// <value>The parent column.</value> public MetaColumn ParentColumn { get; private set; } /// <summary> /// publish event. /// </summary> public event ChangingAwareEventHandler StateChanged; /// <summary> /// Raises the <see cref="E:System.Web.UI.Control.Init"/> event. /// </summary> /// <param name="e"> /// An <see cref="T:System.EventArgs"/> /// object that contains the event data. /// </param> protected override void OnInit(EventArgs e) { ParentColumn = Column; base.OnInit(e); } /// <summary> /// Raises the event checking first that an event if hooked up /// </summary> /// <param name="value">The value of the currently selected item</param> public void RaiseStatusChanged(String value) { // make sure we have a handler attached if (StateChanged != null) { //raise event StateChanged(this, new ChangingAwareEventArgs(value)); } } }

Listing 4 – Parent Change Notifying FieldTemplate

In Listing 4 the control that parent FieldTemplate will inherit so that they can generate events for the child control to subscribe to.

public class ChildChangingAwareFieldTemplate : FieldTemplateUserControl
{
    /// <summary>
    /// Gets or sets the parent column.
    /// </summary>
    /// <value>The parent column.</value>
    public MetaColumn ParentColumn { get; private set; }

    /// <summary>
    /// Gets or sets the parent control.
    /// </summary>
    /// <value>The parent control.</value>
    public IChangeNotifyingFieldTemplate ParentControl { get; set; }

    /// <summary>
    /// Raises the <see cref="E:System.Web.UI.Control.Init"/> event.
    /// </summary>
    /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param>
    protected override void OnInit(EventArgs e)
    {
        // get the parent column
        var parentColumn = Column.GetAttributeOrDefault<ChangingAwareAttribute>().ParentColumn;
        if (!String.IsNullOrEmpty(parentColumn))
            ParentColumn = Column.Table.GetColumn(parentColumn) as MetaColumn;

        // get parent field (note you must specify the container control type in
        // DetailsView and FormView = CompositeDataBoundControl : DataBoundControl
        // ListView = DataBoundControl
        if (ParentColumn != null)
            ParentControl = GetParentControl();

        // finally call base
        base.OnInit(e);
    }

    /// <summary>
    /// Gets the Parent control in a cascade of controls
    /// </summary>
    /// <param name="column"></param>
    /// <returns></returns>
    private IChangeNotifyingFieldTemplate GetParentControl()
    {
        if (ParentColumn != null)
        {
            // get value of dev ddl (Community)
            var parentDataControl = this.GetContainerControl<DataBoundControl>();

            // Get Parent FieldTemplate
            var parentDynamicControl = parentDataControl
                .FindDynamicControlRecursive(ParentColumn.Name)
                as DynamicControl;

            // extract the parent control from the DynamicControl
            IChangeNotifyingFieldTemplate parentControl = null;
            if (parentDynamicControl != null)
                parentControl = parentDynamicControl.Controls[0] as IChangeNotifyingFieldTemplate;

            return parentControl;
        }
        return null;
    }
}

Listing 5 – Child Changing Aware FieldTemplate

And Listing 5 is the control that child FieldTemplates will inherit, so it can subscribe to events from the parent control. It contains the logic to find the parent control and a couple of properties to hold the parent column and controls in.

/// <summary>
/// Get the attribute or a default instance of the attribute
/// if the Column attribute do not contain the attribute
/// </summary>
/// <typeparam name="T">
/// Attribute type
/// </typeparam>
/// <param name="table">
/// Column to search for the attribute on.
/// </param>
/// <returns>
/// The found attribute or a default 
/// instance of the attribute of type T
/// </returns>
public static T GetAttributeOrDefault<T>(this MetaColumn column) where T : Attribute, new()
{
    return column.Attributes.OfType<T>().DefaultIfEmpty(new T()).FirstOrDefault();
}

Listing 6 – Get attribute extension method

The extension method in Listing 6 is there to simplify the code for getting an attribute which we do a lot in Dynamic Data.

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

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

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

/// <summary>
/// Get the Data Control containing the FiledTemplate
/// usually a DetailsView or FormView
/// </summary>
/// <param name="control">
/// Use the current field template as a starting point
/// </param>
/// <returns>
/// A FilterRepeater the control that 
/// contains the current control
/// </returns>
public static T GetContainerControl<T>(this Control control) where T : Control
{
    var parentControl = control.Parent;
    while (parentControl != null)
    {
        var p = parentControl as T;
        if (p != null)
            return p;
        else
            parentControl = parentControl.Parent;
    }
    return null;
}

Listing 7 – A group of extension methods to get the parent control

Listing 7 is the two extension methods used by the child control to find the parent, by first using GetContainerControl to find the DetailsView, FormView or GridView etc.

!Important:

ALL previous Cascading examples have a minor flaw/bug/feature. The issue occurs when the parent control appears in the list of controls after the child control, which means in the controls OnInit event all following controls are not in the list. There are two options here

  1. Force the order of columns shown in the data control
  2. let each child control capture the OnDataBound event of the container DataControl and then find the parent control there, which may be too late to hookup the event
In this article we are going to use the first method and so I will introduce a field generator and an attribute to set the column order.
/// <summary>
/// Allows to specify the ordering of columns. Columns are will
///
be sorted in increasing order based on the Order value. Columns without /// this attribute have a default Order of 0. Negative values are /// allowed and can be used to place a column before all other columns. /// unashamedly nicked from the DD Futures project :D /// </summary> [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field,
Inherited = true,
AllowMultiple = false)] public class ColumnOrderAttribute : Attribute, IComparable { public static ColumnOrderAttribute Default = new ColumnOrderAttribute(0); public ColumnOrderAttribute(int order) { Order = order; } /// <summary> /// The ordering of a column. Can be negative. /// </summary> public int Order { get; private set; } public int ListOrder { get; set; } #region IComparable Members
public int CompareTo(object obj) { return Order - ((ColumnOrderAttribute)obj).Order; } #endregion } public static partial class HelperExtansionMethods { public static ColumnOrderAttribute GetColumnOrdering(this MetaColumn column) { return column.Attributes.OfType<ColumnOrderAttribute>()
.DefaultIfEmpty(ColumnOrderAttribute.Default).First(); } }

Listing 8 – Column Order attribute

/// <summary>
/// Implements the IAutoFieldGenerator interface and 
/// supports advanced scenarios such as declarative 
/// column ordering, workaround for attribute 
/// localization issues.
/// Again mostly swiped from DD Futures
/// </summary>
public class AdvancedFieldGenerator : IAutoFieldGenerator
{

    private MetaTable _table;
    private bool _multiItemMode;

    /// <summary>
    /// Allows to explicitly declare which columns should be skipped
    /// </summary>
    public List<MetaColumn> SkipList
    {
        get;
        set;
    }

    /// <summary>
    /// Creates a new AdvancedFieldGenerator.
    /// </summary>
    /// <param name="table">The table this class generates fields for.</param>
    /// <param name="multiItemMode"><value>true</value> to indicate a multi-item control such as GridView, <value>false</value> for a single-item control such as DetailsView.</param>
    public AdvancedFieldGenerator(MetaTable table, bool multiItemMode)
    {
        if (table == null)
        {
            throw new ArgumentNullException("table");
        }

        _table = table;
        _multiItemMode = multiItemMode;
        SkipList = new List<MetaColumn>();
    }

    private bool IncludeField(MetaColumn column)
    {
        // Skip columns that should not be scaffolded
        if (!column.GetScaffold())
            return false;

        // Don't display long strings in controls that show multiple items
        if (column.IsLongString && _multiItemMode)
            return false;

        // Skip columns that are on the skip list
        if (SkipList.Contains(column))
            return false;

        return true;
    }

    private ColumnOrderAttribute ColumnOrdering(MetaColumn column)
    {
        return column.Attributes.OfType<ColumnOrderAttribute>().DefaultIfEmpty(ColumnOrderAttribute.Default).First();
    }

    #region IAutoFieldGenerator Members

    public ICollection GenerateFields(Control control)
    {
        // Get all of table's columns, take only the ones that should be automatically included in a fields collection,
        // sort the result by the ColumnOrderAttribute, and for each column create a DynamicField
        var fields = from column in _table.Columns
                     where IncludeField(column)
                     orderby ColumnOrdering(column)
                     select new DynamicField()
                     {
                         DataField = column.Name,
                         HeaderText = column.DisplayName
                     };

        return fields.ToList();
    }

    #endregion
}

public static partial class HelperExtansionMethods
{
    /// <summary>
    /// Gets a value indicating if the column should be scaffolded. This honors the
    /// ScaffoldColumnAttribute as well as returns true if the column is an enumerated type.
    /// </summary>
    /// <param name="column"></param>
    /// <returns></returns>
    public static bool GetScaffold(this MetaColumn column)
    {
        // make sure we honor the ScaffoldColumnAttribute. The framework already does this
        // but we want to do this again as the first thing.
        var scaffoldAttribute = column.GetAttribute<ScaffoldColumnAttribute>();
        if (scaffoldAttribute != null)
            return scaffoldAttribute.Scaffold;

        // always return true for enumerated types
        return column.ColumnType.IsEnum || column.Scaffold;
    }
}

Listing 9 – The IAutoFieldGenerator

I have included Listing 8 & 9 for completeness they can both be found in the ASP.NET July 2007 Futures Source Code project on Codeplex and all I’m going to do is add [ColumnOrder(-1)] to the Discontinued column of the Products table (-1 is before zero and the default value is zero).

So now we are ready to setup some FieldTemplates to act as parents and children. Here we will create on parent control by modifying the default Boolean FieldTemplate Boolean_Edit.ascx.

Implementing the above classes in the FieldTemplates

Here we have a class for parent FieldTemplates to inherit and one for children, the parent exposes two properties and an event and the child class does the dirty business of finding the parent control.

Here we are going to use the Boolean_Edit.ascx for as out parent, you could use any theoretically but I thought Boolean made for a good sample.

#region Changing Aware code
/// <summary>
/// override the Value property and 
/// return the controls curretn state
/// </summary>
public override string State
{
    get
    {
        return CheckBox1.Checked.ToString();
    }
}

public MetaColumn ChildColumn { get { return Column; } }

protected void CheckBox1_CheckedChanged(object sender, EventArgs e)
{
    RaiseStatusChanged(this.CheckBox1.Checked.ToString());
}
#endregion

Listing 10 – code to add to the parent control (Boolean_Edit.ascx)

You just need to add the code from Listing 10 to the Boolean_Edit.ascx.cs file and then change the classes inheritance to ParentChangeNotifyingFieldTemplate now Boolean_Edit FieldTemplate is publishing its ChangingAware event.

#region Changing Aware Control
// added page init to hookup the event handler
protected override void OnDataBinding(EventArgs e)
{
    // get the parent column
    var parentColumn = Column.GetAttributeOrDefault<ChangingAwareAttribute>().ParentColumn;

    if (!String.IsNullOrEmpty(parentColumn))
    {
        //TODO: get the value from Row of the ParentColumn
        Object value = DataBinder.GetPropertyValue(Row, parentColumn);
        if (String.Compare(value.ToString(), "true", true) == 0)
            this.Visible = false;
        else
            this.Visible = true;
    }

    base.OnDataBinding(e);
}
#endregion

Listing 11 – this is the code for the Text.ascx.cs file

All you need to do is add the above code Listing 11 the to ReadOnly FieldTemplates that you want to hide in response to the parent in our case its just the Text.ascx.cs file.

#region Event
// added page init to hook-up the event handler
protected void Page_Init(object sender, EventArgs e)
{
    if (ParentColumn != null && ParentControl != null)
    {
        // regiter for the event
        ParentControl.StateChanged += StateChanged;
    }
}

// consume event
protected void StateChanged(object sender, ChangingAwareEventArgs e)
{
    if (ParentColumn != null && ParentControl != null)
    {
        // show or hide depending on current state of parent
        if (String.Compare(e.State, "true", true) == 0)
            this.Visible = false;
        else
            this.Visible = true;
    }
}

// added data binding to allow field to be hidden on load
protected override void OnDataBinding(EventArgs e)
{
    if (ParentControl != null)
        this.Visible = ParentControl.State == "True" ? false : true;
    base.OnDataBinding(e);
}

Listing 12 – this is the code for the Text_Edit.ascx and Integer_Edit.ascx files (UPDATED)

Listing 12 code is added to both the Text_Edit.ascx.cs and Integer_Edit.ascx.cs files

Updated: I’ve update the code in the OnDataBinding event handler to fix a bug during insert where there would be no value in the parent filed.
[MetadataType(typeof(ProductMD))]
public partial class Product
{
    public class ProductMD
    {
        public object ProductID {get;set;}
        public object ProductName {get;set;}
        public object SupplierID {get;set;}
        public object CategoryID {get;set;}

        [ChangingAware("Discontinued")]
        public object QuantityPerUnit {get;set;}

        public object UnitPrice {get;set;}
        public object UnitsInStock {get;set;}

        [ChangingAware("Discontinued")]
        public object UnitsOnOrder {get;set;}

        [ChangingAware("Discontinued")]
        public object ReorderLevel {get;set;}

        [ColumnOrder(-1)]
        public object Discontinued {get;set;}
        // EntitySet
        public object Order_Details {get;set;}
        // EntityRef
        public object Category {get;set;}
        public object Supplier {get;set;}

    }
}

Listing 13 – the Metadata

As you can see in Listing 13 of the metadata I’ve added the ChangeAware attribute to several columns these will be hidden id the row is discontinued. And to make sure that the children can see the parent when looking for it I’ve added a ColumnOrder attribute to the Discontinued column with a value of –1 for force it to be the first field in the row see Figure 1, 2 and 3 .

Figure 1 – As you can see the bottom row is discontinued and some field are hidden appropriately

Figure 1 – As you can see the bottom row is discontinued and some field are hidden appropriately.

 

Figure 2 - normal Figure 3 - Discontinued
Figure 2 - normal Figure 3 - Discontinued

 

So there we have it

Download (UPDATED)

Happy coding HappyWizard

Thursday, 2 April 2009

Disallow Navigation on ForeignKey FieldTemplate – Dynamic Data (UPDATED)

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

C#
[AttributeUsage(AttributeTargets.Property)] public class DisallowNavigationAttribute : Attribute { public Boolean Hide { get; private set; } /// <summary> /// Initializes a new instance of the <see cref="DisallowNavigationAttribute"/> class. /// </summary> public DisallowNavigationAttribute() { Hide = false; } public DisallowNavigationAttribute(Boolean show) { Hide = show; } }
VB
<AttributeUsage(AttributeTargets.[Property] Or AttributeTargets.[Field])> _ Public Class DisallowNavigationAttribute Inherits Attribute Private _hide As [Boolean] Public Property Hide() As [Boolean] Get Return _hide End Get Private Set(ByVal value As [Boolean]) _hide = value End Set End Property Public Sub New(ByVal hide As [Boolean]) Me.Hide = hide End Sub Public Sub New() Me.Hide = False End Sub End Class

Listing 1 – AllowNavigation attribute

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

C#
/// <summary> ///
Get the attribute or a default instance of the attribute /// if the Column attribute do not contain the attribute /// </summary> /// <typeparam name="T">Attribute type</typeparam> /// <param name="table">Column to search for the attribute on.</param> /// <returns>The found attribute or a default instance of the attribute of type T</returns> public static T GetAttributeOrDefault<T>(this MetaColumn column) where T : Attribute, new() { return column.Attributes.OfType<T>().DefaultIfEmpty(new T()).FirstOrDefault(); }
VB
Imports
System.Runtime.CompilerServices Imports System.Web.DynamicData Public Module ExtensionMethods ''' <summary> ''' Get the attribute or a default instance of the attribute ''' if the Column attribute do not contain the attribute ''' </summary> ''' <typeparam name="T">Attribute type</typeparam> ''' <param name="column">Column to search for the attribute on.</param> ''' <returns>The found attribute or a default instance of the attribute of type T</returns> ''' <remarks>Used to simplify getting Attributes from metadata</remarks> <Extension()> _ Public Function GetAttributeOrDefault(Of T As {Attribute, New})(ByVal column As MetaColumn) As T Return column.Attributes.OfType(Of T)().DefaultIfEmpty(New T()).FirstOrDefault() End Function End Module

Listing 2 -  Extension method to get attribute

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.

C#
protected string
GetNavigateUrl() { var dissallow = Column.GetAttributeOrDefault<DisallowNavigationAttribute>(); if (!AllowNavigation || dissallow.Hide) { // remove undeline :D HyperLink1.Style.Add(HtmlTextWriterStyle.TextDecoration, "none !important"); return null; } if (String.IsNullOrEmpty(NavigateUrl)) { return ForeignKeyPath; } else { return BuildForeignKeyPath(NavigateUrl); } }
VB
Protected Function
GetNavigateUrl() As String Dim dissallow = Column.GetAttributeOrDefault(Of DisallowNavigationAttribute)() If Not AllowNavigation OrElse dissallow.Hide Then Return Nothing End If If String.IsNullOrEmpty(NavigateUrl) Then Return ForeignKeyPath Else Return BuildForeignKeyPath(NavigateUrl) End If End Function

Listing 3 – ForeignKey FieldTemplate modifications

Updated: With the addition of the inline style the underline will not show, of course you could always just set the CssClass of the Hyperlink to a style with textdecoration: none !important; if you wanted.

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

C#
[MetadataTypeAttribute(typeof(Products.ProductsMetadata))] public partial class Products { internal sealed class ProductsMetadata { public object ProductID { get; set; } public object ProductName { get; set; } public object QuantityPerUnit { get; set; } public object UnitPrice { get; set; } public object UnitsInStock { get; set; } public object UnitsOnOrder { get; set; } public object ReorderLevel { get; set; } public object Discontinued { get; set; } public object Order_Details { get; set; } [DisallowNavigation(true)] public object Categories { get; set; } [DisallowNavigation(true)] public object Suppliers { get; set; } } }
VB
Imports
Microsoft.VisualBasic Imports System.Web.DynamicData Imports System.ComponentModel Imports System.ComponentModel.DataAnnotations <MetadataType(GetType([Product].[ProductMD]))> _ Partial Public Class [Product] Partial Public Class [ProductMD] Public ProductID As Object Public ProductName As Object Public SupplierID As Object Public CategoryID As Object Public QuantityPerUnit As Object Public UnitPrice As Object Public UnitsInStock As Object Public UnitsOnOrder As Object Public ReorderLevel As Object Public Discontinued As Object Public Order_Details As Object <DisallowNavigationAttribute(True)> _ Public Category As Object Public Supplier As Object End Class End Class

Listing 4 – sample Metadata

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

Happy Coding and remember HappyWizard