Welcome to MSDN Blogs Sign in | Join | Help

Setting font attributes with UIHint in your Entity Partial Class

 

I've written a simple Field Template ( RedBold.ascx ) that reads most font attributes and applies them to your field values. The new entity templates make it easy to apply font attributes to the field labels. The image below shows several font attributes applied to the partial class for the Customer table of the AdventureWorksLT database.

You can apply attributes (bold, italic, foreground color, background color, border style, border color, font name, etc) to the field name or the field value. For example, I specified the following attributes for the last name:

[UIHint("RedBold", null, "Font-Size", "12", "Italic", "B",
        "Font_Name", "Comic Sans MS")]
public object LastName { get; set; }

The font size applies to both the label and the field value. For font attributes like bold and italic that are boolean, you can specify "F" to apply the attribute to the field, "L" to apply to the label or "B" to apply to both the field and label. When you specify an HTML font attribute, the attribute value applies to both the field value and the label. In the code sample above,  "Font_Name" , "Comic Sans MS" applies to both the field name and field value. To apply an attribute to a field value only, prepend the attribute name with "F_". To apply an attribute to a label only, prepend the attribute name with "L_".  The following code snippet shows the font attributes for the middle name and name style fields of the customer table.

[UIHint("RedBold", null, "F_BorderStyle", "Dashed", "F_BorderWidth", "3",
        "F_BorderColor", "DeepPink", "L_BackColor", "Lime")]
public object MiddleName { get; set; }

[UIHint("RedBold", null, "L_BorderColor", "Magenta", "L_BorderStyle", "Groove", "L_BorderWidth", "4")] [Display(Name = "Name Style")] public object NameStyle { get; set; }

Applying the font attributes to field values.

I copied the Text.ascx field template to create RedBold.ascx and added the following PreRender method to apply the font hints. You don't even need the RedBold.ascx  field template, you could simply add the PreRender method and code below to the Text.ascx field template.

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

    UIHintAttribute hint = null;
    hint = (UIHintAttribute)this.Column.Attributes[typeof(UIHintAttribute)];
    if (hint == null ||
       hint.ControlParameters.Count < 1)
        return;

    FontHints.applyUIHints(hint, this.Literal1, /* isField */ true);
}  

Here is a portion of the appUIHints method.

public static void applyUIHints(UIHintAttribute hint, Label lbl, bool isField) {

    if (hint.ControlParameters.ContainsKey("Font_Name")) {
        lbl.Font.Name = (string)hint.ControlParameters["Font_Name"];
    }

}

Entity Templates make it easy to apply font attributes to field labels.

Add the lines after the comment // Add these lines  to the Label_Init method of the Default.ascx entity template.

protected void Label_Init(object sender, EventArgs e) {
    Label label = (Label)sender;
    label.Text = currentColumn.DisplayName;

    // Add these lines for attribute applied font settings
    var hint = (UIHintAttribute)currentColumn.Attributes[typeof(UIHintAttribute)];
    if (hint == null ||
       hint.ControlParameters.Count < 1)
        return;

    FontHints.applyUIHints(hint, label, /*isField */ false);
}

Font attributes can be applied to any scaffolded table when displayed in the details view. The list.aspx page template does not use entity templates, so the font attributes will only apply to field values.

Using font attributes with custom entity templates.

My MSDN sample entity templates as written were unable to apply font attributes to the field labels, so I created a simple user control containing a literal (for the field label) and a DynamicControl (for the field value) to encapsulate the pair. The user control (Controls\Lbl_Literal_pair.ascx ) applies the font attributes from your partial class to your custom entity template. The following image shows the Address table from the AdventureWorksLT database using my custom entity template and several font attributes.

AddressesET

Some of the font attributes I apply include a border around the address field value, non-bold text for city, upper case for city (both the label and field value), lower case for state, italic text for the zip code label, and many more.

Adding custom front attributes to your Dynamic Data project.

