In This Chapter
• Metadata Classes at a Glance
• The MetaForeignKeyColumn Class
• The MetaChildrenColumn Class
All components of Dynamic Data applications, including field templates, entity templates, filter templates, URL routes, and page templates, rely on the Metadata API to implement functionality appropriate for each particular element and combine them in complete websites. This chapter discusses the fundamental parts of the Metadata API you need to understand in order to work with Dynamic Data effectively.
The Metadata API in Dynamic Data consists of three major classes shown in Figure 7.1: MetaModel
, MetaTable,
and MetaColumn
. The MetaModel
class defines the data model as a collection of tables. A single instance of the MetaModel
represents an entire data model class, such as the NorthwindEntities
. Each MetaTable
instance describes a complete entity class like Product and contains one or more MetaColumn
instances that describe their properties, like ProductID
, ProductName
, UnitsInStock,
and UnitPrice
.
At the highest level, the Metadata API represents a meta model—metadata information specific to Dynamic Data. This information is most relevant to Dynamic Data web applications and is exposed using APIs (classes, properties, and methods) designed specifically for this purpose. However, presenting all information required by Dynamic Data applications in special APIs would mean recreating System.Reflection
and System.ComponentModel
APIs, which are well designed and well known, even if not very easy to use. Instead, Dynamic Data tries to strike a balance between creating an API relevant to dynamic web applications and reusing the existing .NET framework APIs.
The next level down includes the metadata information provided by the data model. The data access frameworks, such as LINQ to SQL and Entity Framework, provide metadata information you could use to discover entity classes and access their data. Dynamic Data wraps these APIs in a framework-agnostic provider model defined in the System.Web. DynamicData.Providers
namespace. The provider model is also available through the Metadata API. However, this is an advanced topic that requires special attention. You can find a detailed discussion of this subject in Chapter 13, “Building Dynamic List Pages.”
At the lowest level of abstraction is metadata information provided by the .NET runtime. The Type
class in the System
namespace provides information about classes, properties, methods, and attributes; it is an incredibly powerful but low-level tool. The TypeDescriptor
class in the System.ComponentModel
namespace works at a slightly higher level and has built-in understanding of data annotations. However, using these APIs directly in a Dynamic Data application would require a lot of tedious coding, cause errors, and significantly reduce developer productivity.
The sheer size of the Metadata API (look at the number of properties in the MetaTable
and MetaColumn
classes) can be overwhelming at first. Sections in this chapter call out when a particular component of the Metadata API fits into one of the broad categories just discussed. Of course, trying to shoehorn a complex API into a simple classification does not work in all cases, but hopefully it will help you understand it as you are reading this chapter.
The sample solution accompanying this book includes a simple metadata explorer that allows you to examine the metadata using web browser. Its source code is located in the MetaDataExplorer
folder of the Dynamic Data web application and includes interactive pages representing the MetaModel
, MetaTable
, and MetaColumn
classes. You can access the metadata explorer from any dynamically generated page, such as the Products/List.aspx
, by clicking the Explore Metadata link at the top of the page.
Due to the nature of the subject, discussion of the metadata API can get very abstract. You might find it helpful to have the metadata explorer running on your computer as you are reading this chapter to help you visualize the objects and their relationships. This tool is also helpful when you need to change the data model using Entity Designer or by applying data annotation attributes and see the effect these changes have on the metadata provided by Dynamic Data. Figure 7.2 shows a page describing a MetaTable
object for the Product entity generated by the metadata explorer.
The MetaModel
class serves as a root access point for Dynamic Data components to find information about the tables. In particular, the dynamic URL routing functionality relies on the MetaModel
to determine if the incoming request is valid and route it to the appropriate page template. Although multiple instances are supported, a typical Dynamic Data application contains a single MetaModel
, created and initialized in the Global.asax
as shown in the following example:
public class Global : HttpApplication
{
public static readonly MetaModel DefaultModel = new MetaModel();
void Application_Start(object sender, EventArgs e)
{
DefaultModel.RegisterContext(typeof(NorthwindEntities));
}
}
The MetaModel
instance is initially empty and requires a call to one of the RegisterContext
methods to populate it with MetaTable
objects:
public void RegisterContext(Type contextType);
public void RegisterContext(Func<object> contextFactory);
public void RegisterContext(DataModelProvider dataModelProvider);
In the simplest case, you can initialize the MetaModel
by calling the RegisterContext
method that takes a type object such as typeof(NorthwindEntities)
in the example just shown. MetaModel
has built-in support for Entity Framework and LINQ to SQL and expects a type derived from ObjectContext
or DataContext
, respectively. Using this type, Dynamic Data creates a new instance of the context class whenever a context is required at run time. Any context class registered this way must have a default constructor.
If your context class doesn’t have a default constructor or if you need to perform additional initialization steps, you can use the RegisterContext
method that takes a context factory delegate as a parameter:
public static readonly MetaModel DefaultModel = new MetaModel();
void Application_Start(object sender, EventArgs e)
{
DefaultModel.RegisterContext(this.CreateContext);
}
private object CreateContext()
{
NorthwindEntities context = new NorthwindEntities();
context.ContextOptions.LazyLoadingEnabled = true;
return context;
}
Notice that this example is a bit contrived because you would normally want to rely on the Entity Framework designer, which allows you to choose the default lazy loading option and generate this initialization code as part of the NorthwindEntities
constructor. However, it doesn’t hurt to explicitly do this in a Dynamic Data application, which benefits from lazy loading if you share the data model with other developers, who prefer to have it turned off by default.
When your application uses a LINQ-enabled data access framework other than Entity Framework or LINQ to SQL, you need to use the RegisterContext
method that takes a DataModelProvider
instance instead of the context class. A DataModelProvider
is an object that can extract metadata from a data model and present it to the Dynamic Data metadata API in a format it can consume. Internally, Dynamic Data implements a separate DataModelProvider
for each Entity Framework and LINQ to SQL and gives you the ability to create your own provider to support third-party frameworks, such as SubSonic, LightSpeed, and LinqConnect.
In addition to the three overloads of the RegisterContext
method discussed here, MetaModel
class also includes a second set of similar methods with two parameters each, where the second parameter is a ContextConfiguration
object. You will most commonly use these methods during the initial development stages of your application to make all tables in your model visible by default:
public static readonly MetaModel DefaultModel = new MetaModel();
void Application_Start(object sender, EventArgs e)
{
var configuration = new ContextConfiguration();
configuration.ScaffoldAllTables = true;
DefaultModel.RegisterContext(typeof(NorthwindEntities), configuration);
}
It is probably not a good idea to leave this code in a production application, where you normally want to limit user interface to only those tables you actually want your users to access to prevent confusion and accidental data corruption.
When the RegisterContext
method is called, the MetaModel
uses a data model provider to obtain information about tables the data model supports and creates MetaTable
objects to represent them. When the registration is complete, you can access the tables using a number of different properties and methods:
public ReadOnlyCollection<MetaTable> Tables { get; }
public List<MetaTable> VisibleTables { get; }
If you need to access the list of all tables, you can use the Tables
property. It is a read-only collection of MetaTable
instances, which you can iterate through using a foreach
loop or interrogate using LINQ to Objects. If you are interested only in tables that will be visible to the current user, you can use the VisibleTables
property.
The VisibleTables
property of the MetaModel
class returns a read-only list of tables, despite its type, List<MetaTable>
, which implies the ability to change it. Any changes you make to the VisibleTables
list are ignored, and you should treat it as a read-only collection.
Keep in mind that iterating through the list of tables at runtime can be costly, so if you need a particular table, use one of the methods the MetaModel
class provides for that purpose instead:
public MetaTable GetTable(string uniqueTableName);
public MetaTable GetTable(Type entityType);
The GetTable
method that takes a string that contains a table name is the most efficient way to retrieve a specific table from the MetaModel
. This method retrieves the MetaTable
from the dictionary of table names and objects that MetaModel
maintains internally. You typically use one of these methods when building custom pages that don’t rely on dynamic data URL routing:
MetaTable table = Global.DefaultModel.GetTable("Products");
MetaTable table = Global.DefaultModel.GetTable(typeof(Product));
Although using the GetTable
overload that takes an entity Type
is more elegant (because it allows you to avoid using hard-coded literals or string constants to specify the table name), it is actually less efficient as it iterates through the entire list of tables to find the one with a matching type. Although the performance impact of a single table lookup might not be noticeable, keep in mind that it quickly increases as your data model becomes more complex and as the number of users accessing your application increases.
The GetTable(string)
method is also used by the DynamicDataRouteHandler
when routing dynamic URLs to page templates. It extracts the table name from the URL, calls the GetTable
method of the MetaModel
, and stores the MetaTable
object it returns in the Items
dictionary of the HttpContext
. If you simply need the MetaTable
object for the current request, the fastest way to access it is by calling the static GetRequestMetaTable
method of the DynamicDatarouteHandler
class, as shown here:
MetaTable table = DynamicDataRouteHandler.GetRequestMetaTable(Page.Context);
The GetTable
methods throws an ArgumentException
if you specify a name or type of a table that does not belong to the current MetaModel
. In the unlikely scenario your code expects to find non-existent tables, you can use the TryGetTable
methods instead:
public bool TryGetTable(string uniqueTableName, out MetaTable table);
public bool TryGetTable(Type entityType, out MetaTable table);
The TryGetTable
methods return a Boolean
value that indicates whether or not the table was found. The MetaTable
itself is returned via the second output parameter. It will be null
if the method returns false
. This follows a common .NET pattern and mimics the TryGetValue
method of the generic Dictionary
class:
MetaTable table;
if (Global.DefaultModel.TryGetTable("Products", out table))
{
// use the table here ...
}
By default, when a new MetaModel
instance is created, it is registered globally, making it available through the Default
property of the MetaModel
class as well as its GetModel
method. Both of them are static, thus making the model available to any page or control in your web application:
public static MetaModel Default { get; }
public static MetaModel GetModel(Type contextType);
The Default
property gives you an application-independent way to access the default model, which you would normally get to via the DefaultModel
property of the Global class defined in Global.asax.cs
. In other words, in a typical application, the following lines of code produce the same result:
MetaModel model = Global.DefaultModel;
MetaModel model = MetaModel.Default;
When multiple data contexts have been registered with Dynamic Data, you can also use the static GetModel
method of the MetaModel
class to retrieve a globally registered MetaModel
using its context type:
MetaModel model = MetaModel.GetModel(typeof(NorthwindEntities));
When using Dynamic Data in a large web application that interacts with multiple databases and has multiple models, you might want to avoid global registration to reduce chances of having different parts of the application affecting each other. For this purpose, MetaModel
class provides an overloaded constructor that takes a Boolean
value as a parameter. Passing false
to this constructor prevents the model from being globally registered:
public MetaModel(bool registerGlobally);
Several properties of the MetaModel
class are not related to metadata, but instead provide support for dynamic scaffolding of the different template types used by Dynamic Data applications:
public IFieldTemplateFactory FieldTemplateFactory { get; set; }
public EntityTemplateFactory EntityTemplateFactory { get; set; }
public FilterFactory FilterFactory { get; set; }
The FieldTemplateFactory
property gets or sets an object responsible for creating field templates. The EntityTemplateFactory
is responsible for creating entity templates, and the FilterFactory
is responsible for filter templates. All three of these properties are writeable, allowing you to replace the built-in factories when stock functionality is insufficient.
One particular example of when such a replacement would be beneficial is the limitation of the built-in filter factory, which supports only Boolean, ForeignKey, and Enumeration filters. However, this topic deserves a lot of attention and will be revisited in Chapter 13.
public string DynamicDataFolderVirtualPath { get; set; }
The DynamicDataFolderVirtualPath
property determines where the template factories look for templates. By default, this property is set to "~/DynamicData"
but can be changed to a different location if needed.
The main purpose of the MetaTable
class is to help page templates generate user interface for different entities in the data model. A single MetaTable
instance describes an entire entity class, including its type, properties, attributes, presentation, data access logic, and security permissions.
Each table in the model is identified by a name, which you can access using the Name
property of the MetaTable
class:
public string Name { get; }
Table name usually matches the name of the object context property you would normally use to query the data. In the NorthwindEntities context class, you use the Products
property to retrieve instances of the Product entity class from the Products database table. The MetaTable
instance describing the Product entity would have the name Products
, the same as the name of the NorthwindEntities
property.
You can access the list of table columns using the Columns
property of the MetaTable
class:
public ReadOnlyCollection<MetaColumn> Columns { get; }
public MetaColumn GetColumn(string columnName);
public bool TryGetColumn(string columnName, out MetaColumn column);
The Columns
property returns a collection of read-only MetaColumn
objects, which are discussed in detail later in this chapter. When you need to get a particular table column, the most effective way to do it at run time is by calling the GetColumn
or the TryGetColumn
methods, which take advantage of the internal dictionary of columns that the MetaTable
class maintains. This is much faster than a typical foreach
loop, which is always a good thing in a web application where CPU is a precious resource. As you would expect, the GetColumn
method throws an exception if a column with the specified name cannot be found, and the TryGetColumn
method returns false
and does not throw an exception:
public TableProvider Provider { get; }
The individual column objects are created by the MetaTable
using definitions returned by a table provider, which extracts information from the underlying data access framework. Although normally you should be able to get everything you need directly through the MetaTable
and MetaColumn
objects, you have the option of accessing the original provider, which was used to create and configure the table using the Provider
property of the MetaTable
class:
public Type EntityType { get; }
The EntityType
property of the MetaTable
class returns a Type
object that describes the entity class, such as Product. The Type
class provides access to the .NET runtime metadata describing the entity class, including its properties, methods, events, and so on. Compared to the MetaTable
class itself, the information available through the Type
class is very low-level, and you will rarely need to use it in Dynamic Data applications:
public Type RootEntityType { get; }
The RootEntityType
property, on the other hand, returns a Type
object that describes the root base class of the entity. There is no example of this in the NorthwindEntities data model here, and diving into this topic would exceed the scope of this book, but you can imagine a CRM system that derives entity classes Prospect and Customer from a common base class called Contact. In this scenario, the MetaTable
describing the Customer entity would return typeof(Customer)
as the EntityType
and typeof(Contact)
as the RootEntityType
.
When working with derived entity classes, remember that context class generated by the data model does not have a separate property for querying Customers and another one for querying Prospects. This changes how Dynamic Data determines the names for the MetaTable
instances describing these entities. For a base class like Contact, the MetaTable
name wil be Contacts, plural. However, for a derived class like Prospect, the MetaTable
name will be Prospect, singular.
Entity classes used by Entity Framework and LINQ to SQL can be decorated with a number of different .NET attributes. Some of these attributes have special meaning for Dynamic Data and are represented as distinct properties in the MetaTable
class:
public virtual bool Scaffold { get; }
The Scaffold
property of the MetaTable
class determines whether the table will be visible (or scaffolded) in the pages generated by Dynamic Data. This includes not only the default main page that simply displays a list of all tables, but also the pages that might include hyperlinks to particular tables. For example, if the Suppliers table is not scaffolded, you will not be able to navigate to it from the Product details page:
using System.ComponentModel.DataAnnotations;
[ScaffoldTable(false)]
partial class Supplier { }
By default, the Scaffold
property value is determined by the value of the ScaffoldAllTables
property of the ContextConfiuration
object used to register the context. However, it can be also modified by applying the ScaffoldTableAttribute
to a specific entity class as just shown:
public virtual bool IsReadOnly { get; }
The IsReadOnly
property of the MetaTable
class determines whether users are allowed to modify it. The page templates created by Dynamic Data projects in Visual Studio use this property to hide links that would normally allow users to insert, modify, and delete the table rows:
using System.ComponentModel;
[ReadOnly(true)]
partial class Supplier { }
By default, the IsReadOnly
property returns true
for tables that don’t have a primary key, which could be the case if the MetaTable represents an entity class that was created for a database view, for which the Entity Framework and LINQ to SQL cannot automatically determine the primary key columns. You can also apply the ReadOnlyAttribute
to the entity class as just shown to explicitly specify this value based on your needs:
public virtual string DisplayName { get; }
The DisplayName
property of the MetaTable
class determines the text Dynamic Data page templates display when a human-readable name of the table is required on a dynamically generated web page. By default, the DisplayName
property returns the same value as the Name
property. If, however, the table name is not user-friendly, such as the Order_Details table in the Northwind database, it can be changed by applying the DisplayNameAttribute
to the entity class as shown here:
using System.ComponentModel;
[DisplayName("Order Items")]
partial class Order_Detail { }
Last, the MetaTable
class has a set of three properties that determine how the actual data retrieved from the database table needs to be displayed by the user interface elements generated by Dynamic Data:
public virtual MetaColumn DisplayColumn { get; }
public virtual MetaColumn SortColumn { get;
public virtual bool SortDescending { get; }
The DisplayColumn
property returns a MetaColumn
object that represents the property whose value would allow a user to identify a particular entity instance or table row. For the Supplier entity class in the Northwind database scenario, this would normally be the CompanyName
property; for the Product
entity class, it would be the ProductName
. The read-only ForeignKey field template uses the DisplayColumn
to provide text for a hyperlink it generates, so that contact name of a Supplier is displayed on the Product details page.
The SortColumn
property returns a MetaColumn
that should be used to sort the items retrieved from the database by default, and the SortDescending
property indicates whether the rows should be sorted in ascending or descending order of the SortColumn
values. The ForeignKey_Edit field template uses these properties to sort the items in the drop-down list it generates, making the Suppliers appear in the order of their CompanyName:
using System.ComponentModel.DataAnnotations;
[DisplayColumn("CompanyName", "CompanyName", false)]
partial class Supplier { }
By default, MetaTable
uses a heuristic algorithm to guess the right display column. All three of these properties, DisplayColumn
, SortColumn
, and SortDescending
, can also be specified explicitly by applying the DisplayColumnAttribute
to the entity class as just shown. Its constructor takes the name of the display column, the name of the sort column, and a Boolean
value that indicates whether default sorting should be performed in descending order.
A single display column is often insufficient to provide a meaningful text that identifies rows of a non-trivial entity. Consider a Customer entity where instead of the combined ContactName
as we have in the Northwind sample database, you have separate properties called FirstName
, LastName
, and MiddleName
. You would probably want to use a combination of two or three of these properties as a display value.
A straightforward solution to this problem would be to define a custom property called FullName
, as just shown and specify it as the display column for the entity class by applying the DisplayColumnAttribute
:
[DisplayColumn("FullName")]
partial class Customer
{
public string FullName
{
get { return LastName + ", " + FirstName + " " + MiddleName; }
}
}
Unfortunately, this approach does not work out of the box due to a limitation of the Entity Framework data model provider in Dynamic Data. As a workaround, you can override the ToString
method in your entity class to return the required display string.
partial class Customer
{
public override ToString()
{
return LastName + ", " + FirstName + " " + MiddleName;
}
}
When it comes to populating drop-down lists and generating links to pages, the value returned by the ToString
method takes precedence over the column specified in the DisplayColumnAttribute
and returned by the DisplayColumn
property of the MetaTable
class.
Chapter 13, “Building Dynamic List Pages,” discusses the Dynamic Data provider model in detail and offers a better solution for this problem.
public AttributeCollection Attributes { get; }
All attributes of the entity can be also accessed through the Attributes
property of the MetaTable
class. This includes the built-in attributes from the System.ComponentModel
and System.ComponentModel.DataAnnotations
namespaces discussed in this book as well as any custom attributes you might have created in your application.
In addition to the metadata information, the MetaTable
class also plays an important role in implementing data access logic in Dynamic Data web applications:
public Type DataContextType { get; }
public string DataContextPropertyName { get; }
The DataContextType
property of the MetaTable
class returns a Type
object that represents the data context class generated by the data model. This class is typically derived from the ObjectContext
base class in Entity Framework and the DataContext
class in LINQ to SQL. The DataContextPropertyName
property returns the name of the property you would use to query the table rows, such as the Products property in the NorthwindEntities object context. At run time, this information is used by the DynamicDataManager
control in page templates to configure the EntityDataSource
and LinqDataSource
controls responsible for retrieving entity data and saving changes to the database.
It is significant that DataContextType
is a property of the MetaTable
class and not the MetaModel
. A single MetaModel
instance can have tables from different data contexts registered by separate calls to the RegisterContext
method. If different contexts have the tables with the same name, your code needs to distinguish them by passing the context type to the GetTable
method of the MetaModel
class, along with the table name.
As you might recall from our discussion of the RegisterContext
method of the MetaModel
class, if the object context requires additional initialization tasks, you can provide a factory method when registering it, instead of simply giving it the type:
public virtual object CreateContext();
The CreateContext
method of the MetaTable
class is responsible for creating the context object, either by creating an instance of the registered type or by calling the context factory method, if that’s how the context was registered with the MetaModel
:
public IQueryable GetQuery();
public virtual IQueryable GetQuery(object context);
The MetaTable
class also provides two overloaded GetQuery
methods that can be used to query any table dynamically. This is how the ForeignKey_Edit field template as well as the ForeignKey filter template populate their drop-down list controls. These methods return a LINQ IQueryable
object that you can use to retrieve rows from the database. Chapter 14, “Implementing Security,” discusses how the GetQuery
method can be overridden to implement row-level security.
Support for presentation logic is, by far, the biggest part of the public interface exposed by the MetaTable
class. Here are the important members to consider:
public virtual IEnumerable<MetaColumn> GetScaffoldColumns(
DataBoundControlMode mode, ContainerType containerType);
The GetScaffoldColumns
method is responsible for returning the list of columns that will be displayed by the default page templates. The containerType
parameter specifies whether the columns are needed for a single item view or a list. The mode
parameter specifies whether the current entity is displayed in ReadOnly
, Insert
, or Edit
mode.
public virtual IEnumerable<MetaColumn> GetFilteredColumns();
The GetFilteredColumns
method is used by the QueryableFilterRepeater
control, which generates the filter controls in the List and ListDetails page templates.
public string GetActionPath(string action);
public string GetActionPath(string action, IList<object> primaryKeyValues);
public string GetActionPath(string action, object row);
public string GetActionPath(string action,
RouteValueDictionary routeValues);
The GetActionPath
method is used whenever a hyperlink is needed to a dynamically generated page, which includes the DynamicHyperLink
control as well as the read-only ForeignKey field template. In the simplest form, this method takes a single action parameter, which can be any string, but typically comes from one of the strings defined in the static PageAction
class—Insert
, Edit
, Details
, or List
. The remaining overloads of the GetActionPath
method are used to specify additional parameter values in the query string. This could be limited to just the primary key values of a particular row when generating a link to a single-item page or any column values when generating a link to a prefiltered list page.
public IList<object> GetPrimaryKeyValues(object row);
The primary key values can be extracted from a row (an entity instance) using the GetPrimaryKeyValues
method. This is how the GetActionPath
overload that takes a row object works.
public IDictionary<string, object> GetColumnValuesFromRoute(
HttpContext context);
public DataKey GetDataKeyFromRoute();
When a user clicks one of the dynamic hyperlinks, page templates typically have to extract column values from the request query string. This is done with the help of the GetColumnValuesFromRoute
method, which is used by the list page template and the GetDataKeyFromRoute
method, which does the same thing for single-item templates.
public virtual string GetDisplayString(object row);
When a human-readable string representing a row or an entity is needed, you can use the GetDisplayString
method. This is how the ForeignKey field and filter templates set the Text
property of the ListItem
objects for their DropDownList
controls. This method takes into account the DisplayColumn
of the MetaTable
to locate the column whose value will serve as the display string.
public string GetPrimaryKeyString(IList<object> primaryKeyValues);
public string GetPrimaryKeyString(object row);
If you need a string that can be used to uniquely identify an entity and enable retrieving it from database later, you can use one of the GetPrimaryKeyString
overloads. This is how the ForeignKey field and filter templates generate the Value
property of the ListItem
objects in their DropDownList
controls.
Last, but not least, the MetaTable
class includes a set of virtual methods that can be used to implement table-level authorization. The default implementation of these methods returns true
, making all tables accessible for all users, as long as these tables are scaffolded. Chapter 14 discusses how Dynamic Data applications can take advantage of this extensibility mechanism to implement role-based UI trimming:
public virtual bool CanDelete(IPrincipal principal);
public virtual bool CanInsert(IPrincipal principal);
public virtual bool CanRead(IPrincipal principal);
public virtual bool CanUpdate(IPrincipal principal);
The MetaColumn
class describes a single property of an entity class. In the Northwind data model, where the Product entity class represents the Products database table, Dynamic Data creates a separate instance of the MetaColumn
class for each property the Product
entity class defines, including primitive properties, like ProductID
, ProductName
, CategoryID
, and SupplierID
as well as navigation properties like Category and Supplier.
The navigation properties of entity classes are represented by the MetaForeignKeyColumn
and MetaChildrenColumn
classes derived from the MetaColumn
class. These classes are discussed later in this chapter; this section focuses on the common properties of all meta columns and their specialized descendants.
A .NET property is defined by its name, type, and accessibility of its getter and setter.
public string Name { get; }
The Name
property of the MetaColumn
class returns the name of the entity property. It is a raw, internal string that can be used to identify the column but might not be appropriate for display to end users.
public Type ColumnType { get; }
public TypeCode TypeCode { get; }
The ColumnType
property returns a Type
object that represents the .NET type of the entity property. For the ProductName
property of the Product entity, this would be a typeof(string)
. The TypeCode
property, on the other hand, returns one of the members of the TypeCode
enumeration, and for the ProductName column, this would be TypeCode.String
.
public bool IsBinaryData { get; }
public bool IsFloatingPoint { get; }
public bool IsInteger { get; }
public bool IsLongString { get; }
public bool IsString { get; }
A set of convenience properties is also available to determine a higher-level group of the property’s data types. For example, the IsFloatingPoint
property returns true
for Float
, Double,
and Decimal
columns; the IsInteger
property works similarly for Byte
, Int16
, Int32
, and Int64
columns. The IsLongString
property returns true
if the MaxLength
of the string exceeds one billion characters (1,073,741,819
to be exact). In practical terms, this means memo columns, TEXT
and NTEXT
in Microsoft SQL Server, and determines whether a MultilineText
field template will be used for this column by default.
public int MaxLength { get; }
For binary and string columns, the MaxLength
property returns the maximum length allowed for the column values in the data model. For example, the ProductName column of the Products table is limited to 40 characters, and the MaxLength
property would return 40 by default.
using System.ComponentModel.DataAnnotations;
[MetadataType(typeof(Product.Metadata))]
partial class Product
{
public class Metadata
{
[StringLength(32)]
public object ProductName { get; set; }
}
}
However, the MaxLength
property can also be changed by applying the StringLengthAttribute
to the entity property in code, as just shown.
public virtual bool IsReadOnly { get; }
The IsReadOnly
property of the MetaColumn
class returns true
if the entity property value cannot be set by users. A property can be read-only if its setter is not accessible or doesn’t exist. The latter option does not apply to entity classes generated by Entity Framework and LINQ to SQL, where a property must have a setter for the framework to be able to set its value based on information retrieved from the database. Both of these frameworks, however, support lower visibility (protected
or internal
) for property setters.
In addition to changing definition of the entity property in the model, you can also make it read-only by applying the EditableAttribute
defined in the System.ComponentModel.DataAnnotations
namespace.
using System.ComponentModel.DataAnnotations;
[MetadataType(typeof(Product.Metadata))]
partial class Product
{
public class Metadata
{
[Editable(false)]
public object ProductName { get; set; }
}
}
You can also achieve the same effect and make a property read-only for Dynamic Data purposes by applying the ReadOnlyAttribute
defined in the System.ComponentModel
namespace. However, the EditableAttribute
also allows providing additional metadata information to allow editing separately for Insert and Update modes.
public PropertyInfo EntityTypeProperty { get; }
The EntityTypeProperty
of the MetaColumn
class returns an instance of the PropertyInfo
class defined in the System.Reflection
namespace. PropertyInfo
contains a low-level .NET metadata describing the property. It also allows you to dynamically get or set the value of the entity property by calling the GetValue
and SetValue
methods of the PropertyInfo
class.
public ColumnProvider Provider { get; }
The Provider
property of the MetaColumn
class returns the original ColumnProvider
that was used to create the current MetaColumn
instance from the information extracted from the data model by the metadata provider. Though you should not need to access the underlying column provider directly, it is accessible through this property.
In addition to the name and type of the entity property, Dynamic Data also needs to know what kind of property is it, or in other words, if the property plays a special role in the entity class.
public bool IsPrimaryKey { get; }
The IsPrimaryKey
property of the MetaColumn
class returns true
for entity properties that are a part of the entity’s primary key. With surrogate primary keys being prevalent in database design today, most entity classes have a single property in their primary keys. However, compound primary keys are also supported. Primary key columns can be accessed quickly using the PrimaryKeyColumns
property of the MetaTable
class.
An entity class must have a primary key for Entity Framework and LINQ to SQL to be able to update rows in its database table. When reverse-engineering an existing database into a data model, Visual Studio can automatically determine the primary keys for tables but not views. If your data model must include views, you might have to edit the model manually to provide the missing information about primary keys.
public bool IsForeignKeyComponent { get; }
The IsForeignKeyComponent
property of the MetaColumn
class returns true
for those primitive properties that are a part of one or more navigation foreign key properties. For example, the SupplierID property of the Product entity is a component of a foreign key represented by the Supplier
navigation property and the IsForeignKeyComponent
property of the MetaColumn
object describing the SupplierID property returns true
.
The foreign key component properties have a special meaning in Dynamic Data applications because they should not be presented to the users under normal circumstances. It would not be very helpful to have a TextBox
for entering SupplierID
values in raw integer form. Instead, the navigation property, Supplier
in this case, is presented as a DropDownList
, or another appropriate control, with which users can choose a supplier instead of having to enter their IDs.
public bool IsGenerated { get; }
The IsGenerated
property of the MetaColumn
class returns true
for those properties whose values are automatically generated by the database server. With Microsoft SQL Server, this includes IDENTITY
columns for which the server generates an incremental value for each new row, ROWVERSION
columns for which the server generates incremental values with every insert and update, as well as the computed columns defined at the database table level.
Dynamic Data always chooses a read-only field template to display values of generated columns, regardless of whether the entity itself is displayed in Insert or Edit mode.
public bool IsCustomProperty { get; }
The IsCustomProperty
of the MetaColumn
class returns true
for properties defined by developers in code as opposed to the normal properties defined in the data model itself. Out of the box, custom properties cannot be used for sorting or filtering purposes. The framework also cannot verify that they are persisted in the database correctly. Because of these reasons, Dynamic Data makes a safe bet and does not create filter or field controls for custom properties by default; although it is possible to change this behavior by applying UIHintAttribute
and FilterUIHintAttribute
to the property in code.
As of .NET version 4.0, service pack 1, the Dynamic Data provider for Entity Framework does not support custom properties, and you will not find MetaColumn
objects describing them. This is why it is impossible to use custom properties as display columns. As mentioned earlier, this problem can be solved by extending the metadata provider, which is discussed in Chapter 13.
A large number of the MetaColumn
public interface is dedicated to data annotations—the .NET attributes that can be applied by developers to the entity properties in code.
public AttributeCollection Attributes { get; }
All attributes applied to a particular entity property can be accessed through the Attributes
property of the MetaColumn
class. Similar to the Attributes
property of the MetaTable
class, this property also includes the built-in attributes from the System.ComponentModel
and System.ComponentModel.DataAnnotations
namespaces as well as any custom attributes that you might have created in your application.
Although it is possible to get all data annotations directly through the Attributes collection, doing so would require scanning it repeatedly, which would quickly drain the precious CPU resources of your web server. Instead, Dynamic Data class scans the attributes only once, when the MetaColumn
object is created, and stores their values in fields of the object for instantaneous access later.
Several data annotations and MetaColumn
properties play an important role in the scaffolding process, where Dynamic Data determines whether a particular column needs to be represented by a field or filter control on a page and how the template for that control is selected.
public virtual bool Scaffold { get; set; }
The Scaffold
property of the MetaColumn
class determines whether Dynamic Data will generate a field template for the entity property it represents. By default, Scaffold
returns false
for automatically generated, custom, and primitive properties that are a part of a foreign key. You can change the default value of this property by applying the ScaffoldColumnAttribute
or the DisplayAttribute
with the AutoGenerateField
property value specified.
public virtual string UIHint { get; }
The UIHint
property of the MetaColumn
class returns the name of a field template that Dynamic Data tries first when generating a control for the entity property. The UIHint
property returns a blank string unless you apply the UIHintAttribute
to the entity property in code.
public DataTypeAttribute DataTypeAttribute { get; }
The DataTypeAttribute
property returns the DataTypeAttribute
that was applied to the entity property in code. The UIHint
and data type take precedence over the more generic template names based on .NET types that Dynamic Data tries when searching for an appropriate field template.
public string FilterUIHint { get; }
The FilterUIHint
property is similar to the UIHint
but specifies the name of a filter template. It also returns a blank string by default and can be specified by applying the FilterUIHintAttribute
to the entity property. Dynamic Data uses filter hints when generating filter controls, typically as part of a QueryableFilterRepeater
in the List page template.
public bool AllowInitialValue { get; }
The AllowInitialValue
property of the MetaColumn determines whether the entity property can have a value entered in Insert mode. The AllowInitialValue
returns false for generated and read-only properties. You can also override it by applying the EditableAttribute
to the entity property in code with the AllowInitialValue
specified.
Several properties of the MetaColumn
class provide support for business logic based on data annotations. This number is surprisingly small, as it covers only three of the attributes discussed in Chapter 3, “Field Templates.”
public object DefaultValue { get; }
The DefaultValue
property of the MetaColumn
class returns a value used by default for the entity property in Insert mode. It can be specified in code by applying the DefaultValueAttribute
to the entity property in code.
Dynamic Data, or more specifically the EntityDataSource, does not create a new entity instance when generating an insert page. This is significant, because even though the Entity Framework designer allows you to define default property values in the model, the stock code generators produce entity classes where the default values are assigned in code and entity properties do not have the DefaultValueAttribute
.
This results in inconsistent behavior when an entity instance is created in code as opposed to the Dynamic Data UI. Although it is straightforward to apply the DefaultValueAttribute
manually, doing so would be prone to errors in large data models. Instead, consider switching to T4-based text templates for generating entity classes and modifying them to automatically generate the DefaultValueAttribute
.
public bool IsRequired { get; }
The IsRequired
property of the MetaColumn
returns true if the entity property must have a value. By default, this property returns True for non-nullable entity properties. You can also set it by applying the RequiredAttribute
to the entity property in code.
Several properties of the MetaColumn
define textual labels describing the entity property that are or can be displayed to the end users.
public virtual string DisplayName { get; }
The DisplayName
property returns a string that serves as a label for the entity property on dynamically generated single-item pages, such as Insert, Edit, and Details.
public virtual string ShortDisplayName { get; }
The ShortDisplayName
returns a string used in a header of a grid column that displays the entity property values on a dynamically generated List page.
public virtual string Prompt { get; }
The Prompt
property returns a string that could be used as a watermark in Edit and Insert mode field templates. None of the field templates supplied with Dynamic Data project templates take advantage of this property. However, you could easily implement support for it with the TextBoxWatermarkExtender
from the AJAX control toolkit.
All three of these properties—DisplayName
, ShortDisplayName
, and Prompt
—can be specified in code by applying the DisplayAttribute
to the entity property.
public virtual string Description { get; }
The Description
property of the MetaColumn class returns the description specified in code by applying the DescriptionAttribute
to the entity property. The built-in field templates Dynamic Data provides do not currently utilize this property. However, you can easily modify them and use this property to generate a tooltip for the data controls in field templates.
The last remaining data annotation properties worth mentioning are the ones related to formatting options that can be specified by applying the DisplayFormatAttribute
to the entity property. These properties are used by the DynamicControl
and DynamicField
controls to implement flexible and consistent formatting of field values.
public string DataFormatString { get; }
The DataFormatString
property returns a string that contains a .NET format string, such as "{0:C}"
, that will be used to convert field value to a string by calling the Format
method of the String
class. Although including the parameter number 0
might seem unnecessary, knowing that there is only one field value to format, it is required for the format string to work properly.
public bool ApplyFormatInEditMode { get; }
The ApplyFormatInEditMode
property determines whether Dynamic Data will use the DataFormatString
not only in Read-only mode and on List and Details pages, but also in Edit mode as well.
public string NullDisplayText { get; }
The NullDisplayText
property specifies the text Dynamic Data will display when the field value is null.
public bool ConvertEmptyStringToNull { get; }
The ConvertEmptyStringToNull
property determines whether NullDisplayText
will be used for empty strings as well.
public bool HtmlEncode { get; }
The HtmlEncode
property determines whether any special characters that might be a part of the field value will be encoded using HTML escape sequences. This property is true by default, making the dynamically generated pages immune against HTML injection attacks. You should only consider changing it when your application allows users to enter formatted text in HTML form and performs thorough cleansing of the values entered by users to remove dangerous HTML tags, such as <script>
, before saving them in the database.
The MetaForeignKeyColumn
class is a specialized descendant of the MetaColumn
class that represents many-to-one and one-to-one navigation properties, such as the Supplier
property of the Product entity in the Northwind data model. These “foreign key” columns in the Dynamic Data metadata API have additional attributes that you can access through this class.
public MetaTable ParentTable { get; }
The ParentTable
property returns a MetaTable
object describing the parent entity referenced by the foreign key. In case of the Supplier
property of the Product entity, the ParentTable
property returns the MetaTable
object describing the Supplier entity. This property serves an important role in implementing filtering and navigation functionality. In particular, it is used by the ForeignKey field and filter templates to retrieve the rows from the parent table for display in a drop-down list.
public ReadOnlyCollection<string> ForeignKeyNames { get; }
The ForeignKeyNames
property of the MetaForeignKeyColumn
class returns a collection of the primitive entity properties that form the foreign key. For the Supplier
property of the Product entity, this property returns a collection with a single column name—SupplierID. The collection returned by this property has multiple items only if the parent table has a compound primary key and the foreign key includes multiple primitive properties.
The remaining properties and methods of the MetaForeignKeyColumn
class provide support for implementing presentation logic in Dynamic Data applications.
public bool IsPrimaryKeyInThisTable { get; }
The IsPrimaryKeyInThisTable
property of the MetaForeignKeyColumn
class returns true
if any of the primitive components of the foreign key is a member of the primary key of the entity itself. For example, the Order_Detail
entity in the Northwind data model has a compound primary key that consists of OrderID
and ProductID
properties. The Order
navigation property is represented by a MetaForeignKeyColumn
whose IsPrimaryKeyInThisTable
returns true
because OrderID
is a part of the primary key of the Order_Detail
entity.
Dynamic Data does not allow editing primary key values by default and uses the IsPrimaryKeyInThisTableProperty
property to display read-only field templates for foreign key columns that are a part of a primary key in Edit mode.
public IList<object> GetForeignKeyValues(object row);
The GetForeignKeyValues
method of the MetaForeignKeyColumn
class returns a list values of the primitive entity properties that form the foreign key of a given row or entity instance. Using the Products table of the Northwind database as an example, the product called “Chai
” has a SupplierID equal to 1
; calling the GetForeignKeyValues
method for this row returns a list with a single Int32
value (1). The order of values returned by this method matches the order of column names returned by the ForeignKeyNames
property.
public string GetForeignKeyPath(string action, object row);
public string GetForeignKeyPath(string action, object row, string path);
The GetForeignKeyPath
methods of the MetaForeignKeyColumn
class extract the list of foreign key values from the specified row by calling the GetForeignKeyValues
method and then pass it to the GetActionPath
method of the ParentTable
. The action
parameter is typically specified using one of the values defined in the PageAction
static class. The row
parameter is an instance of the entity that contains the foreign key. And path
can be specified when additional query parameters need to be specified in the query string.
Similar to the GetActionPath
method of the MetaTable
class, the GetForeignKeyPath
methods are used when a hyperlink is needed to navigate from a child entity to a parent entity. This is how the read-only ForeignKey field template generates the URL of its hyperlink.
public string GetForeignKeyString(object row);
The GetForeignKeyString
method calls the GetForeignKeyValues
method to obtain values of the primitive foreign key columns and converts them to a string where individual values are separated by commas. For the Supplier
foreign key of the Product entity, this would return a single SupplierID
value, such as “1” for the “Chai” product.
This method serves as a counterpart for the GetPrimaryKeyString
method of the MetaColumn
class discussed earlier. The ForeignKey_Edit field template uses the GetPrimaryKeyString
method to generate values for the drop-down list items representing rows from the parent table. To select the current value of the foreign key property in the drop-down list, it calls the GetForeignKeyString
method.
public void ExtractForeignKey(IDictionary dictionary, string value);
The ExtractForeignKey
method is the opposite of the GetForeignKeyString
. It takes a comma-separated list of foreign key-Edit values and extracts them into a dictionary. Field templates use this method to implement data-binding logic, extract a current foreign key value from their web controls, and pass it to a data source control. In particular, if the “Exotic Liquids” is selected as a supplier for the “Chai” product, the ExtractForeignKey
method will receive a string that contains ID of the supplier - “1
” and add value 1
with key “SupplierID
” to the dictionary.
The MetaChildrenColumn
class is another specialized descendant of the MetaColumn
class that represents one-to-many and many-to-many navigation properties. In one-to-many relationships, MetaChildrenColumn
is the opposite of the MetaForeignKeyColumn
. For example, the Supplier entity of the Northwind data model has a one-to-many property called Products, which returns all products a particular supplier offers.
public MetaTable ChildTable { get; }
The ChildTable
property of the MetaChildrenColumn
class returns a MetaTable
object describing the child entity referenced by the navigation property. For the Products property of the Supplier entity, the child entity is Product and the ChildTable
property of the MetaChildrenColumn
object would return the Products MetaTable
.
public bool IsManyToMany { get; }
The IsManyToMany
property returns true
if the column represents a many-to-many relationship and false
if the relationship is one-to-many. In the Northwind data model, the Employee entity has a many-to-many relationship with Territory, and the MetaChildrenColumn
instance describing the Territories column has the IsManyToMany
property set to true
.
LINQ to SQL does not support many-to-many associations natively. The IsManyToMany
property is always false for columns describing LINQ to SQL data models.
public MetaColumn ColumnInOtherTable { get; }
The ColumnInOtherTable
property of the MetaChildrenColumn
class returns a MetaColumn
object that represents the opposite end of the association. For the Products property of the Supplier entity, this property returns a MetaColumn
that represents the Supplier
property of the Product entity.
This property can return a MetaForeignKeyColumn
object if the children column represents a one-to-many navigation property, such as the Products property of the Supplier entity, whose counterpart is the Supplier property of the Product entity. It can also return a MetaChildrenColumn
object if the children column represents a many-to-many navigation property, such as the Territories
property of the Employee entity, whose counterpart is the Employees
property of the Territory entity. This property can also return null
if the navigation property represents a one-way association, or in other words, when the children navigation property doesn’t have a counterpart on the other side.
public string GetChildrenPath(string action, object row);
public string GetChildrenPath(string action, object row, string path);
The GetChildrenPath
methods of the MetaChildrenColumn
class are used when a hyperlink is needed to navigate from a parent entity to its children. They retrieve the primary key values from the specified row
and pass them to the GetActionPath
method of the ChildTable
. The generated URL contains the route to the child table, including name of the page template based on the specified action
and primary key names and values in the query. This is how the Children field template generates a hyperlink that takes the user from the parent entity to the List page of the child entity, filtered by the parent’s primary key.
Dynamic Data metadata API provides the information web applications need to generate pages dynamically. The MetaModel
class serves as the entry point for registering data models and accessing the metadata. The MetaTable
class describes a single entity as a collection of attributes, such as DisplayName
and SortColumn
and a collection of MetaColumn
objects that describe the entity properties. The MetaColumn
class describes primitive entity properties and has two descendant classes—MetaForeignKeyColumn
, which describes many-to-one and one-to-one navigation properties, and MetaChildrenColumn
, which describes one-to-many and many-to-many navigation properties.
The metadata API in Dynamic Data relies on metadata providers, which are special classes designed to extract metadata information from the underlying data access framework and present it to the meta model classes in a uniform format. Dynamic Data ships with built-in providers for Entity Framework and LINQ to SQL and allows creating custom providers for other data access frameworks.
3.144.37.196