PageRenderTime 54ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 1ms

/mcs/class/System.Web.DynamicData/System.Web.DynamicData/MetaTable.cs

https://bitbucket.org/foobar22/mono
C# | 596 lines | 405 code | 114 blank | 77 comment | 107 complexity | 268de7f98c382ad92299fb05a7d6eb18 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, CC-BY-SA-3.0, GPL-2.0, Unlicense, Apache-2.0, LGPL-2.0
  1. //
  2. // MetaTable.cs
  3. //
  4. // Author:
  5. // Atsushi Enomoto <atsushi@ximian.com>
  6. // Marek Habersack <mhabersack@novell.com>
  7. //
  8. // Copyright (C) 2008-2009 Novell Inc. http://novell.com
  9. //
  10. //
  11. // Permission is hereby granted, free of charge, to any person obtaining
  12. // a copy of this software and associated documentation files (the
  13. // "Software"), to deal in the Software without restriction, including
  14. // without limitation the rights to use, copy, modify, merge, publish,
  15. // distribute, sublicense, and/or sell copies of the Software, and to
  16. // permit persons to whom the Software is furnished to do so, subject to
  17. // the following conditions:
  18. //
  19. // The above copyright notice and this permission notice shall be
  20. // included in all copies or substantial portions of the Software.
  21. //
  22. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  23. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  24. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  25. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  26. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  27. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  28. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  29. //
  30. using System;
  31. using System.Collections;
  32. using System.Collections.Generic;
  33. using System.Collections.ObjectModel;
  34. using System.Collections.Specialized;
  35. using System.ComponentModel;
  36. using System.ComponentModel.DataAnnotations;
  37. using System.Globalization;
  38. using System.Linq;
  39. using System.Reflection;
  40. using System.Security.Permissions;
  41. using System.Security.Principal;
  42. using System.Text;
  43. using System.Web.Caching;
  44. using System.Web.UI;
  45. using System.Web.Routing;
  46. using System.Web.DynamicData.ModelProviders;
  47. namespace System.Web.DynamicData
  48. {
  49. [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
  50. [AspNetHostingPermission (SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
  51. public class MetaTable
  52. {
  53. RouteCollection routes;
  54. MetaColumn displayColumn;
  55. MetaColumn sortColumn;
  56. bool? entityHasToString;
  57. bool? sortDescending;
  58. global::System.ComponentModel.AttributeCollection attributes;
  59. string displayName;
  60. bool displayColumnChecked;
  61. bool sortColumnChecked;
  62. internal MetaTable (MetaModel model, TableProvider provider, ContextConfiguration configuration)
  63. {
  64. bool scaffoldAllTables;
  65. this.model = model;
  66. Provider = provider;
  67. if (configuration != null) {
  68. ScaffoldAllTables = scaffoldAllTables = configuration.ScaffoldAllTables;
  69. Func <Type, TypeDescriptionProvider> factory = configuration.MetadataProviderFactory;
  70. if (factory != null) {
  71. Type t = EntityType;
  72. TypeDescriptionProvider p = factory (t);
  73. if (p != null)
  74. TypeDescriptor.AddProvider (p, t);
  75. }
  76. } else
  77. scaffoldAllTables = false;
  78. ScaffoldTableAttribute attr = null;
  79. MetaModel.GetDataFieldAttribute <ScaffoldTableAttribute> (Attributes, ref attr);
  80. Scaffold = attr != null ? attr.Scaffold : scaffoldAllTables;
  81. DataContextType = provider.DataModel.ContextType;
  82. var columns = new List <MetaColumn> ();
  83. var primaryKeyColumns = new List <MetaColumn> ();
  84. var foreignKeyColumnNames = new List <string> ();
  85. MetaColumn mc;
  86. foreach (var c in provider.Columns) {
  87. // this seems to be the determining factor on whether we create
  88. // MetaColumn or MetaForeignKeyColumn/MetaChildrenColumn. As the
  89. // determination depends upon the relationship direction, we must
  90. // check that using the ColumnProvider's association, if any.
  91. //
  92. // http://msdn.microsoft.com/en-us/library/system.web.dynamicdata.metaforeignkeycolumn.aspx
  93. // http://msdn.microsoft.com/en-us/library/system.web.dynamicdata.metachildrencolumn.aspx
  94. // http://forums.asp.net/t/1426992.aspx
  95. var association = c.Association;
  96. if (association == null)
  97. mc = new MetaColumn (this, c);
  98. else {
  99. var dir = association.Direction;
  100. if (dir == AssociationDirection.OneToOne || dir == AssociationDirection.ManyToOne)
  101. mc = new MetaForeignKeyColumn (this, c);
  102. else
  103. mc = new MetaChildrenColumn (this, c);
  104. }
  105. columns.Add (mc);
  106. if (c.IsPrimaryKey)
  107. primaryKeyColumns.Add (mc);
  108. if (mc is MetaForeignKeyColumn)
  109. foreignKeyColumnNames.Add (c.Name);
  110. }
  111. Columns = new ReadOnlyCollection <MetaColumn> (columns);
  112. PrimaryKeyColumns = new ReadOnlyCollection <MetaColumn> (primaryKeyColumns);
  113. if (foreignKeyColumnNames.Count == 0)
  114. ForeignKeyColumnsNames = String.Empty;
  115. else
  116. ForeignKeyColumnsNames = String.Join (",", foreignKeyColumnNames.ToArray ());
  117. HasPrimaryKey = primaryKeyColumns.Count > 0;
  118. // See http://forums.asp.net/t/1388561.aspx
  119. //
  120. // Also, http://forums.asp.net/t/1307243.aspx - that seems to be out of
  121. // scope for us, though (at least for now)
  122. IsReadOnly = primaryKeyColumns.Count == 0;
  123. // FIXME: fill more properties.
  124. }
  125. MetaModel model;
  126. public global::System.ComponentModel.AttributeCollection Attributes {
  127. get {
  128. if (attributes == null) {
  129. ICustomTypeDescriptor descriptor = MetaModel.GetTypeDescriptor (EntityType);
  130. if (descriptor != null)
  131. attributes = descriptor.GetAttributes ();
  132. }
  133. return attributes;
  134. }
  135. }
  136. public ReadOnlyCollection<MetaColumn> Columns { get; private set; }
  137. public string DataContextPropertyName {
  138. get { return Provider.Name; }
  139. }
  140. public Type DataContextType { get; private set; }
  141. public MetaColumn DisplayColumn {
  142. get {
  143. if (displayColumn == null)
  144. displayColumn = FindDisplayColumn ();
  145. return displayColumn;
  146. }
  147. }
  148. public string DisplayName {
  149. get {
  150. if (displayName == null)
  151. displayName = DetermineDisplayName ();
  152. return displayName;
  153. }
  154. }
  155. public Type EntityType {
  156. get { return Provider.EntityType; }
  157. }
  158. public string ForeignKeyColumnsNames { get; private set; }
  159. public bool HasPrimaryKey { get; private set; }
  160. public bool IsReadOnly { get; private set; }
  161. public string ListActionPath {
  162. get { return GetActionPath (PageAction.List); }
  163. }
  164. public MetaModel Model {
  165. get { return model; }
  166. }
  167. public string Name {
  168. get { return Provider.Name; }
  169. }
  170. public ReadOnlyCollection<MetaColumn> PrimaryKeyColumns { get; private set; }
  171. public TableProvider Provider { get; private set; }
  172. public bool Scaffold { get; private set; }
  173. internal bool ScaffoldAllTables { get; private set; }
  174. public MetaColumn SortColumn {
  175. get {
  176. if (sortColumn == null)
  177. sortColumn = FindSortColumn ();
  178. return sortColumn;
  179. }
  180. }
  181. public bool SortDescending {
  182. get {
  183. if (sortDescending == null)
  184. sortDescending = DetermineSortDescending ();
  185. return (bool)sortDescending;
  186. }
  187. }
  188. string BuildActionPath (string path, RouteValueDictionary values)
  189. {
  190. var sb = new StringBuilder ();
  191. sb.Append (path);
  192. if (values != null && values.Count > 0) {
  193. sb.Append ('?');
  194. bool first = true;
  195. foreach (var de in values) {
  196. if (first)
  197. first = false;
  198. else
  199. sb.Append ('&');
  200. sb.Append (Uri.EscapeDataString (de.Key));
  201. sb.Append ('=');
  202. object parameterValue = de.Value;
  203. if (parameterValue != null)
  204. sb.Append (Uri.EscapeDataString (parameterValue.ToString ()));
  205. }
  206. }
  207. return sb.ToString ();
  208. }
  209. public object CreateContext ()
  210. {
  211. return Activator.CreateInstance (DataContextType);
  212. }
  213. string DetermineDisplayName ()
  214. {
  215. DisplayNameAttribute attr = null;
  216. MetaModel.GetDataFieldAttribute <DisplayNameAttribute> (Attributes, ref attr);
  217. if (attr == null)
  218. return Name;
  219. return attr.DisplayName;
  220. }
  221. bool DetermineSortDescending ()
  222. {
  223. DisplayColumnAttribute attr = null;
  224. MetaModel.GetDataFieldAttribute <DisplayColumnAttribute> (Attributes, ref attr);
  225. if (attr == null)
  226. return false;
  227. return attr.SortDescending;
  228. }
  229. void FillWithPrimaryKeys (RouteValueDictionary values, IList<object> primaryKeyValues)
  230. {
  231. if (primaryKeyValues == null)
  232. return;
  233. ReadOnlyCollection <MetaColumn> pkc = PrimaryKeyColumns;
  234. int pkcCount = pkc.Count;
  235. // Fill the above with primary keys using primaryKeyValues - .NET does not
  236. // check (again) whether there are enough elements in primaryKeyValues, it
  237. // just assumes there are at least as many of them as in the
  238. // PrimaryKeyColumns collection, so we'll emulate this bug here.
  239. if (primaryKeyValues.Count < pkc.Count)
  240. throw new ArgumentOutOfRangeException ("index");
  241. // This is so wrong (the generated URL might contain values assigned to
  242. // primary column keys which do not correspond with the column's type) but
  243. // this is how the upstream behaves, unfortunately.
  244. for (int i = 0; i < pkcCount; i++)
  245. values.Add (pkc [i].Name, primaryKeyValues [i]);
  246. }
  247. MetaColumn FindDisplayColumn ()
  248. {
  249. if (displayColumnChecked)
  250. return displayColumn;
  251. displayColumnChecked = true;
  252. ReadOnlyCollection<MetaColumn> columns = Columns;
  253. // 1. The column that is specified by using the DisplayColumnAttribute attribute.
  254. DisplayColumnAttribute attr = Attributes [typeof (DisplayColumnAttribute)] as DisplayColumnAttribute;
  255. if (attr != null) {
  256. string name = attr.DisplayColumn;
  257. foreach (MetaColumn mc in columns)
  258. if (String.Compare (name, mc.Name, StringComparison.Ordinal) == 0)
  259. return mc;
  260. throw new InvalidOperationException ("The display column '" + name + "' specified for the table '" + EntityType.Name + "' does not exist.");
  261. }
  262. // 2. The first string column that is not in the primary key.
  263. // LAMESPEC: also a column which is not a custom one
  264. ReadOnlyCollection <MetaColumn> pkc = PrimaryKeyColumns;
  265. bool havePkc = pkc.Count > 0;
  266. foreach (MetaColumn mc in columns) {
  267. if (mc.IsCustomProperty || (havePkc && pkc.Contains (mc)))
  268. continue;
  269. if (mc.ColumnType == typeof (string))
  270. return mc;
  271. }
  272. // 3. The first string column that is in the primary key.
  273. if (havePkc) {
  274. foreach (MetaColumn mc in pkc) {
  275. if (mc.ColumnType == typeof (string))
  276. return mc;
  277. }
  278. // 4. The first non-string column that is in the primary key.
  279. return pkc [0];
  280. }
  281. // No check, again, is made whether the columns collection contains enough
  282. // columns to perform successful lookup, we're emulating that here.
  283. if (columns.Count == 0)
  284. throw new ArgumentOutOfRangeException ("index");
  285. // Fallback - return the first column
  286. return columns [0];
  287. }
  288. MetaColumn FindSortColumn ()
  289. {
  290. if (sortColumnChecked)
  291. return sortColumn;
  292. sortColumnChecked = true;
  293. DisplayColumnAttribute attr = Attributes [typeof (DisplayColumnAttribute)] as DisplayColumnAttribute;
  294. if (attr == null)
  295. return null;
  296. string name = attr.SortColumn;
  297. if (String.IsNullOrEmpty (name))
  298. return null;
  299. MetaColumn ret = null;
  300. Exception exception = null;
  301. try {
  302. ret = Columns.First <MetaColumn> ((MetaColumn mc) => {
  303. if (String.Compare (mc.Name, name, StringComparison.Ordinal) == 0)
  304. return true;
  305. return false;
  306. });
  307. } catch (Exception ex) {
  308. exception = ex;
  309. }
  310. if (ret == null)
  311. throw new InvalidOperationException ("The sort column '" + name + "' specified for table '" + Name + "' does not exist.", exception);
  312. return ret;
  313. }
  314. public string GetActionPath (string action)
  315. {
  316. // You can see this is the call we should make by modifying one of the unit
  317. // tests (e.g. MetaTableTest.GetActinPath) and commenting out the line which
  318. // assigns a context to HttpContext.Current and looking at the exception
  319. // stack trace.
  320. return GetActionPath (action, (IList <object>)null);
  321. }
  322. public string GetActionPath (string action, IList<object> primaryKeyValues)
  323. {
  324. if (String.IsNullOrEmpty (action))
  325. return String.Empty;
  326. var values = new RouteValueDictionary ();
  327. values.Add ("Action", action);
  328. values.Add ("Table", Name);
  329. FillWithPrimaryKeys (values, primaryKeyValues);
  330. // To see that this internal method is called, comment out setting of
  331. // HttpContext in the GetActionPath_Action_PrimaryKeyValues test and look at
  332. // the stack trace
  333. return GetActionPathFromRoutes (values);
  334. }
  335. public string GetActionPath (string action, object row)
  336. {
  337. // To see that this method is called, comment out setting of
  338. // HttpContext in the GetActionPath_Action_Row test and look at
  339. // the stack trace
  340. return GetActionPath (action, GetPrimaryKeyValues (row));
  341. }
  342. public string GetActionPath (string action, RouteValueDictionary routeValues)
  343. {
  344. if (String.IsNullOrEmpty (action))
  345. return String.Empty;
  346. // .NET doesn't check whether routeValues is null, we'll just "implement"
  347. // the behavior here...
  348. if (routeValues == null)
  349. throw new NullReferenceException ();
  350. // NO check is made whether those two are already in the dictionary...
  351. routeValues.Add ("Action", action);
  352. routeValues.Add ("Table", Name);
  353. // To see that this internal method is called, comment out setting of
  354. // HttpContext in the GetActionPath_Action_RouteValues test and look at
  355. // the stack trace
  356. return GetActionPathFromRoutes (routeValues);
  357. }
  358. public string GetActionPath (string action, IList<object> primaryKeyValues, string path)
  359. {
  360. if (String.IsNullOrEmpty (path))
  361. return GetActionPath (action, primaryKeyValues);
  362. var values = new RouteValueDictionary ();
  363. FillWithPrimaryKeys (values, primaryKeyValues);
  364. return BuildActionPath (path, values);
  365. }
  366. public string GetActionPath (string action, object row, string path)
  367. {
  368. return GetActionPath (action, GetPrimaryKeyValues (row), path);
  369. }
  370. string GetActionPathFromRoutes (RouteValueDictionary values)
  371. {
  372. if (routes == null)
  373. routes = RouteTable.Routes;
  374. VirtualPathData vpd = routes.GetVirtualPath (DynamicDataRouteHandler.GetRequestContext (HttpContext.Current), values);
  375. return vpd == null ? String.Empty : vpd.VirtualPath;
  376. }
  377. public MetaColumn GetColumn (string columnName)
  378. {
  379. MetaColumn mc;
  380. if (TryGetColumn (columnName, out mc))
  381. return mc;
  382. throw new InvalidOperationException (String.Format ("Column '{0}' does not exist in the meta table '{1}'", columnName, Name));
  383. }
  384. public string GetDisplayString (object row)
  385. {
  386. if (row == null)
  387. return String.Empty;
  388. if (entityHasToString == null) {
  389. Type type = EntityType;
  390. MethodInfo pi = type == null ? null : type.GetMethod ("ToString", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
  391. entityHasToString = pi != null;
  392. }
  393. if ((bool)entityHasToString)
  394. return row.ToString ();
  395. // Once again no check is made for row's type
  396. MetaColumn mc = DisplayColumn;
  397. object value = DataBinder.GetPropertyValue (row, mc.Name);
  398. if (value == null)
  399. return String.Empty;
  400. return value.ToString ();
  401. }
  402. public string GetPrimaryKeyString (IList<object> primaryKeyValues)
  403. {
  404. if (primaryKeyValues == null || primaryKeyValues.Count == 0)
  405. return String.Empty;
  406. var strings = new List <string> ();
  407. bool allNull = true;
  408. foreach (object o in primaryKeyValues) {
  409. if (o == null) {
  410. strings.Add (null);
  411. continue;
  412. }
  413. var str = o.ToString ();
  414. if (str == null) {
  415. strings.Add (null);
  416. continue;
  417. }
  418. allNull = false;
  419. strings.Add (str);
  420. }
  421. if (allNull) {
  422. strings = null;
  423. return String.Empty;
  424. }
  425. var ret = String.Join (",", strings.ToArray ());
  426. strings = null;
  427. return ret;
  428. }
  429. public string GetPrimaryKeyString (object row)
  430. {
  431. return GetPrimaryKeyString (GetPrimaryKeyValues (row));
  432. }
  433. public IList<object> GetPrimaryKeyValues (object row)
  434. {
  435. if (row == null)
  436. return null;
  437. ReadOnlyCollection <MetaColumn> pkc = PrimaryKeyColumns;
  438. int pkcCount = pkc.Count;
  439. var ret = new List <object> ();
  440. if (pkcCount == 0)
  441. return ret;
  442. // No check is made whether row is of correct type,
  443. // DataBinder.GetPropertyValue is called instead to fetch value of each
  444. // member of the row object corresponding to primary key columns.
  445. for (int i = 0; i < pkcCount; i++)
  446. ret.Add (DataBinder.GetPropertyValue (row, pkc [i].Name));
  447. return ret;
  448. }
  449. public IQueryable GetQuery ()
  450. {
  451. return GetQuery (CreateContext ());
  452. }
  453. public IQueryable GetQuery (object context)
  454. {
  455. return Provider.GetQuery (context == null ? CreateContext () : context);
  456. }
  457. internal void Init ()
  458. {
  459. ReadOnlyCollection <MetaColumn> columns = Columns;
  460. if (columns == null)
  461. return;
  462. foreach (MetaColumn mc in columns)
  463. mc.Init ();
  464. }
  465. public override string ToString ()
  466. {
  467. return Name;
  468. }
  469. public bool TryGetColumn (string columnName, out MetaColumn column)
  470. {
  471. if (columnName == null)
  472. throw new ArgumentNullException ("columnName");
  473. foreach (var m in Columns)
  474. if (m.Name == columnName) {
  475. column = m;
  476. return true;
  477. }
  478. column = null;
  479. return false;
  480. }
  481. }
  482. }