696 lines
28 KiB
C#
696 lines
28 KiB
C#
|
using System.Collections;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Collections.ObjectModel;
|
||
|
using System.ComponentModel;
|
||
|
using System.ComponentModel.DataAnnotations;
|
||
|
using System.Diagnostics;
|
||
|
using System.Diagnostics.CodeAnalysis;
|
||
|
using System.Globalization;
|
||
|
using System.Linq;
|
||
|
using System.Web.DynamicData.ModelProviders;
|
||
|
using System.Web.Resources;
|
||
|
using System.Collections.Concurrent;
|
||
|
|
||
|
|
||
|
namespace System.Web.DynamicData {
|
||
|
/// <summary>
|
||
|
/// Object that represents a database or a number of databases used by the dynamic data. It can have multiple different data contexts registered on it.
|
||
|
/// </summary>
|
||
|
public class MetaModel : IMetaModel {
|
||
|
private List<Type> _contextTypes = new List<Type>();
|
||
|
private static object _lock = new object();
|
||
|
private List<MetaTable> _tables = new List<MetaTable>();
|
||
|
private ReadOnlyCollection<MetaTable> _tablesRO;
|
||
|
private Dictionary<string, MetaTable> _tablesByUniqueName = new Dictionary<string, MetaTable>(StringComparer.OrdinalIgnoreCase);
|
||
|
private Dictionary<ContextTypeTableNamePair, MetaTable> _tablesByContextAndName = new Dictionary<ContextTypeTableNamePair, MetaTable>();
|
||
|
private SchemaCreator _schemaCreator;
|
||
|
private EntityTemplateFactory _entityTemplateFactory;
|
||
|
private IFieldTemplateFactory _fieldTemplateFactory;
|
||
|
private FilterFactory _filterFactory;
|
||
|
private static Exception s_registrationException;
|
||
|
private static MetaModel s_defaultModel;
|
||
|
private string _dynamicDataFolderVirtualPath;
|
||
|
private HttpContextBase _context;
|
||
|
private readonly static ConcurrentDictionary<Type, bool> s_registeredMetadataTypes = new ConcurrentDictionary<Type, bool>();
|
||
|
|
||
|
// Use global registration is true by default
|
||
|
private bool _registerGlobally = true;
|
||
|
|
||
|
internal virtual int RegisteredDataModelsCount {
|
||
|
get {
|
||
|
return _contextTypes.Count;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// ctor
|
||
|
/// </summary>
|
||
|
public MetaModel()
|
||
|
: this(true /* registerGlobally */) {
|
||
|
}
|
||
|
|
||
|
public MetaModel(bool registerGlobally)
|
||
|
: this(SchemaCreator.Instance, registerGlobally) {
|
||
|
}
|
||
|
|
||
|
// constructor for testing purposes
|
||
|
internal MetaModel(SchemaCreator schemaCreator, bool registerGlobally) {
|
||
|
// Create a readonly wrapper for handing out
|
||
|
_tablesRO = new ReadOnlyCollection<MetaTable>(_tables);
|
||
|
_schemaCreator = schemaCreator;
|
||
|
_registerGlobally = registerGlobally;
|
||
|
|
||
|
// Don't touch Default.Model when we're not using global registration
|
||
|
if (registerGlobally) {
|
||
|
lock (_lock) {
|
||
|
if (Default == null) {
|
||
|
Default = this;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal HttpContextBase Context {
|
||
|
get {
|
||
|
return _context ?? HttpContext.Current.ToWrapper();
|
||
|
}
|
||
|
set {
|
||
|
_context = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// allows for setting of the DynamicData folder for this mode. The default is ~/DynamicData/
|
||
|
/// </summary>
|
||
|
public string DynamicDataFolderVirtualPath {
|
||
|
get {
|
||
|
if (_dynamicDataFolderVirtualPath == null) {
|
||
|
_dynamicDataFolderVirtualPath = "~/DynamicData/";
|
||
|
}
|
||
|
|
||
|
return _dynamicDataFolderVirtualPath;
|
||
|
}
|
||
|
set {
|
||
|
// Make sure it ends with a slash
|
||
|
_dynamicDataFolderVirtualPath = VirtualPathUtility.AppendTrailingSlash(value);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns a reference to the first instance of MetaModel that is created in an app. Provides a simple way of referencing
|
||
|
/// the default MetaModel instance. Applications that will use multiple models will have to provide their own way of storing
|
||
|
/// references to any additional meta models. One way of looking them up is by using the GetModel method.
|
||
|
/// </summary>
|
||
|
public static MetaModel Default {
|
||
|
get {
|
||
|
CheckForRegistrationException();
|
||
|
return s_defaultModel;
|
||
|
}
|
||
|
internal set { s_defaultModel = value; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the model instance that had the contextType registered with it
|
||
|
/// </summary>
|
||
|
/// <param name="contextType">A DataContext or ObjectContext type (e.g. NorthwindDataContext)</param>
|
||
|
/// <returns>a model</returns>
|
||
|
public static MetaModel GetModel(Type contextType) {
|
||
|
CheckForRegistrationException();
|
||
|
if (contextType == null) {
|
||
|
throw new ArgumentNullException("contextType");
|
||
|
}
|
||
|
MetaModel model;
|
||
|
if (MetaModelManager.TryGetModel(contextType, out model)) {
|
||
|
return model;
|
||
|
}
|
||
|
else {
|
||
|
throw new InvalidOperationException(String.Format(
|
||
|
CultureInfo.CurrentCulture,
|
||
|
DynamicDataResources.MetaModel_ContextDoesNotBelongToModel,
|
||
|
contextType.FullName));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Registers a context. Uses the default ContextConfiguration options.
|
||
|
/// </summary>
|
||
|
/// <param name="contextType"></param>
|
||
|
public void RegisterContext(Type contextType) {
|
||
|
RegisterContext(contextType, new ContextConfiguration());
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Registers a context. Uses the the given ContextConfiguration options.
|
||
|
/// </summary>
|
||
|
/// <param name="contextType"></param>
|
||
|
/// <param name="configuration"></param>
|
||
|
public void RegisterContext(Type contextType, ContextConfiguration configuration) {
|
||
|
if (contextType == null) {
|
||
|
throw new ArgumentNullException("contextType");
|
||
|
}
|
||
|
RegisterContext(() => Activator.CreateInstance(contextType), configuration);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Registers a context. Uses default ContextConfiguration. Accepts a context factory that is a delegate used for
|
||
|
/// instantiating the context. This allows developers to instantiate context using a custom constructor.
|
||
|
/// </summary>
|
||
|
/// <param name="contextFactory"></param>
|
||
|
public void RegisterContext(Func<object> contextFactory) {
|
||
|
RegisterContext(contextFactory, new ContextConfiguration());
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Registers a context. Uses given ContextConfiguration. Accepts a context factory that is a delegate used for
|
||
|
/// instantiating the context. This allows developers to instantiate context using a custom constructor.
|
||
|
/// </summary>
|
||
|
/// <param name="contextFactory"></param>
|
||
|
/// <param name="configuration"></param>
|
||
|
public void RegisterContext(Func<object> contextFactory, ContextConfiguration configuration) {
|
||
|
object contextInstance = null;
|
||
|
try {
|
||
|
if (contextFactory == null) {
|
||
|
throw new ArgumentNullException("contextFactory");
|
||
|
}
|
||
|
contextInstance = contextFactory();
|
||
|
if (contextInstance == null) {
|
||
|
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, DynamicDataResources.MetaModel_ContextFactoryReturnsNull), "contextFactory");
|
||
|
}
|
||
|
Type contextType = contextInstance.GetType();
|
||
|
if (!_schemaCreator.ValidDataContextType(contextType)) {
|
||
|
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, DynamicDataResources.MetaModel_ContextTypeNotSupported, contextType.FullName));
|
||
|
}
|
||
|
}
|
||
|
catch (Exception e) {
|
||
|
s_registrationException = e;
|
||
|
throw;
|
||
|
}
|
||
|
|
||
|
// create model abstraction
|
||
|
RegisterContext(_schemaCreator.CreateDataModel(contextInstance, contextFactory), configuration);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Register context using give model provider. Uses default context configuration.
|
||
|
/// </summary>
|
||
|
/// <param name="dataModelProvider"></param>
|
||
|
public void RegisterContext(DataModelProvider dataModelProvider) {
|
||
|
RegisterContext(dataModelProvider, new ContextConfiguration());
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Register context using give model provider. Uses given context configuration.
|
||
|
/// </summary>
|
||
|
/// <param name="dataModelProvider"></param>
|
||
|
/// <param name="configuration"></param>
|
||
|
[SuppressMessage("Microsoft.Security", "CA2119:SealMethodsThatSatisfyPrivateInterfaces",
|
||
|
Justification = "Interface is not used in any security sesitive code paths.")]
|
||
|
public virtual void RegisterContext(DataModelProvider dataModelProvider, ContextConfiguration configuration) {
|
||
|
if (dataModelProvider == null) {
|
||
|
throw new ArgumentNullException("dataModelProvider");
|
||
|
}
|
||
|
|
||
|
if (configuration == null) {
|
||
|
throw new ArgumentNullException("configuration");
|
||
|
}
|
||
|
|
||
|
if (_registerGlobally) {
|
||
|
CheckForRegistrationException();
|
||
|
}
|
||
|
|
||
|
// check if context has already been registered
|
||
|
if (_contextTypes.Contains(dataModelProvider.ContextType)) {
|
||
|
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, DynamicDataResources.MetaModel_ContextAlreadyRegistered, dataModelProvider.ContextType.FullName));
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
IEnumerable<TableProvider> tableProviders = dataModelProvider.Tables;
|
||
|
|
||
|
// create and validate model
|
||
|
var tablesToInitialize = new List<MetaTable>();
|
||
|
foreach (TableProvider tableProvider in tableProviders) {
|
||
|
RegisterMetadataTypeDescriptionProvider(tableProvider, configuration.MetadataProviderFactory);
|
||
|
|
||
|
MetaTable table = CreateTable(tableProvider);
|
||
|
table.CreateColumns();
|
||
|
|
||
|
var tableNameAttribute = tableProvider.Attributes.OfType<TableNameAttribute>().SingleOrDefault();
|
||
|
string nameOverride = tableNameAttribute != null ? tableNameAttribute.Name : null;
|
||
|
table.SetScaffoldAndName(configuration.ScaffoldAllTables, nameOverride);
|
||
|
|
||
|
CheckTableNameConflict(table, nameOverride, tablesToInitialize);
|
||
|
|
||
|
tablesToInitialize.Add(table);
|
||
|
}
|
||
|
|
||
|
_contextTypes.Add(dataModelProvider.ContextType);
|
||
|
|
||
|
if (_registerGlobally) {
|
||
|
MetaModelManager.AddModel(dataModelProvider.ContextType, this);
|
||
|
}
|
||
|
|
||
|
foreach (MetaTable table in tablesToInitialize) {
|
||
|
AddTable(table);
|
||
|
}
|
||
|
// perform initialization at the very end to ensure all references will be properly registered
|
||
|
foreach (MetaTable table in tablesToInitialize) {
|
||
|
table.Initialize();
|
||
|
}
|
||
|
}
|
||
|
catch (Exception e) {
|
||
|
if (_registerGlobally) {
|
||
|
s_registrationException = e;
|
||
|
}
|
||
|
throw;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal static void CheckForRegistrationException() {
|
||
|
if (s_registrationException != null) {
|
||
|
throw new InvalidOperationException(
|
||
|
String.Format(CultureInfo.CurrentCulture, DynamicDataResources.MetaModel_RegistrationErrorOccurred),
|
||
|
s_registrationException);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Reset any previous registration error that may have happened. Normally, the behavior is that when an error
|
||
|
/// occurs during registration, the exception is cached and rethrown on all subsequent operations. This is done
|
||
|
/// so that if an error occurs in Application_Start, it shows up on every request. Calling this method clears
|
||
|
/// out the error and potentially allows new RegisterContext calls.
|
||
|
/// </summary>
|
||
|
public static void ResetRegistrationException() {
|
||
|
s_registrationException = null;
|
||
|
}
|
||
|
|
||
|
// Used for unit tests
|
||
|
internal static void ClearSimpleCache() {
|
||
|
s_registeredMetadataTypes.Clear();
|
||
|
}
|
||
|
|
||
|
internal static MetaModel CreateSimpleModel(Type entityType) {
|
||
|
// Never register a TDP more than once for a type
|
||
|
if (!s_registeredMetadataTypes.ContainsKey(entityType)) {
|
||
|
var provider = new AssociatedMetadataTypeTypeDescriptionProvider(entityType);
|
||
|
TypeDescriptor.AddProviderTransparent(provider, entityType);
|
||
|
s_registeredMetadataTypes.TryAdd(entityType, true);
|
||
|
}
|
||
|
|
||
|
MetaModel model = new MetaModel(false /* registerGlobally */);
|
||
|
|
||
|
// Pass a null provider factory since we registered the provider ourselves
|
||
|
model.RegisterContext(new SimpleDataModelProvider(entityType), new ContextConfiguration { MetadataProviderFactory = null });
|
||
|
return model;
|
||
|
}
|
||
|
|
||
|
internal static MetaModel CreateSimpleModel(ICustomTypeDescriptor descriptor) {
|
||
|
MetaModel model = new MetaModel(false /* registerGlobally */);
|
||
|
//
|
||
|
model.RegisterContext(new SimpleDataModelProvider(descriptor));
|
||
|
return model;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Instantiate a MetaTable object. Can be overridden to instantiate a derived type
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
protected virtual MetaTable CreateTable(TableProvider provider) {
|
||
|
return new MetaTable(this, provider);
|
||
|
}
|
||
|
|
||
|
private void AddTable(MetaTable table) {
|
||
|
_tables.Add(table);
|
||
|
_tablesByUniqueName.Add(table.Name, table);
|
||
|
if (_registerGlobally) {
|
||
|
MetaModelManager.AddTable(table.EntityType, table);
|
||
|
}
|
||
|
|
||
|
if (table.DataContextType != null) {
|
||
|
// need to use the name from the provider since the name from the table could have been modified by use of TableNameAttribute
|
||
|
_tablesByContextAndName.Add(new ContextTypeTableNamePair(table.DataContextType, table.Provider.Name), table);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void CheckTableNameConflict(MetaTable table, string nameOverride, List<MetaTable> tablesToInitialize) {
|
||
|
// try to find name conflict in tables from other context, or already processed tables in current context
|
||
|
MetaTable nameConflictTable;
|
||
|
if (!_tablesByUniqueName.TryGetValue(table.Name, out nameConflictTable)) {
|
||
|
nameConflictTable = tablesToInitialize.Find(t => t.Name.Equals(table.Name, StringComparison.CurrentCulture));
|
||
|
}
|
||
|
if (nameConflictTable != null) {
|
||
|
if (String.IsNullOrEmpty(nameOverride)) {
|
||
|
throw new ArgumentException(String.Format(
|
||
|
CultureInfo.CurrentCulture,
|
||
|
DynamicDataResources.MetaModel_EntityNameConflict,
|
||
|
table.EntityType.FullName,
|
||
|
table.DataContextType.FullName,
|
||
|
nameConflictTable.EntityType.FullName,
|
||
|
nameConflictTable.DataContextType.FullName));
|
||
|
}
|
||
|
else {
|
||
|
throw new ArgumentException(String.Format(
|
||
|
CultureInfo.CurrentCulture,
|
||
|
DynamicDataResources.MetaModel_EntityNameOverrideConflict,
|
||
|
nameOverride,
|
||
|
table.EntityType.FullName,
|
||
|
table.DataContextType.FullName,
|
||
|
nameConflictTable.EntityType.FullName,
|
||
|
nameConflictTable.DataContextType.FullName));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static void RegisterMetadataTypeDescriptionProvider(TableProvider entity, Func<Type, TypeDescriptionProvider> providerFactory) {
|
||
|
if (providerFactory != null) {
|
||
|
Type entityType = entity.EntityType;
|
||
|
// Support for type-less MetaTable
|
||
|
if (entityType != null) {
|
||
|
TypeDescriptionProvider provider = providerFactory(entityType);
|
||
|
if (provider != null) {
|
||
|
TypeDescriptor.AddProviderTransparent(provider, entityType);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns a collection of all the tables that are part of the context, regardless of whether they are visible or not.
|
||
|
/// </summary>
|
||
|
public ReadOnlyCollection<MetaTable> Tables {
|
||
|
get {
|
||
|
CheckForRegistrationException();
|
||
|
return _tablesRO;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns a collection of the currently visible tables for this context. Currently visible is defined as:
|
||
|
/// - a table whose EntityType is not abstract
|
||
|
/// - a table with scaffolding enabled
|
||
|
/// - a table for which a custom page for the list action can be found and that can be read by the current User
|
||
|
/// </summary>
|
||
|
public List<MetaTable> VisibleTables {
|
||
|
get {
|
||
|
CheckForRegistrationException();
|
||
|
return Tables.Where(IsTableVisible).OrderBy(t => t.DisplayName).ToList();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private bool IsTableVisible(MetaTable table) {
|
||
|
return !table.EntityType.IsAbstract && !String.IsNullOrEmpty(table.ListActionPath) && table.CanRead(Context.User);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Looks up a MetaTable by the entity type. Throws an exception if one is not found.
|
||
|
/// </summary>
|
||
|
/// <param name="entityType"></param>
|
||
|
/// <returns></returns>
|
||
|
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "We really want this to be a Type.")]
|
||
|
public MetaTable GetTable(Type entityType) {
|
||
|
MetaTable table;
|
||
|
if (!TryGetTable(entityType, out table)) {
|
||
|
throw new ArgumentException(String.Format(
|
||
|
CultureInfo.CurrentCulture,
|
||
|
DynamicDataResources.MetaModel_UnknownEntityType,
|
||
|
entityType.FullName));
|
||
|
}
|
||
|
|
||
|
return table;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Tries to look up a MetaTable by the entity type.
|
||
|
/// </summary>
|
||
|
/// <param name="entityType"></param>
|
||
|
/// <param name="table"></param>
|
||
|
/// <returns></returns>
|
||
|
public bool TryGetTable(Type entityType, out MetaTable table) {
|
||
|
CheckForRegistrationException();
|
||
|
|
||
|
if (entityType == null) {
|
||
|
throw new ArgumentNullException("entityType");
|
||
|
}
|
||
|
|
||
|
if (!_registerGlobally) {
|
||
|
table = Tables.SingleOrDefault(t => t.EntityType == entityType);
|
||
|
return table != null;
|
||
|
}
|
||
|
|
||
|
return MetaModelManager.TryGetTable(entityType, out table);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Looks up a MetaTable by unique name. Throws if one is not found. The unique name defaults to the table name, or an override
|
||
|
/// can be provided via ContextConfiguration when the context that contains the table is registered. The unique name uniquely
|
||
|
/// identifies a table within a give MetaModel. It is used for URL generation.
|
||
|
/// </summary>
|
||
|
/// <param name="uniqueTableName"></param>
|
||
|
/// <returns></returns>
|
||
|
public MetaTable GetTable(string uniqueTableName) {
|
||
|
CheckForRegistrationException();
|
||
|
|
||
|
MetaTable table;
|
||
|
if (!TryGetTable(uniqueTableName, out table)) {
|
||
|
throw new ArgumentException(String.Format(
|
||
|
CultureInfo.CurrentCulture,
|
||
|
DynamicDataResources.MetaModel_UnknownTable,
|
||
|
uniqueTableName));
|
||
|
}
|
||
|
|
||
|
return table;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Tries to look up a MetaTable by unique name. Doe
|
||
|
/// </summary>
|
||
|
/// <param name="uniqueTableName"></param>
|
||
|
/// <param name="table"></param>
|
||
|
/// <returns></returns>
|
||
|
public bool TryGetTable(string uniqueTableName, out MetaTable table) {
|
||
|
CheckForRegistrationException();
|
||
|
|
||
|
if (uniqueTableName == null) {
|
||
|
throw new ArgumentNullException("uniqueTableName");
|
||
|
}
|
||
|
|
||
|
return _tablesByUniqueName.TryGetValue(uniqueTableName, out table);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Looks up a MetaTable by the contextType/tableName combination. Throws if one is not found.
|
||
|
/// </summary>
|
||
|
/// <param name="tableName"></param>
|
||
|
/// <param name="contextType"></param>
|
||
|
/// <returns></returns>
|
||
|
public MetaTable GetTable(string tableName, Type contextType) {
|
||
|
CheckForRegistrationException();
|
||
|
|
||
|
if (tableName == null) {
|
||
|
throw new ArgumentNullException("tableName");
|
||
|
}
|
||
|
|
||
|
if (contextType == null) {
|
||
|
throw new ArgumentNullException("contextType");
|
||
|
}
|
||
|
|
||
|
MetaTable table;
|
||
|
if (!_tablesByContextAndName.TryGetValue(new ContextTypeTableNamePair(contextType, tableName), out table)) {
|
||
|
if (!_contextTypes.Contains(contextType)) {
|
||
|
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture,
|
||
|
DynamicDataResources.MetaModel_UnknownContextType,
|
||
|
contextType.FullName));
|
||
|
}
|
||
|
else {
|
||
|
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture,
|
||
|
DynamicDataResources.MetaModel_UnknownTableInContext,
|
||
|
contextType.FullName,
|
||
|
tableName));
|
||
|
}
|
||
|
}
|
||
|
return table;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Lets you set a custom IFieldTemplateFactory. An IFieldTemplateFactor lets you customize which field templates are created
|
||
|
/// for the various columns.
|
||
|
/// </summary>
|
||
|
public IFieldTemplateFactory FieldTemplateFactory {
|
||
|
get {
|
||
|
// If no custom factory was set, use our default
|
||
|
if (_fieldTemplateFactory == null) {
|
||
|
FieldTemplateFactory = new FieldTemplateFactory();
|
||
|
}
|
||
|
|
||
|
return _fieldTemplateFactory;
|
||
|
}
|
||
|
set {
|
||
|
_fieldTemplateFactory = value;
|
||
|
|
||
|
// Give the model to the factory
|
||
|
if (_fieldTemplateFactory != null) {
|
||
|
_fieldTemplateFactory.Initialize(this);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public EntityTemplateFactory EntityTemplateFactory {
|
||
|
get {
|
||
|
if (_entityTemplateFactory == null) {
|
||
|
EntityTemplateFactory = new EntityTemplateFactory();
|
||
|
}
|
||
|
|
||
|
return _entityTemplateFactory;
|
||
|
}
|
||
|
set {
|
||
|
_entityTemplateFactory = value;
|
||
|
if (_entityTemplateFactory != null) {
|
||
|
_entityTemplateFactory.Initialize(this);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public FilterFactory FilterFactory {
|
||
|
get {
|
||
|
if (_filterFactory == null) {
|
||
|
FilterFactory = new FilterFactory();
|
||
|
}
|
||
|
return _filterFactory;
|
||
|
}
|
||
|
set {
|
||
|
_filterFactory = value;
|
||
|
if (_filterFactory != null) {
|
||
|
_filterFactory.Initialize(this);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private string _queryStringKeyPrefix = String.Empty;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Lets you get an action path (URL) to an action for a particular table/action/entity instance combo.
|
||
|
/// </summary>
|
||
|
/// <param name="tableName"></param>
|
||
|
/// <param name="action"></param>
|
||
|
/// <param name="row">An object representing a single row of data in a table. Used to provide values for query string parameters.</param>
|
||
|
/// <returns></returns>
|
||
|
public string GetActionPath(string tableName, string action, object row) {
|
||
|
return GetTable(tableName).GetActionPath(action, row);
|
||
|
}
|
||
|
|
||
|
private class ContextTypeTableNamePair : IEquatable<ContextTypeTableNamePair> {
|
||
|
public ContextTypeTableNamePair(Type contextType, string tableName) {
|
||
|
Debug.Assert(contextType != null);
|
||
|
Debug.Assert(tableName != null);
|
||
|
|
||
|
ContextType = contextType;
|
||
|
TableName = tableName;
|
||
|
|
||
|
HashCode = ContextType.GetHashCode() ^ TableName.GetHashCode();
|
||
|
}
|
||
|
|
||
|
private int HashCode { get; set; }
|
||
|
public Type ContextType { get; private set; }
|
||
|
public string TableName { get; private set; }
|
||
|
|
||
|
public bool Equals(ContextTypeTableNamePair other) {
|
||
|
if (other == null) {
|
||
|
return false;
|
||
|
}
|
||
|
return ContextType == other.ContextType && TableName.Equals(other.TableName, StringComparison.Ordinal);
|
||
|
}
|
||
|
|
||
|
public override int GetHashCode() {
|
||
|
return HashCode;
|
||
|
}
|
||
|
|
||
|
public override bool Equals(object obj) {
|
||
|
return Equals(obj as ContextTypeTableNamePair);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal static class MetaModelManager {
|
||
|
private static Hashtable s_modelByContextType = new Hashtable();
|
||
|
private static Hashtable s_tableByEntityType = new Hashtable();
|
||
|
|
||
|
internal static void AddModel(Type contextType, MetaModel model) {
|
||
|
Debug.Assert(contextType != null);
|
||
|
Debug.Assert(model != null);
|
||
|
lock (s_modelByContextType) {
|
||
|
s_modelByContextType.Add(contextType, model);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal static bool TryGetModel(Type contextType, out MetaModel model) {
|
||
|
model = (MetaModel)s_modelByContextType[contextType];
|
||
|
return model != null;
|
||
|
}
|
||
|
|
||
|
internal static void AddTable(Type entityType, MetaTable table) {
|
||
|
Debug.Assert(entityType != null);
|
||
|
Debug.Assert(table != null);
|
||
|
lock (s_tableByEntityType) {
|
||
|
s_tableByEntityType[entityType] = table;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal static void Clear() {
|
||
|
lock (s_modelByContextType) {
|
||
|
s_modelByContextType.Clear();
|
||
|
}
|
||
|
lock (s_tableByEntityType) {
|
||
|
s_tableByEntityType.Clear();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal static bool TryGetTable(Type type, out MetaTable table) {
|
||
|
table = (MetaTable)s_tableByEntityType[type];
|
||
|
return table != null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ReadOnlyCollection<IMetaTable> IMetaModel.Tables {
|
||
|
get {
|
||
|
return Tables.OfType<IMetaTable>().ToList().AsReadOnly();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool IMetaModel.TryGetTable(string uniqueTableName, out IMetaTable table) {
|
||
|
MetaTable metaTable;
|
||
|
table = null;
|
||
|
if (TryGetTable(uniqueTableName, out metaTable)) {
|
||
|
table = metaTable;
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool IMetaModel.TryGetTable(Type entityType, out IMetaTable table) {
|
||
|
MetaTable metaTable;
|
||
|
table = null;
|
||
|
if (TryGetTable(entityType, out metaTable)) {
|
||
|
table = metaTable;
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
List<IMetaTable> IMetaModel.VisibleTables {
|
||
|
get {
|
||
|
return VisibleTables.OfType<IMetaTable>().ToList();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
IMetaTable IMetaModel.GetTable(string tableName, Type contextType) {
|
||
|
return GetTable(tableName, contextType);
|
||
|
}
|
||
|
|
||
|
IMetaTable IMetaModel.GetTable(string uniqueTableName) {
|
||
|
return GetTable(uniqueTableName);
|
||
|
}
|
||
|
|
||
|
IMetaTable IMetaModel.GetTable(Type entityType) {
|
||
|
return GetTable(entityType);
|
||
|
}
|
||
|
}
|
||
|
}
|