You can download the complete project and copy FontHint.cs, Controls\Lbl_Literal_pair.ascx and DynamicData\FieldTemplates\RedBold.ascx to your project. You will have to change the namespace and annotate your entity partial class with [UIHint("RedBold" and font attributes. See partials.cs for examples. As mentioned previously, you don't even need to create the RedBold.ascx field template, you can apply my modifications to the default.ascx field template.

Posted by ricka0 | 1 Comments

Attachment(s): FontAttrib.zip

Dynamic Data FAQ

Please post corrections/new submissions to the Dynamic Data Forum. Put FAQ Submission/Correction in your title.

See Tips on getting your ASP.NET Dynamic Data questions answered quickly

Post LINQ To SQL Questions here
Post Entity Framework Questions here

Links that will answer questions:

From the Dynamic Data architect David Ebbo

From http://blogs.msdn.com/rickAndy  (Most samples include VB)

From ASP.Net Developer David Fowler 

Q: How do I get started with Dynamic Data?
A: If you like videos, see ASP.NET Dynamic Data videos
     MSDN on Dynamic Data
     Walkthrough: Creating a New ASP.NET Dynamic Data Web Site Using Scaffolding
     http://www.asp.net/dynamicdata/
     Dynamic Data Forum

Q: What's new with Dynamic Data for .Net 4 Beta?  <-- New
A: See this link.

Q: The data annotation to my partial class is not working?
A: The most common problem is no namespace or incorrect namespace around your partial class. A partial class that is not part of the data model (or part of another class) is known as a naked partial class. The easiest way to test if you have a naked partial class is to add a non-existing field to the partial class. In the following snippet,  bogus is not in the CustomerAddress table.

[MetadataType(typeof(CustomerAddressMetaData))]
   public partial class CustomerAddress {
       public class CustomerAddressMetaData {

           [ScaffoldColumn(false)]
           public object ModifiedDate;

           public object bogus;
       }
   }

When you run the Dynamic Data project, you will get the following exception in DefaultModel.RegisterContext (in global.asax):

The associated metadata type for type 'DynamicDataProject.CustomerAddress' contains the following unknown properties or fields: bogus. Please make sure that the names of these members match the names of the properties on the main type.

Q: How do I generate GUIDs on inserts?
A: You don't, you shouldn't. Let the DB generate them or you will have performance problems. You can modify the data model XML and explicitly state they are DB generated. With the EDM, use StoreGeneratedPattern="Computed"  while Linq to SQL uses IsDbGenerated="true"

Q: How to set Displayformat for a password field?
A: See Steve's excellent Password FieldTemplates for Dynamic Data

Q: How do I check for duplicates before I insert? (Or get values from the DB for any reason before I insert)
A: see this post.

Q: How do I mark a columns as read-only?
A: see this post or Making a Field Read-Only via the ReadOnlyAttribute – Dynamic Data - Note the next version of Dynamic Data And The ASP.NET Dynamic Data 4.0 Preview 3 support the [ReadOnly(true)] attribute.

Q: Can Dynamic Data use a data model generated by a 3rd party O/RM tool?
A:The current Dynamic Data needs the following items to work with a technology:
-    Technology must support Linq
-    DataSource control must exist for the technology
-    Dynamic Data ModelProvider must exist for the technology

An example of one today is LLBLGen: http://www.llblgen.com/defaultgeneric.aspx

nHibernate currently does not meet the Linq requirements.

Q: What DB's are supported?
A: See ADO.NET Entity Framework Providers

Q: How do I hide columns from the Edit and other page templates?
A: Suppose you want to display the Salary field, but you don't want to show it in the Edit and Insert templates. See Stephens popular tutorial

Dynamic Data - Hiding Columns in selected PageTemplates

Q: How do I disallow navigation links on Foreign Key columns?
A: By default, DD displays the first string column in the Foreign Key table in stead of the FK. This is rendered as a link (NavigateUrl) to the FK table. If you're not happy with the default (column shown) use the DisplayColumn attribute on the FK table to specify which property is displayed. See also Improving the FK field display: Showing two fields in Foreign Key columns. Back to the original question. The easiest way to disable the FK property from being a NavigateUrl is to hide the FK table by annotating your entity partial class with the ScaffoldTable   attribute. Note,  ScaffoldTable   will hide your table. If you want to expose your table but disable the hyperlink - see Stephens excellent blog Allow Navigation on ForeignKey FieldTemplate – Dynamic Data

Q: How do I move the Edit Delete Details links from the left side (column 0) to the right side of the table.
A: Create your own IAutoFieldGenerator.GenerateFields and add the command field at the end. (thx David Fowler). See this post.

Q: How do I implement role based security? (tables/columns visibility dependent on users role).
A: See this post, and custom IAutoFieldGenerator

Q: How do map stored procedures (sprocs) to Inserts and get back the auto generated PK using L2S?
A: See LINQ to SQL (Part 7 - Updating our Database using Stored Procedures) , Stored Procedures (LINQ to SQL) and How to override default insert method of Details View in Dynamic Data .

Posted by ricka0 | 1 Comments

Custom Validation Error message not displayed

 

A customer pointed out that his Spanish validation error messages were not being displayed; the default Error message was shown instead.  I'll use the Products table from the NorthWind Db to reproduce the problem and show a work-around.

Create a partial class for the Products entity:

 [MetadataType(typeof(ProductsMD))]
    public partial class Products {
        public class ProductsMD {
            [DataType("dummy", ErrorMessage = "El campo debe ser un entero positivo")]
            [DisplayName("En la acción")]
            public object UnitsInStock { get; set; }
        }
    }
VB
    <MetadataType(GetType(ProductsMD))> _
Public Class Products
        Public Class ProductsMD
            <DataType("dummy", ErrorMessage:="El campo debe ser un entero positivo"), _
             DisplayName("En la acción")> _
            Public Property UnitsInStock() As Object
            End Property
        End Class
    End Class

Navigate to the Products table, edit a row, and change the UnitsInStock to a letter. Select update and you will get a default error message, not the message applied with the DataType attribute.

The example above was doomed to failure as the DataType attribute above does not match the DataType enum or have a matching field template.

There are a couple of possible solutions for displaying custom validation errors on integer conversion failure. You can add the compare validator message that will be displayed for any validation error. The code should be added as the last statement of the Page_Load method in the DynamicData\FieldTemplates\Integer_Edit.ascx.cs  (or .vb) file.

CompareValidator1.ErrorMessage = String.Format("El campo {0} debe ser un entero", Column.DisplayName);

or for VB

CompareValidator1.ErrorMessage = String.Format("El campo {0} debe ser un entero", Column.DisplayName)

Entering a non-integer such as Z to the units in stock field now yields the following error message:

El campo En la acción debe ser un entero

Adding the CompareValidator1.ErrorMessage to the integer template works for every scaffolded integer.

For more flexibility, you can extract the custom message from the data model partial class. Replace the CompareValidator1.ErrorMessage  line above with the following in the Page_Load method.

 var dataTypeAttribute = MetadataAttributes.OfType<DataTypeAttribute>().SingleOrDefault();

 if (dataTypeAttribute != null) {
    CompareValidator1.ErrorMessage = dataTypeAttribute.FormatErrorMessage(Column.DisplayName);
 }

VB

Dim dataTypeAttribute = MetadataAttributes.OfType(Of DataTypeAttribute)().SingleOrDefault() 

If dataTypeAttribute IsNot Nothing Then 
    CompareValidator1.ErrorMessage = dataTypeAttribute.FormatErrorMessage(Column.DisplayName) 
End If 

Even simpler than adding a dummy attribute and extracting the error message;  use the RegularExpression attribute and allow only positive integers. The following snippet fixes the error message problem without requiring you to extract the error message in the Page_Load method.

  [MetadataType(typeof(ProductsMD))]
    public partial class Products {
        public class ProductsMD {
  //          [DataType("dummy", ErrorMessage = "El campo debe ser un entero positivo")]
            [RegularExpression(@"^0*[1-9][0-9]*$", ErrorMessage = "Must be a entero positivo")]
            [DisplayName("En la acción")]
            public object UnitsInStock { get; set; }
        }
    }
VB
<MetadataType(GetType(ProductsMD))> _ 
Public Partial Class Products 
    Public Class ProductsMD 
Private _UnitsInStock As Object 
        ' [DataType("dummy", ErrorMessage = "El campo debe ser un entero positivo")] 
        <RegularExpression("^0*[1-9][0-9]*$", ErrorMessage := "Must be a entero positivo")> _ 
        <DisplayName("En la acción")> _ 
        Public Property UnitsInStock() As Object 
            Get 
                Return _UnitsInStock 
            End Get 
            Set(ByVal value As Object) 
                _UnitsInStock = value 
            End Set 
        End Property 
    End Class 
End Class 

Notes:

  1. We are using the DataType attribute as a dummy attribute so we can extract our custom error message when a validation error occurs.
  2. This is a known bug that should be fixed in the next release.
Posted by ricka0 | 3 Comments
Filed under: ,

Explicit connection string for EF

The default constructor for the ObjectContext class in the  Entity Data Model (EDM) retrieves the construction string from the web.config or app.config file. If you have multiple data models or need to pass in the the construction string at run time you must use the ObjectContext constructor that takes a construction string. (Note: Be sure to see the bug/work-around at the end of this article.) The follow snippet shows how to build the string for integrated security.

//  copy connection string from app.config or web.config  

    // connectionString="metadata=res://*;
    // provider=System.Data.SqlClient;
    // provider connection string=&quot;
    // Data Source=ricka0;Initial Catalog=Northwind;Persist Security Info=True;
    // User ID=sa;Password=*(IU89iu;MultipleActiveResultSets=True&quot;"
    // providerName="System.Data.EntityClient" 
    

    public static string UglyConStr() {

        return "metadata=res://*;"
          + "provider=System.Data.SqlClient;"
          + "provider connection string=';"  // Replace &quot with ' (single quote)
        + "Data Source=ricka0;"
        + "Initial Catalog=Northwind;"
        + "Persist Security Info=True;"
        + "User ID=sa;Password=*(IU89iu;"
        + "MultipleActiveResultSets=True';"  // Replace &quot with ' (single quote)
            //    + "providerName=\"System.Data.EntityClient\""
        ;
    }

While the construction string above works, it's very ugly. The hair pulling trick to get it working is replacing &quot with a single quote ' as shown in the comments. Using  the raw connection string from the config file (using &quot ) results in a misleading error message Keyword not supported: 'data source'.  

A much more elegant construction string using SQL connection is shown below.

 public static string getConStrSQL() {

        string connectionString = new System.Data.EntityClient.EntityConnectionStringBuilder
        {
            Metadata = "res://*",
            Provider = "System.Data.SqlClient",
            ProviderConnectionString = new System.Data.SqlClient.SqlConnectionStringBuilder
            {
                InitialCatalog = "Northwind",
                DataSource = "ricka0",
                IntegratedSecurity = false,
                UserID = getUID(),                 // User ID such as "sa"
                Password = getPWD(),               // hide the password
            }.ConnectionString
        }.ConnectionString;

        return connectionString;
    }

In VB:

Public Shared Function getConStrSQL() As String
     Dim connectionString As String = New System.Data.EntityClient.EntityConnectionStringBuilder() _
         With {.Metadata = "res://*", _
               .Provider = "System.Data.SqlClient", _
               .ProviderConnectionString = New System.Data.SqlClient.SqlConnectionStringBuilder() _
                 With {.InitialCatalog = "Northwind", _
                       .DataSource = "ricka0", _
                       .IntegratedSecurity = False, _
                       .UserID = getUID(), _
                       .Password = getPWD()}.ConnectionString}.ConnectionString
     Return connectionString
 End Function

The integrated security approach is slightly different.

public static string getConStrIntegrated() {

        string conStrIntegratedSecurity = new System.Data.EntityClient.EntityConnectionStringBuilder
           {
               Metadata = "res://*",
               Provider = "System.Data.SqlClient",
               ProviderConnectionString = new System.Data.SqlClient.SqlConnectionStringBuilder
               {
                   InitialCatalog = "NorthwindEF",
                   DataSource = "bing0",
                   IntegratedSecurity = true,
               }.ConnectionString
           }.ConnectionString;

        return conStrIntegratedSecurity;
    }
For Dynamic Data, simply pass the construction string to the MetaModel RegisterContext as follows. 
 public static void RegisterRoutes(RouteCollection routes) {
        MetaModel model = new MetaModel();


        model.RegisterContext(() => new NorthwindModel.NorthwindEntities(getConStrIntegrated()),
            new ContextConfiguration()
            {
                ScaffoldAllTables = true
            });

        // Routes omitted for clarity
    }

Unfortunately, the current version of Dynamic Data doesn't support this approach with EF (L2S does work). To get the page templates to use the connection string you must add the following line to the Page_Load method in the page templates.

GridDataSource.ContextCreating += delegate(object ceSender, System.Web.UI.WebControls.EntityDataSourceContextCreatingEventArgs ceArgs) {
            ceArgs.Context = (System.Data.Objects.ObjectContext)table.CreateContext();
        };

The complete Page_Load is below

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

         GridDataSource.ContextCreating += delegate(object ceSender, System.Web.UI.WebControls.EntityDataSourceContextCreatingEventArgs ceArgs) {
            ceArgs.Context = (System.Data.Objects.ObjectContext)table.CreateContext();
        }; 

        Title = table.DisplayName;
        GridDataSource.Include = table.ForeignKeyColumnsNames;
        InsertHyperLink.NavigateUrl = table.GetActionPath(PageAction.Insert);

        // Disable various options if the table is readonly
        if (table.IsReadOnly) {
            GridView1.Columns[0].Visible = false;
            InsertHyperLink.Visible = false;
        }
    }
Posted by ricka0 | 2 Comments

Improving the FK field display: Showing two fields in Foreign Key columns

 

The default scaffold of the CustomerAddress table in the AdventureWorksLT database poses a problem: Dynamic Data (DD) defaults to using the first string field in the referenced table. In this case, the first string field is the Title field (Mr,Ms, and so on).  The image below shows the problem with the FilterRepeater drop down list and Customer column elements.

We can take a first step toward fixing this issue by creating and annotating a partial class for the Customer entity. The code below now displays the LastName field for the customer entity. The DisplayColumn attribute tells referring entities which column to use for display instead of the Foreign Key field.

 [DisplayColumn("LastName")]
 [MetadataType(typeof(CustomerMetaData))]
 public partial class Customer {
      public class CustomerMetaData {
   }
 }

While the resulting view is an improvement, it’s not there yet. Note that it doesn't distinguish between the customers with non-unique last names, such as Adams or Liu.

 

To fix this, we overload the ToString method for the Customer partial class as shown below:

// [DisplayColumn("LastName")]
[MetadataType(typeof(CustomerMetaData))]
public partial class Customer {

     public override string ToString() {
         return LastName.ToString() + ", " + FirstName.ToString();
     }

     public class CustomerMetaData {

}

The resulting display gets it right: the code now shows the correct field, and it distinguishes between David, Jinghao and Kevin Liu.

Special thanks to David Ebbo for recommending the ToString() approach and Phil Haack for granting me blog dibs.

Posted by ricka0 | 4 Comments
Filed under: ,

How to create an updateable view with ADO Entity Framework and with LINQ to SQL

Creating an update-able view with  ADO Entity Framework (EF) or LINQ to SQL (L2S) is a fairly advanced topic and not directly associated with Dynamic Data. At the end of the article I have a sample console application to verify the modified L2S data model allows updates on a view. To create an update-able view, you must modify the wizard (or other tool) generated XML file (data model). Each time you generate a new data model (for example when the schema changes), you will need to reapply these steps.

ADO Entity Framework (EF) makes views Read Only via the <DefiningQuery> element. You make the data model view update-able by removing the <DefiningQuery> element and making a few minor changes. Note the example below is a very simple view on one table and includes the primary key.

This is what I did to make an update-able view for the AdventureWorksLT DB

CREATE VIEW [SalesLT].[vAddr]
AS
SELECT
AddressID,[AddressLine1],[City],[StateProvince],[CountryRegion],[PostalCode]
FROM [AdventureWorksLT2008].[SalesLT].[Address]

The next line shows this view is update-able (at least from T-SQL)  

UPDATE vAddr SET PostalCode = '54321'
WHERE addressID > 11382 AND
StateProvince
= 'WA'

 (18 row(s) affected) 

Edit the EF SSDL, comment out the <DefiningQuery> , remove store: prefix from Schema="SalesLT"  and remove store:Name="vAddr" . The commented/changed code below

<EntitySet Name="Address" EntityType="AdventureWorksLT2008Model.Store.Address" store:Type="Tables" Schema="SalesLT" />
<
EntitySet Name="vAddr" EntityType="AdventureWorksLT2008Model.Store.vAddr" store:Type="Views" Schema="SalesLT" />

<!--<EntitySet Name="vAddr" EntityType="AdventureWorksLT2008Model.Store.vAddr" store:Type="Views" store:Schema="SalesLT" store:Name="vAddr"> -->
<!--
<DefiningQuery>SELECT [vAddr].[AddressID] AS [AddressID], [vAddr].[AddressLine1] AS [AddressLine1], [vAddr].[City] AS [City],
[vAddr].[StateProvince] AS [StateProvince],
[vAddr].[CountryRegion] AS [CountryRegion],
[vAddr].[PostalCode] AS [PostalCode]
FROM [SalesLT].[vAddr] AS [vAddr]</DefiningQuery>
-->
<!--
</EntitySet>-->

</EntityContainer>

LINQ to SQL is the simplest.

Using the view above,

Simply change the following line in the wizard generated code to use    AutoSync = AutoSync.OnInsert in lieu of AutoSync=AutoSync.Always on the AddressID property.

// [Column(Storage="_AddressID", AutoSync=AutoSync.Always, DbType="Int NOT NULL IDENTITY", IsPrimaryKey=true, IsDbGenerated=true)]
[Column(Storage =
"_AddressID", AutoSync = AutoSync.OnInsert, DbType = "Int NOT NULL IDENTITY", IsPrimaryKey = true, IsDbGenerated = true)]
public int AddressID

The following example shows how to test the view from a console application.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Linq;
using System.Data.Linq.Mapping;

namespace updateableView {

        public class T {
            public LTDataContext db;
            //    readonly string con = "Data Source=bing0;Initial Catalog=AdventureWorksLT2008;Integrated Security=True";
            readonly string con = "Data Source=bing0;Initial Catalog=AdventureWorksLT2008;" +
                                   "Persist Security Info=True;User ID=sa;Password=*(IU89iu";

            public T() {
                db = new LTDataContext(con);
            }

            public void addAddr(string city) {

                vAddr adr = new vAddr();
                adr.AddressLine1 = "1234 N St.";
                adr.City = city;
                adr.PostalCode = "99966";
                adr.StateProvince = "Mt";
                adr.CountryRegion = "None";

                db.vAddrs.InsertOnSubmit(adr);
                db.SubmitChanges();
            }

            public void tq(string city) {

                Table<vAddr> addr = db.GetTable<vAddr>();
                var q = from c in addr
                        where c.City == city
                        select c;

                foreach (var cst in q)
                    Console.WriteLine("id = {0}, City = {1}", cst.AddressID, cst.City);
            }
        }

        class Program {
            static void Main(string[] args) {
                T tdb = new T();
                string city = "GF";
                tdb.addAddr(city);
                tdb.tq(city);

            }
        }
    }

Posted by ricka0 | 3 Comments
Filed under: ,

Adding the AdvancedFilterRepeater to a Dynamic Data web site.

The following is a brief outline of the steps need to add the Advanced Filter repeater from the the Dynamic Data Futures project on codeplex to your website. A more complete set of instructions can be found on C# BITS

  • Create a working Dynamic Data web site.
  • Copy DynamicDataFutures\Futures (from the working DynamicDataFutures solution) to your Web project.
  • Add exiting project (add the DynamicDataFutures\Futures\DynamicDataFutures.csproj )
  • Add the following to the  <controls>  of web.config
    <add tagPrefix="asp" namespace="Microsoft.Web.DynamicData" assembly="Microsoft.Web.DynamicData" />
            <add namespace="AjaxControlToolkit" assembly="AjaxControlToolkit" tagPrefix="ajaxToolkit" />         </controls>
    </pages>
  • Rename PageTemplates\List ,ListDetails  to ListOrg & ListDetails.org. Copy List\ListDetails from DynamicDataFutures  (so you can use the AdvancedFilterRepeater )
  • Create a directory Filters under Dynamic data. Copy the contents of DynamicDataFutures\Samples\DynamicDataFuturesSample\DynamicData\Filters to your DynamicData\Filters and add all the items in the DynamicData\Filters folder.
  • Add a ref to YourWebProj\Futures\bin\Debug\Microsoft.Web.DynamicData.dll and  AjaxControlToolkit.dll
  • Copy (then add existing items) the following DynamicDataFutures\Samples\DynamicDataFuturesSample\AutocompleteFilter.asmx (and .cs) , AutocompleteStyle.css and CascadeAttribute.cs
  • If you are using NW and want to get the features of the DynamicDataFutures sample, copy NorthwindPartials.cs, ReorderLevelEnum.cs to your project (and add exiting item). 
  • Add App_GlobalResources

  • Copy the Resources.* ONLY  from the DynamicDataFutures sample and add exiting items.
  • Change the namespace of NorthwindPartials.cs to your namespace and add using DynamicDataFuturesSample;
Posted by ricka0 | 1 Comments
Filed under: ,

Tweaking the Filter Repeater

The default implementation of Dynamic Data provides a drop down list box for each foreign key and boolean field in a table. The drop down list allows you to filter the table with the value selected from the drop down list box. The figure below shows a view of the Product table from the AdventureWorksLT database. Several columns are not displayed because the scaffoldcolumnattribute was applied.

The figure below shows the Product table again after Mountain Bikes is selected in the ProductCategory drop down list. Only products with product category Mountain Bikes are displayed.

The drop down list box is implemented in Dynamic Data by the FilterUserControl.ascx user control in the DynamicData\Content folder. While the filter user control is useful to filter a table when there are a relatively few foreign keys used as categories, it can be unmanageable on tables with hundreds or more foreign keys. For example, the CustomerAddress table of the AdventureWorksLT database has over 400 AddressID foreign keys, and each foreign key points to only one address of a customer (either the Main Office or the Shipping address).

When the foreign key field displayed by the filter user control is a very long string, the drop down list box can move off the browser window. This document will offer and approach to limit the width of the drop down list box. The figure below shows the ProductModelProductDescriptions from the AdventureWorksLT database.

In this blog I show how I like to establish a maximum number of entries in the filter repeater and how to set the maximum width.

Modifying the filter user control to limit number of entries

Editing the filter user control

1. Add a key element to the appSettings Element in the web.config file that will set an upper bound to the number of foreign key entries that will be displayed. The following example sets the upper bound to 55. 

<appSettings>
  <add key="FilterUC_FK_limit" value="955"/>
  <add key="FilterUC_Char_limit" value="50"/>
</appSettings>

1 Add a text label to the the filter user control in the FilterUserControl.ascx file found in the DynamicData\Content folder. The following example shows the completed markup.

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

<asp:Label ID="LBL1" runat="server" Text="Label" ForeColor= "Brown" Font-Bold="true" />
<asp:DropDownList ID="DropDownList1" runat="server" AutoPostBack="true" EnableViewState="true" CssClass="droplist">
    <asp:ListItem Text="All" Value="" />
</asp:DropDownList>

3. Add a string property to the FilterUserControl. For example, add a property with the name LBL_Txt that will be used to set the label we added to the FilterUserControl.aspx file in a previous step.

4. Add the Page_PreRender method in the FilterUserControl code behind file and set the label text. For example, set the label text with the LBL_Txt property created in the previous step.

5. Modify the Page_Init method in the FilterUserControl code behind file to limit the number of foreign keys displayed. The FilterUC_FK_limit application setting created in the first step will be used to set the maximum number of entries in the drop down list box. The following example shows the completed FilterUserControl code behind file.

Visual Basic

Imports System.Web.DynamicData

Partial Class FilterUserControl
    Inherits System.Web.DynamicData.FilterUserControlBase
    '
    Public LBL_Txt As String
    '
    Public Overrides ReadOnly Property SelectedValue As String
        Get
            Return DropDownList1.SelectedValue
        End Get
    End Property
    '
    Public Event SelectedIndexChanged As EventHandler
    '
    Protected Sub Page_Init(ByVal sender As Object, ByVal e As EventArgs)
        '
        If Page.IsPostBack Then
            Return
        End If
        '
        PopulateListControl(DropDownList1)
        '
        Dim maxCnt As Integer = Convert.ToInt32(ConfigurationManager.AppSettings("FilterUC_FK_limit"))
        If (DropDownList1.Items.Count > maxCnt) Then
            DropDownList1.Visible = False
            LBL1.Visible = False
        End If
        '
        If Not String.IsNullOrEmpty(InitialValue) Then
            DropDownList1.SelectedValue = InitialValue
        End If
        '
    End Sub
    '
    '
    Protected Sub Page_PreRender(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.PreRender
        LBL1.Text = LBL_Txt
    End Sub
End Class

C#

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Collections.Specialized;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Xml.Linq;
using System.Web.DynamicData;

public partial class FilterUserControl : System.Web.DynamicData.FilterUserControlBase {

    public string LBL_Txt { get; set; }

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

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

    protected void Page_PreRender(object sender, EventArgs e) {
        LBL1.Text = LBL_Txt;
    }

    protected void Page_Init(object sender, EventArgs e) {

        if (Page.IsPostBack)
            return; PopulateListControl(DropDownList1);

        int maxCnt = Convert.ToInt32(ConfigurationManager.AppSettings["FilterUC_FK_limit"]); 
        if (DropDownList1.Items.Count > maxCnt) {
            DropDownList1.Visible = false;
            LBL1.Visible = false;
        }

        if (!String.IsNullOrEmpty(InitialValue))
            DropDownList1.SelectedValue = InitialValue;
    }
}


 

5. Remove the original filter label from the page template (list.aspx and listDetails.aspx) and initialize the new label. The original markup is shown below.

<asp:FilterRepeater ID="FilterRepeater" runat="server">
    <ItemTemplate>
       <asp:Label runat="server" Text='<%# Eval("DisplayName") %>' AssociatedControlID="DynamicFilter$DropDownList1" />
        <asp:DynamicFilter runat="server" ID="DynamicFilter" OnSelectedIndexChanged="OnFilterSelectedIndexChanged" />
     </ItemTemplate>
     <FooterTemplate><br /><br /></FooterTemplate>
 </asp:FilterRepeater>

The replacement markup is shown below.

<ItemTemplate>
    <asp:DynamicFilter runat="server" ID="DynamicFilter" LBL_Txt='<%# Eval("DisplayName") %>'
                      OnSelectedIndexChanged="OnFilterSelectedIndexChanged" />
  </ItemTemplate>

 

Part II:  Limiting the width of the filter user control Part II:

To limit the width of the drop down list box, the elements of the list box will be truncated at the maximum allowed character width.Editing the filter user control for maximum characters1. Add a key element to the appSettings Element in the web.config file that will set an upper bound on the number of characters that will be copied to each element of the drop downn list. The key with value FilterUC_Char_limit sets the maximum number of characters to 50.

<appSettings>
  <add key="FilterUC_FK_limit" value="955"/>
  <add key="FilterUC_Char_limit" value="50"/>
</appSettings>

2. Modify the Page_Init method in the FilterUserControl code behind file to limit the number of characters to be copied to the drop down list box. The following example shows how to limit the number of characters.

C#

int maxLen = Convert.ToInt32(ConfigurationManager.AppSettings["FilterUC_Char_limit"]);

for (int i = 0; i < DropDownList1.Items.Count; i++) {
   string s = DropDownList1.Items[i].Text;

   if (s.Length > maxLen) {
      s = s.Substring(0, maxLen - 1);
      s += " ... (see Details for full)";
      DropDownList1.Items[i].Text = s;
   }
}

Visual Basic

Dim maxLen As Integer = Convert.ToInt32(ConfigurationManager.AppSettings("FilterUC_Char_limit"))
   For i As Integer = 0 To DropDownList1.Items.Count - 1

       Dim s As String = DropDownList1.Items(i).Text

       If s.Length > maxLen Then
           s = s.Substring(0, maxLen - 1)
           s += " ... (see Details for full)"
           DropDownList1.Items(i).Text = s
       End If
   Next

The figure below shows the results with the ProductModelProductDescription table.

Posted by ricka0 | 3 Comments
Filed under: ,
 
Page view tracker