|
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Security.Principal;
using System.Web.DynamicData.ModelProviders;
using System.Web.DynamicData.Util;
using System.Web.Resources;
using System.Web.Routing;
using System.Web.UI;
using System.Web.UI.WebControls;
using AttributeCollection = System.ComponentModel.AttributeCollection;
namespace System.Web.DynamicData {
/// <summary>
/// Represents a database table for use by dynamic data pages
/// </summary>
public class MetaTable : IMetaTable {
private const int DefaultColumnOrder = 10000;
private Dictionary<string, MetaColumn> _columnsByName;
private HttpContextBase _context;
private MetaColumn _displayColumn;
private string _foreignKeyColumnsNames;
private bool? _hasToStringOverride;
private MetaTableMetadata _metadata;
private ReadOnlyCollection<MetaColumn> _primaryKeyColumns;
private string[] _primaryKeyColumnNames;
private bool _scaffoldDefaultValue;
private MetaColumn _sortColumn;
private bool _sortColumnProcessed;
private TableProvider _tableProvider;
private string _listActionPath;
/// <summary>
/// A collection of attributes declared on this entity type (i.e. class-level attributes).
/// </summary>
public AttributeCollection Attributes {
get {
return Metadata.Attributes;
}
}
/// <summary>
/// All columns
/// </summary>
public ReadOnlyCollection<MetaColumn> Columns {
get;
// internal for unit testing
internal set;
}
// for unit testing
internal HttpContextBase Context {
private get {
return _context ?? new HttpContextWrapper(HttpContext.Current);
}
set {
_context = value;
}
}
/// <summary>
/// Name of table coming from the property on the data context. E.g. the value is "Products" for a table that is part of
/// the NorthwindDataContext.Products collection.
/// </summary>
public string DataContextPropertyName {
get {
return _tableProvider.DataContextPropertyName;
}
}
/// <summary>
/// The type of the data context this table belongs to.
/// </summary>
public Type DataContextType {
get {
return Provider.DataModel.ContextType;
}
}
/// <summary>
/// Returns the column being used for display values when entries in this table are used as parents in foreign key relationships.
/// Which column to use can be specified using DisplayColumnAttribute. If the attribute is not present, the following heuristic is used:
/// 1. First non-PK string column
/// 2. First PK string column
/// 3. First PK non-string column
/// 4. First column
/// </summary>
public virtual MetaColumn DisplayColumn {
get {
// use a local to avoid a null value if ResetMetadata gets called
var displayColumn = _displayColumn;
if (displayColumn == null) {
displayColumn = GetDisplayColumnFromMetadata() ?? GetDisplayColumnFromHeuristic();
_displayColumn = displayColumn;
}
return displayColumn;
}
}
/// <summary>
/// Gets the string to be user-friendly string representing this table. Defaults to the value of the Name property.
/// Can be customized using DisplayNameAttribute.
/// </summary>
[SuppressMessage("Microsoft.Security", "CA2119:SealMethodsThatSatisfyPrivateInterfaces",
Justification = "Interface denotes existence of property, not used for security.")]
public virtual string DisplayName {
get {
return Metadata.DisplayName ?? Name;
}
}
/// <summary>
/// Return the type of the Entity represented by this table (e.g. Product)
/// </summary>
public Type EntityType {
get {
return Provider.EntityType;
}
}
/// <summary>
/// Get a comma separated list of foreign key names. This is useful to set the IncludePaths on an EntityDataSource
/// </summary>
public string ForeignKeyColumnsNames {
get {
if (_foreignKeyColumnsNames == null) {
var fkColumnNamesArray = Columns.OfType<MetaForeignKeyColumn>().Select(column => column.Name).ToArray();
_foreignKeyColumnsNames = String.Join(",", fkColumnNamesArray);
}
return _foreignKeyColumnsNames;
}
}
/// <summary>
/// Returns true if the table has a primary key
/// </summary>
public bool HasPrimaryKey {
get {
// Some of the columns may be primary keys, but if this is a view, it doesn't "have"
// any primary keys, so PrimaryKey is null.
return PrimaryKeyColumns.Count > 0;
}
}
private bool HasToStringOverride {
get {
// Check if the entity type overrides ToString()
//
if (!_hasToStringOverride.HasValue) {
MethodInfo toStringMethod = EntityType.GetMethod("ToString");
_hasToStringOverride = (toStringMethod.DeclaringType != typeof(object));
}
return _hasToStringOverride.Value;
}
}
/// <summary>
/// Returns true if this is a read-only table or view(has not PK).
/// </summary>
[SuppressMessage("Microsoft.Security", "CA2119:SealMethodsThatSatisfyPrivateInterfaces",
Justification = "Interface denotes existence of property, not used for security.")]
public virtual bool IsReadOnly {
get {
return Metadata.IsReadOnly || !HasPrimaryKey;
}
}
/// <summary>
/// Gets the action path to the list action for this table
/// </summary>
public string ListActionPath {
get {
return _listActionPath ?? GetActionPath(PageAction.List);
}
internal set {
_listActionPath = value;
}
}
private MetaTableMetadata Metadata {
get {
// use a local to avoid returning null if ResetMetadata gets called
var metadata = _metadata;
if (metadata == null) {
metadata = new MetaTableMetadata(this);
_metadata = metadata;
}
return metadata;
}
}
/// <summary>
/// The model this table belongs to.
/// </summary>
public MetaModel Model { get; private set; }
/// <summary>
/// Unique name of table. This name is unique within a given data context. (e.g. "MyCustomName_Products")
/// </summary>
public string Name {
get;
private set;
}
/// <summary>
/// Columns that constitute the primary key of this table
/// </summary>
public ReadOnlyCollection<MetaColumn> PrimaryKeyColumns {
get {
if (_primaryKeyColumns == null) {
_primaryKeyColumns = Columns.Where(c => c.IsPrimaryKey).ToList().AsReadOnly();
}
return _primaryKeyColumns;
}
}
internal string[] PrimaryKeyNames {
get {
if (_primaryKeyColumnNames == null) {
_primaryKeyColumnNames = PrimaryKeyColumns.Select(c => c.Name).ToArray();
}
return _primaryKeyColumnNames;
}
}
/// <summary>
/// The underlying provider for this column
/// </summary>
public TableProvider Provider { get { return _tableProvider; } }
/// <summary>
/// Return the root type of this entity's inheritance hierarchy; if the type is at the top
/// of an inheritance hierarchy or does not have any inheritance, will return EntityType.
/// </summary>
public Type RootEntityType {
get {
return Provider.RootEntityType;
}
}
/// <summary>
/// Whether or not to scaffold. This can be customized using ScaffoldAttribute
/// </summary>
[SuppressMessage("Microsoft.Security", "CA2119:SealMethodsThatSatisfyPrivateInterfaces",
Justification = "Interface denotes existence of property, not used for security.")]
public virtual bool Scaffold {
get {
return Metadata.ScaffoldTable ?? _scaffoldDefaultValue;
}
}
/// <summary>
/// Gets the column used as the sorting column when used FK relationships. Defaults to the same column that is returned by DisplayColumn.
/// Can be customized using options on DisplayColumnAttribute.
/// </summary>
public virtual MetaColumn SortColumn {
get {
if (!_sortColumnProcessed) {
var displayColumnAttribute = Metadata.DisplayColumnAttribute;
if (displayColumnAttribute != null && !String.IsNullOrEmpty(displayColumnAttribute.SortColumn)) {
if (!TryGetColumn(displayColumnAttribute.SortColumn, out _sortColumn)) {
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,
DynamicDataResources.MetaTable_CantFindSortColumn,
displayColumnAttribute.SortColumn,
Name));
}
if (_sortColumn is MetaChildrenColumn) {
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,
DynamicDataResources.MetaTable_CantUseChildrenColumnAsSortColumn,
_sortColumn.Name,
Name));
}
}
_sortColumnProcessed = true;
}
return _sortColumn;
}
}
/// <summary>
/// Returns true if the entries in this column are meant to be sorted in a descending order when used as parents in a FK relationship.
/// Can be declared using options on DisplayColumnAttribute
/// </summary>
[SuppressMessage("Microsoft.Security", "CA2119:SealMethodsThatSatisfyPrivateInterfaces",
Justification = "Interface denotes existence of property, not used for security.")]
public virtual bool SortDescending {
get {
return Metadata.SortDescending;
}
}
public MetaTable(MetaModel metaModel, TableProvider tableProvider) {
_tableProvider = tableProvider;
Model = metaModel;
}
/// <summary>
/// Build the attribute collection, made publicly available through the Attributes property
/// </summary>
protected virtual AttributeCollection BuildAttributeCollection() {
return Provider.Attributes;
}
/// <summary>
/// Returns whether the passed in user is allowed to delete items from the table
/// </summary>
public virtual bool CanDelete(IPrincipal principal) {
return Provider.CanDelete(principal);
}
/// <summary>
/// Returns whether the passed in user is allowed to insert into the table
/// </summary>
public virtual bool CanInsert(IPrincipal principal) {
return Provider.CanInsert(principal);
}
/// <summary>
/// Returns whether the passed in user is allowed to read from the table
/// </summary>
public virtual bool CanRead(IPrincipal principal) {
return Provider.CanRead(principal);
}
/// <summary>
/// Returns whether the passed in user is allowed to make changes tothe table
/// </summary>
public virtual bool CanUpdate(IPrincipal principal) {
return Provider.CanUpdate(principal);
}
public static MetaTable CreateTable(Type entityType) {
return MetaModel.CreateSimpleModel(entityType).Tables.First();
}
public static MetaTable CreateTable(ICustomTypeDescriptor typeDescriptor) {
return MetaModel.CreateSimpleModel(typeDescriptor).Tables.First();
}
/// <summary>
/// Instantiate a MetaChildrenColumn object. Can be overridden to instantiate a derived type
/// </summary>
/// <returns></returns>
protected virtual MetaChildrenColumn CreateChildrenColumn(ColumnProvider columnProvider) {
return new MetaChildrenColumn(this, columnProvider);
}
/// <summary>
/// Instantiate a MetaColumn object. Can be overridden to instantiate a derived type
/// </summary>
/// <returns></returns>
protected virtual MetaColumn CreateColumn(ColumnProvider columnProvider) {
return new MetaColumn(this, columnProvider);
}
private MetaColumn CreateColumnInternal(ColumnProvider columnProvider) {
if (columnProvider.Association != null) {
switch (columnProvider.Association.Direction) {
case AssociationDirection.OneToOne:
case AssociationDirection.ManyToOne:
return CreateForeignKeyColumn(columnProvider);
case AssociationDirection.ManyToMany:
case AssociationDirection.OneToMany:
return CreateChildrenColumn(columnProvider);
}
Debug.Assert(false);
}
return CreateColumn(columnProvider);
}
internal void CreateColumns() {
var columns = new List<MetaColumn>();
_columnsByName = new Dictionary<string, MetaColumn>(StringComparer.OrdinalIgnoreCase);
foreach (ColumnProvider columnProvider in Provider.Columns) {
MetaColumn column = CreateColumnInternal(columnProvider);
columns.Add(column);
if (_columnsByName.ContainsKey(column.Name)) {
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, DynamicDataResources.MetaTable_ColumnNameConflict,
column.Name, Provider.Name));
}
_columnsByName.Add(column.Name, column);
}
Columns = new ReadOnlyCollection<MetaColumn>(columns);
}
/// <summary>
/// Instantiate a data context that this table belongs to. Uses the instatiotion method specified when the context was registered.
/// </summary>
/// <returns></returns>
public virtual object CreateContext() {
return Provider.DataModel.CreateContext();
}
/// <summary>
/// Instantiate a MetaForeignKeyColumn object. Can be overridden to instantiate a derived type
/// </summary>
/// <returns></returns>
protected virtual MetaForeignKeyColumn CreateForeignKeyColumn(ColumnProvider columnProvider) {
return new MetaForeignKeyColumn(this, columnProvider);
}
/// <summary>
/// Gets the action path for the given row (to get primary key values for query string filters, etc.)
/// </summary>
/// <param name="action"></param>
/// <param name="row">the instance of the row</param>
/// <returns></returns>
public string GetActionPath(string action, object row) {
// Delegate to the overload that takes an array of primary key values
return GetActionPath(action, GetPrimaryKeyValues(row));
}
/// <summary>
/// Gets the action path for the given row (to get primary key values for query string filters, etc.)
/// </summary>
/// <param name="action"></param>
/// <param name="row">the instance of the row</param>
/// <param name="path"></param>
/// <returns></returns>
public string GetActionPath(string action, object row, string path) {
// Delegate to the overload that takes an array of primary key values
return GetActionPath(action, GetPrimaryKeyValues(row), path);
}
/// <summary>
/// Gets the action path for the current table and the passed in action
/// </summary>
public string GetActionPath(string action) {
return GetActionPath(action, (IList<object>)null);
}
/// <summary>
/// Gets the action path for the current table and the passed in action. Also, include all the passed in
/// route values in the path
/// </summary>
/// <returns></returns>
public string GetActionPath(string action, RouteValueDictionary routeValues) {
routeValues.Add(DynamicDataRoute.TableToken, Name);
routeValues.Add(DynamicDataRoute.ActionToken, action);
// Try to get the path from the route
return GetActionPathFromRoutes(routeValues);
}
/// <summary>
/// Gets the action path for the current table and the passed in action. Also, include the passed in
/// primary key as part of the route.
/// </summary>
/// <returns></returns>
public string GetActionPath(string action, IList<object> primaryKeyValues) {
var routeValues = new RouteValueDictionary();
routeValues.Add(DynamicDataRoute.TableToken, Name);
routeValues.Add(DynamicDataRoute.ActionToken, action);
GetRouteValuesFromPK(routeValues, primaryKeyValues);
// Try to get the path from the route
return GetActionPathFromRoutes(routeValues);
}
/// <summary>
/// Use the passed in path and append to it query string parameters for the passed in primary key values
/// </summary>
public string GetActionPath(string action, IList<object> primaryKeyValues, string path) {
// If there is no path, use standard routing
if (String.IsNullOrEmpty(path)) {
return GetActionPath(action, primaryKeyValues);
}
// Get all the PK values in a dictionary
var routeValues = new RouteValueDictionary();
GetRouteValuesFromPK(routeValues, primaryKeyValues);
// Create a query string from it and Add it to the path
return QueryStringHandler.AddFiltersToPath(path, routeValues);
}
private string GetActionPathFromRoutes(RouteValueDictionary routeValues) {
RequestContext requestContext = DynamicDataRouteHandler.GetRequestContext(Context);
string path = null;
if (requestContext != null) {
// Add the model to the route values so that the route can make sure it only
// gets matched if it is meant to work with that model
routeValues.Add(DynamicDataRoute.ModelToken, Model);
VirtualPathData vpd = RouteTable.Routes.GetVirtualPath(requestContext, routeValues);
if (vpd != null) {
path = vpd.VirtualPath;
}
}
// If the virtual path is null, then there is no page to link to
return path ?? String.Empty;
}
/// <summary>
/// Looks up a column by the given name. If no column is found, an exception is thrown.
/// </summary>
/// <param name="columnName"></param>
/// <returns></returns>
public MetaColumn GetColumn(string columnName) {
MetaColumn column;
if (!TryGetColumn(columnName, out column)) {
throw new InvalidOperationException(String.Format(
CultureInfo.CurrentCulture,
DynamicDataResources.MetaTable_NoSuchColumn,
Name,
columnName));
}
return column;
}
private static int GetColumnOrder(MetaColumn column) {
var displayAttribute = column.Metadata.DisplayAttribute;
if (displayAttribute != null && displayAttribute.GetOrder() != null) {
return displayAttribute.GetOrder().Value;
}
return DefaultColumnOrder;
}
private static int GetColumnOrder(MetaColumn column, IDictionary<string, int> groupings) {
var displayAttribute = column.Metadata.DisplayAttribute;
int order;
if (displayAttribute != null) {
string groupName = displayAttribute.GetGroupName();
if (!String.IsNullOrEmpty(groupName) && groupings.TryGetValue(groupName, out order)) {
return order;
}
}
return GetColumnOrder(column);
}
/// <summary>
/// Look for this table's primary key in the route values (i.e. typically the query string).
/// If they're all found, return a DataKey containing the primary key values. Otherwise return null.
/// </summary>
public DataKey GetDataKeyFromRoute() {
var queryStringKeys = new OrderedDictionary(PrimaryKeyNames.Length);
foreach (MetaColumn key in PrimaryKeyColumns) {
// Try to find the PK in the route values. If any PK is not found, return null
string value = Misc.GetRouteValue(key.Name);
if (string.IsNullOrEmpty(value))
return null;
queryStringKeys[key.Name] = Misc.ChangeType(value, key.ColumnType);
}
return new DataKey(queryStringKeys, PrimaryKeyNames);
}
private MetaColumn GetDisplayColumnFromHeuristic() {
// Pick best available option (except for columns based on custom properties)
// 1. First non-PK string column
// 2. First PK string column
// 3. First PK non-string column
// 4. First column (from all columns)
var serverSideColumns = Columns.Where(c => !c.IsCustomProperty).ToList();
return serverSideColumns.FirstOrDefault(c => c.IsString && !c.IsPrimaryKey) ??
serverSideColumns.FirstOrDefault(c => c.IsString) ??
serverSideColumns.FirstOrDefault(c => c.IsPrimaryKey) ??
Columns.First();
}
private MetaColumn GetDisplayColumnFromMetadata() {
var displayColumnAttribute = Metadata.DisplayColumnAttribute;
if (displayColumnAttribute == null) {
return null;
}
MetaColumn displayColumn = null;
if (!TryGetColumn(displayColumnAttribute.DisplayColumn, out displayColumn)) {
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,
DynamicDataResources.MetaTable_CantFindDisplayColumn,
displayColumnAttribute.DisplayColumn,
Name));
}
return displayColumn;
}
/// <summary>
/// Gets the value to be used as the display string for an instance of a row of this table when used in FK relationships.
/// If the row is null, returns an empty string. If the entity class has an overidden ToString() method, returns the result
/// of that method. Otherwise, returns the ToString representation of the value of the display column (as returned by the DisplayColumn
/// property) for the given row.
/// </summary>
/// <param name="row">the instance of the row</param>
/// <returns></returns>
public virtual string GetDisplayString(object row) {
if (row == null)
return String.Empty;
// Make sure it's of the right type, and handle collections
row = PreprocessRowObject(row);
// If there is a ToString() override, use it
if (HasToStringOverride) {
return row.ToString();
}
// Otherwise, use the 'display column'
object displayObject = DataBinder.GetPropertyValue(row, DisplayColumn.Name);
return displayObject == null ? String.Empty : displayObject.ToString();
}
/// <summary>
/// Returns an enumeration of columns that are filterable by default. A column is filterable if it
/// <ul>
/// <li>is decorated with FilterAttribte with Enabled=true</li>
/// <li>is scaffold, is not a custom property, and is either a FK column or a Bool column</li>
/// </ul>
/// The enumeration is ordered by the value of the FilterAttribute.Order property. If a column
/// does not have that attribute, the value 0 is used.
/// </summary>
/// <returns></returns>
public virtual IEnumerable<MetaColumn> GetFilteredColumns() {
IDictionary<string, int> columnGroupOrder = GetColumnGroupingOrder();
return Columns.Where(c => IsFilterableColumn(c, Context.User))
.OrderBy(c => GetColumnOrder(c, columnGroupOrder))
.ThenBy(c => GetColumnOrder(c));
}
private IDictionary<string, int> GetColumnGroupingOrder() {
// Group columns that have groups by group names. Then put them into a dictionary from group name ->
// minimum column order so that groups are "stick" close together.
return Columns.Where(c => c.Metadata.DisplayAttribute != null && !String.IsNullOrEmpty(c.Metadata.DisplayAttribute.GetGroupName()))
.GroupBy(c => c.Metadata.DisplayAttribute.GetGroupName())
.ToDictionary(cg => cg.Key,
cg => cg.Min(c => GetColumnOrder(c)));
}
/// <summary>
/// Get a dictionary of primary key names and their values for the given row instance
/// </summary>
/// <param name="row"></param>
/// <returns></returns>
public IDictionary<string, object> GetPrimaryKeyDictionary(object row) {
row = PreprocessRowObject(row);
Dictionary<string, object> result = new Dictionary<string, object>();
foreach (MetaColumn pkMember in PrimaryKeyColumns) {
result.Add(pkMember.Name, DataBinder.GetPropertyValue(row, pkMember.Name));
}
return result;
}
/// <summary>
/// Get a comma separated list of values representing the primary key for the given row instance
/// </summary>
/// <param name="row"></param>
/// <returns></returns>
public string GetPrimaryKeyString(object row) {
// Make sure it's of the right type, and handle collections
row = PreprocessRowObject(row);
return GetPrimaryKeyString(GetPrimaryKeyValues(row));
}
/// <summary>
/// Get a comma separated list of values representing the primary key
/// </summary>
/// <param name="primaryKeyValues"></param>
/// <returns></returns>
public string GetPrimaryKeyString(IList<object> primaryKeyValues) {
return Misc.PersistListToCommaSeparatedString(primaryKeyValues);
}
/// <summary>
/// Get the value of the primary key components for a given row
/// </summary>
/// <param name="row"></param>
/// <returns></returns>
public IList<object> GetPrimaryKeyValues(object row) {
if (row == null)
return null;
// Make sure it's of the right type, and handle collections
row = PreprocessRowObject(row);
return Misc.GetKeyValues(PrimaryKeyColumns, row);
}
/// <summary>
/// Get the IQueryable for the entity type represented by this table (i.e. IQueryable of Product). Retrieves it from a new context
/// instantiated using the CreateContext().
/// </summary>
/// <returns></returns>
public IQueryable GetQuery() {
return GetQuery(null);
}
/// <summary>
/// Get the IQueryable for the entity type represented by this table (i.e. IQueryable of Product). Retrieves it from the provided
/// context instance, or instantiates a new context using the CreateContext().
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public virtual IQueryable GetQuery(object context) {
if (context == null) {
context = CreateContext();
}
IQueryable query = Provider.GetQuery(context);
if (EntityType != RootEntityType) {
Expression ofTypeExpression = Expression.Call(typeof(Queryable), "OfType", new[] { EntityType }, query.Expression);
query = query.Provider.CreateQuery(ofTypeExpression);
}
// Return the sorted query if there is a sort column
if (SortColumn != null) {
return Misc.BuildSortQueryable(query, this);
}
return query;
}
private void GetRouteValuesFromPK(RouteValueDictionary routeValues, IList<object> primaryKeyValues) {
if (primaryKeyValues != null) {
for (int i = 0; i < PrimaryKeyNames.Length; i++) {
routeValues.Add(PrimaryKeyNames[i], Misc.SanitizeQueryStringValue(primaryKeyValues[i]));
}
}
}
/// <summary>
/// Returns an enumeration of columns that are to be displayed in a scaffolded context. By default all columns with the Scaffold
/// property set to true are included, with the exception of:
/// <ul>
/// <li>Long-string columns (IsLongString property set to true) when the inListControl flag is true</li>
/// <li>Children columns when mode is equal to Insert</li>
/// </ul>
/// </summary>
/// <param name="mode">The mode, such as ReadOnly, Edit, or Insert.</param>
/// <param name="inListControl">A flag indicating if the table is being displayed as an individual entity or as part of list-grid.</param>
/// <returns></returns>
public virtual IEnumerable<MetaColumn> GetScaffoldColumns(DataBoundControlMode mode, ContainerType containerType) {
IDictionary<string, int> columnGroupOrder = GetColumnGroupingOrder();
return Columns.Where(c => IsScaffoldColumn(c, mode, containerType))
.OrderBy(c => GetColumnOrder(c, columnGroupOrder))
.ThenBy(c => GetColumnOrder(c));
}
/// <summary>
/// Gets the table associated with the given type, regardless of which model it belongs to.
/// </summary>
public static MetaTable GetTable(Type entityType) {
MetaTable table;
if (!TryGetTable(entityType, out table)) {
throw new InvalidOperationException(String.Format(
CultureInfo.CurrentCulture,
DynamicDataResources.MetaModel_EntityTypeDoesNotBelongToModel,
entityType.FullName));
}
return table;
}
/// <summary>
/// Perform initialization logic for this table
/// </summary>
internal protected virtual void Initialize() {
foreach (MetaColumn column in Columns) {
column.Initialize();
}
}
internal static bool IsFilterableColumn(IMetaColumn column, IPrincipal user) {
Debug.Assert(column != null);
var displayAttribute = column.Attributes.FirstOrDefault<DisplayAttribute>();
if (displayAttribute != null && displayAttribute.GetAutoGenerateFilter().HasValue) {
return displayAttribute.GetAutoGenerateFilter().Value;
}
if (!String.IsNullOrEmpty(column.FilterUIHint)) {
return true;
}
// non-scaffolded columns should not be displayed by default
if (!column.Scaffold) {
return false;
}
// custom properties won't be queryable by the server
if (column.IsCustomProperty) {
return false;
}
var fkColumn = column as IMetaForeignKeyColumn;
if (fkColumn != null) {
// Only allow if the user has access to the parent table
return fkColumn.ParentTable.CanRead(user);
}
if (column.ColumnType == typeof(bool)) {
return true;
}
if (column.GetEnumType() != null) {
return true;
}
return false;
}
private bool IsScaffoldColumn(IMetaColumn column, DataBoundControlMode mode, ContainerType containerType) {
if (!column.Scaffold) {
return false;
}
// 1:Many children columns don't make sense for new rows, so ignore them in Insert mode
if (mode == DataBoundControlMode.Insert) {
var childrenColumn = column as IMetaChildrenColumn;
if (childrenColumn != null && !childrenColumn.IsManyToMany) {
return false;
}
}
var fkColumn = column as IMetaForeignKeyColumn;
if (fkColumn != null) {
// Ignore the FK column if the user doesn't have access to the parent table
if (!fkColumn.ParentTable.CanRead(Context.User)) {
return false;
}
}
return true;
}
public IDictionary<string, object> GetColumnValuesFromRoute(HttpContext context) {
return GetColumnValuesFromRoute(context.ToWrapper());
}
internal IDictionary<string, object> GetColumnValuesFromRoute(HttpContextBase context) {
RouteValueDictionary routeValues = DynamicDataRouteHandler.GetRequestContext(context).RouteData.Values;
Dictionary<string, object> columnValues = new Dictionary<string, object>();
foreach (var column in Columns) {
if (Misc.IsColumnInDictionary(column, routeValues)) {
MetaForeignKeyColumn foreignKeyColumn = column as MetaForeignKeyColumn;
if (foreignKeyColumn != null) {
// Add all the foreign keys to the column values.
foreach (var fkName in foreignKeyColumn.ForeignKeyNames) {
columnValues[fkName] = routeValues[fkName];
}
}
else {
// Convert the value to the correct type.
columnValues[column.Name] = Misc.ChangeType(routeValues[column.Name], column.ColumnType);
}
}
}
return columnValues;
}
private object PreprocessRowObject(object row) {
// If null, nothing to do
if (row == null)
return null;
// If it's of the correct entity type, we're done
if (EntityType.IsAssignableFrom(row.GetType())) {
return row;
}
// If it's a list, try using the first item
var rowCollection = row as IList;
if (rowCollection != null) {
if (rowCollection.Count >= 1) {
Debug.Assert(rowCollection.Count == 1);
return PreprocessRowObject(rowCollection[0]);
}
}
// We didn't recoginze the object, so return it unchanged
return row;
}
/// <summary>
/// Resets cached table metadata (i.e. information coming from attributes) as well as metadata of all columns.
/// The metadata cache will be rebuilt the next time any metadata-derived information gets requested.
/// </summary>
public void ResetMetadata() {
_metadata = null;
_displayColumn = null;
_sortColumnProcessed = false;
foreach (var column in Columns) {
column.ResetMetadata();
}
}
internal void SetScaffoldAndName(bool scaffoldDefaultValue, string nameOverride) {
if (!String.IsNullOrEmpty(nameOverride)) {
Name = nameOverride;
}
else if (Provider != null) {
Name = Provider.Name;
}
_scaffoldDefaultValue = scaffoldDefaultValue;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
[SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
public override string ToString() {
return Name;
}
/// <summary>
/// Tries to find a column by the given name. If a column is found, it is assigned to the column
/// variable and the method returns true. Otherwise, it returns false and column is null.
/// </summary>
/// <param name="columnName"></param>
/// <param name="column"></param>
/// <returns></returns>
public bool TryGetColumn(string columnName, out MetaColumn column) {
if (columnName == null) {
throw new ArgumentNullException("columnName");
}
return _columnsByName.TryGetValue(columnName, out column);
}
/// <summary>
/// Gets the table associated with the given type, regardless of which model it belongs to.
/// </summary>
public static bool TryGetTable(Type entityType, out MetaTable table) {
MetaModel.CheckForRegistrationException();
if (entityType == null) {
throw new ArgumentNullException("entityType");
}
return System.Web.DynamicData.MetaModel.MetaModelManager.TryGetTable(entityType, out table);
}
#region IMetaTable Members
string[] IMetaTable.PrimaryKeyNames {
get {
return PrimaryKeyNames;
}
}
object IMetaTable.CreateContext() {
return CreateContext();
}
string IMetaTable.GetDisplayString(object row) {
return GetDisplayString(row);
}
IQueryable IMetaTable.GetQuery(object context) {
return GetQuery(context);
}
#endregion
private class MetaTableMetadata {
private DisplayNameAttribute _displayNameAttribute;
private ReadOnlyAttribute _readOnlyAttribute;
public MetaTableMetadata(MetaTable table) {
Debug.Assert(table != null);
Attributes = table.BuildAttributeCollection();
_readOnlyAttribute = Attributes.FirstOrDefault<ReadOnlyAttribute>();
_displayNameAttribute = Attributes.FirstOrDefault<DisplayNameAttribute>();
DisplayColumnAttribute = Attributes.FirstOrDefault<DisplayColumnAttribute>();
ScaffoldTable = Attributes.GetAttributePropertyValue<ScaffoldTableAttribute, bool?>(a => a.Scaffold, null);
}
public AttributeCollection Attributes { get; private set; }
public DisplayColumnAttribute DisplayColumnAttribute {
get;
private set;
}
public string DisplayName {
get {
return _displayNameAttribute.GetPropertyValue(a => a.DisplayName, null);
}
}
public bool? ScaffoldTable {
get;
private set;
}
public bool SortDescending {
get {
return DisplayColumnAttribute.GetPropertyValue(a => a.SortDescending, false);
}
}
public bool IsReadOnly {
get {
return _readOnlyAttribute.GetPropertyValue(a => a.IsReadOnly, false);
}
}
}
ReadOnlyCollection<IMetaColumn> IMetaTable.Columns {
get {
// Covariance only supported on interfaces
return Columns.OfType<IMetaColumn>().ToList().AsReadOnly();
}
}
IMetaModel IMetaTable.Model {
get {
return Model;
}
}
IMetaColumn IMetaTable.DisplayColumn {
get { return DisplayColumn; }
}
IMetaColumn IMetaTable.GetColumn(string columnName) {
return GetColumn(columnName);
}
IEnumerable<IMetaColumn> IMetaTable.GetFilteredColumns() {
// We can remove the of type when we get rid of the Vnext solution since interface covariance support
// was only added in 4.0
return GetFilteredColumns().OfType<IMetaColumn>();
}
IEnumerable<IMetaColumn> IMetaTable.GetScaffoldColumns(DataBoundControlMode mode, ContainerType containerType) {
// We can remove the of type when we get rid of the Vnext solution since interface covariance support
// was only added in 4.0
return GetScaffoldColumns(mode, containerType).OfType<IMetaColumn>();
}
ReadOnlyCollection<IMetaColumn> IMetaTable.PrimaryKeyColumns {
get {
return PrimaryKeyColumns.OfType<IMetaColumn>().ToList().AsReadOnly();
}
}
IMetaColumn IMetaTable.SortColumn {
get {
return SortColumn;
}
}
bool IMetaTable.TryGetColumn(string columnName, out IMetaColumn column) {
MetaColumn metaColumn;
column = null;
if (TryGetColumn(columnName, out metaColumn)) {
column = metaColumn;
return true;
}
return false;
}
bool IMetaTable.CanDelete(IPrincipal principal) {
return CanDelete(principal);
}
bool IMetaTable.CanInsert(IPrincipal principal) {
return CanInsert(principal);
}
bool IMetaTable.CanRead(IPrincipal principal) {
return CanRead(principal);
}
bool IMetaTable.CanUpdate(IPrincipal principal) {
return CanUpdate(principal);
}
}
}
|