Chapter 7. Metadata API

In This Chapter

Metadata Classes at a Glance

The MetaModel Class

The MetaTable Class

The MetaColumn Class

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.

Metadata Classes at a Glance

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.

Image

Figure 7.1. Metadata API at a glance.

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.

Metadata Explorer

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.

Image

Figure 7.2. Metadata explorer.

The MetaModel Class

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));
  }
}

Data Model Registration

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.

Accessing MetaTable Objects

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.


Note

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.


Note

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 ...
}

Global Model Registration

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);

Scaffolding Support

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 MetaTable Class

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.

Entity Definition

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.


Note

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.


Data Annotations

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.


Note

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.

Data Access Support

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.


Note

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.

Presentation Logic Support

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.

Security Support

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

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.

Property Definition

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.

Property Kind

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.


Note

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.


Note

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.


Data Annotations

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.

Scaffolding Support

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.

Business Logic Support

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.


Note

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.

Display Support

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.

Formatting Options

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

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.

Presentation Logic

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.


Note

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

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.


Note

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.

Summary

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.37.196