Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Web.DynamicData / DynamicData / DynamicDataExtensions.cs
1 namespace System.Web.DynamicData {
2     using System;
3     using System.Collections;
4     using System.Collections.Generic;
5     using System.ComponentModel.DataAnnotations;
6     using System.Diagnostics.CodeAnalysis;
7     using System.Data.Linq;
8     using System.Diagnostics;
9     using System.Globalization;
10     using System.Linq.Expressions;
11     using System.Web;
12     using System.Web.DynamicData.Util;
13     using System.Web.Resources;
14     using System.Web.UI;
15     using System.Web.UI.WebControls;
16     /// <summary>
17     /// Extension methods used by DynamicData
18     /// </summary>
19     public static class DynamicDataExtensions {
20         public static void SetMetaTable(this INamingContainer control, MetaTable table) {
21             SetMetaTableInternal(control, table, null/* defaultValues*/, new HttpContextWrapper(HttpContext.Current));
22         }
23
24         public static void SetMetaTable(this INamingContainer control, MetaTable table, IDictionary<string, object> defaultValues) {
25             if (defaultValues == null) {
26                 throw new ArgumentNullException("defaultValues");
27             }
28             SetMetaTableInternal(control, table, defaultValues, new HttpContextWrapper(HttpContext.Current));
29         }
30
31         public static void SetMetaTable(this INamingContainer control, MetaTable table, object defaultValues) {
32             if (defaultValues == null) {
33                 throw new ArgumentNullException("defaultValues");
34             }
35             SetMetaTableInternal(control, table, Misc.ConvertObjectToDictionary(defaultValues), new HttpContextWrapper(HttpContext.Current));
36         }
37
38         public static IDictionary<string, object> GetDefaultValues(this IDataSource dataSource) {
39             return GetDefaultValues(dataSource, new HttpContextWrapper(HttpContext.Current));
40         }
41
42         public static IDictionary<string, object> GetDefaultValues(this INamingContainer control) {
43             return GetDefaultValues(control, new HttpContextWrapper(HttpContext.Current));
44         }
45
46         public static MetaTable GetMetaTable(this IDataSource dataSource) {
47             return GetMetaTable(dataSource, new HttpContextWrapper(HttpContext.Current));
48         }
49
50         public static bool TryGetMetaTable(this IDataSource dataSource, out MetaTable table) {
51             return TryGetMetaTable(dataSource, new HttpContextWrapper(HttpContext.Current), out table);
52         }
53
54         public static MetaTable GetMetaTable(this INamingContainer control) {
55             return GetMetaTable(control, new HttpContextWrapper(HttpContext.Current));
56         }
57
58         public static bool TryGetMetaTable(this INamingContainer control, out MetaTable table) {
59             return TryGetMetaTable(control, new HttpContextWrapper(HttpContext.Current), out table);
60         }
61
62         internal static void ApplyFieldGenerator(INamingContainer control, MetaTable table) {
63             GridView gridView = control as GridView;
64             if (gridView != null && gridView.AutoGenerateColumns && gridView.ColumnsGenerator == null) {
65                 gridView.ColumnsGenerator = new DefaultAutoFieldGenerator(table);
66             }
67             else {
68                 DetailsView detailsView = control as DetailsView;
69                 if (detailsView != null && detailsView.AutoGenerateRows && detailsView.RowsGenerator == null) {
70                     detailsView.RowsGenerator = new DefaultAutoFieldGenerator(table);
71                 }
72             }
73         }
74
75         internal static DefaultValueMapping GetDefaultValueMapping(object control, HttpContextBase context) {
76             IDictionary<object, MappingInfo> mapping = MetaTableHelper.GetMapping(context);
77             MappingInfo mappingInfo;
78             if (mapping.TryGetValue(control, out mappingInfo)) {
79                 return mappingInfo.DefaultValueMapping;
80             }
81             return null;
82         }
83
84         internal static IDictionary<string, object> GetDefaultValues(object control, HttpContextBase context) {
85             DefaultValueMapping mapping = GetDefaultValueMapping(control, context);
86             if (mapping != null) {
87                 return mapping.Values;
88             }
89             return null;
90         }
91
92         internal static MetaTable GetMetaTable(IDataSource dataSource, HttpContextBase context) {
93             MetaTable table;
94             if (TryGetMetaTable(dataSource, context, out table)) {
95                 return table;
96             }
97             throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, DynamicDataResources.MetaTable_CannotGetTableFromDataSource));
98         }
99
100         internal static bool TryGetMetaTable(IDataSource dataSource, HttpContextBase context, out MetaTable table) {
101             if (dataSource == null) {
102                 throw new ArgumentNullException("dataSource");
103             }
104
105             Debug.Assert(context != null);
106
107             table = MetaTableHelper.GetTableFromMapping(context, dataSource);
108             if (table == null) {
109                 var dynamicDataSource = dataSource as IDynamicDataSource;
110                 if (dynamicDataSource != null) {
111                     table = MetaTableHelper.GetTableFromDynamicDataSource(dynamicDataSource);
112                 }
113             }
114             return table != null;
115         }
116
117         internal static MetaTable GetMetaTable(INamingContainer control, HttpContextBase context) {
118             MetaTable table;
119             if (!TryGetMetaTable(control, context, out table)) {
120                 throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, DynamicDataResources.MetaTable_CannotGetTableFromControl));
121             }
122             return table;
123         }
124
125         internal static bool TryGetMetaTable(INamingContainer control, HttpContextBase context, out MetaTable table) {
126             if (control == null) {
127                 throw new ArgumentNullException("control");
128             }
129
130             table = MetaTableHelper.GetTableFromMapping(context, control);
131             return table != null;
132         }
133
134         internal static void SetMetaTableInternal(INamingContainer control, MetaTable table, IDictionary<string, object> defaultValues, HttpContextBase context) {
135             if (control == null) {
136                 throw new ArgumentNullException("control");
137             }
138             if (table == null) {
139                 throw new ArgumentNullException("table");
140             }
141             IDataBoundControl dataBoundControl = control as IDataBoundControl;
142             IDataSource dataSource = null;
143             if (dataBoundControl != null) {
144                 dataSource = dataBoundControl.DataSourceObject;
145             }
146             MetaTableHelper.SetTableInMapping(context, control, table, defaultValues);
147             if (dataSource != null) {
148                 // If the control being mapped is a databound control then register its datasource
149                 MetaTableHelper.SetTableInMapping(context, dataSource, table, defaultValues);
150             }
151         }
152
153         /// <summary>
154         /// Return the MetaTable association with a datasource
155         /// </summary>
156         [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "This is a legacy API and cannot be changed")]
157         public static MetaTable GetTable(this IDynamicDataSource dataSource) {
158             return MetaTableHelper.GetTableWithFullFallback(dataSource, HttpContext.Current.ToWrapper());
159         }
160
161         /// <summary>
162         /// Expand Dynamic where parameter (e.g. DynamicControlParameter, DynamicQueryStringParameter) into
163         /// 'regular' parameters that the datasource can understand
164         /// </summary>
165         /// <param name="dataSource">The datasource which Where parameters need to be expanded</param>
166         public static void ExpandDynamicWhereParameters(this IDynamicDataSource dataSource) {
167
168             ParameterCollection whereParameters = dataSource.WhereParameters;
169
170             // First, check if any parameters need to be expanded
171             bool needProcessing = false;
172             foreach (Parameter parameter in whereParameters) {
173                 if (parameter is IWhereParametersProvider) {
174                     needProcessing = true;
175                     break;
176                 }
177             }
178
179             // If not, don't do anything
180             if (!needProcessing)
181                 return;
182
183             // Make a copy of the parameters, and clear the collection
184             var whereParametersCopy = new Parameter[whereParameters.Count];
185             whereParameters.CopyTo(whereParametersCopy, 0);
186             whereParameters.Clear();
187
188             // Go through all the parameters and expand them
189             foreach (Parameter parameter in whereParametersCopy) {
190                 ExpandWhereParameter(dataSource, parameter);
191             }
192         }
193
194         private static void ExpandWhereParameter(IDynamicDataSource dataSource, Parameter parameter) {
195             var provider = parameter as IWhereParametersProvider;
196             if (provider == null) {
197                 // If it's a standard parameter, just add it
198                 dataSource.WhereParameters.Add(parameter);
199             }
200             else {
201                 // Get the list of sub-parameters and expand them recursively
202                 IEnumerable<Parameter> newParameters = provider.GetWhereParameters(dataSource);
203                 foreach (Parameter newParameter in newParameters) {
204                     ExpandWhereParameter(dataSource, newParameter);
205                 }
206             }
207         }
208
209         /// <summary>
210         /// Find the containing data control, and return the data source it points to
211         /// </summary>
212         public static IDynamicDataSource FindDataSourceControl(this Control current) {
213             return DataControlHelper.FindDataSourceControl(current);
214         }
215
216         /// <summary>
217         /// Find the containing data control, and return the MetaTable associated with it, if any
218         /// </summary>
219         public static MetaTable FindMetaTable(this Control current) {
220             return MetaTableHelper.FindMetaTable(current, HttpContext.Current.ToWrapper());
221         }
222
223         /// <summary>
224         /// Find the field template for a column within the current naming container
225         /// </summary>
226         public static Control FindFieldTemplate(this Control control, string columnName) {
227             return control.FindControl(DynamicControl.GetControlIDFromColumnName(columnName));
228         }
229
230         /// <summary>
231         /// Make the SelectedIndex sync up with the PersistedSelection. Concretely, what it means is that
232         /// if you select a row and then page away (or sort), the selection remains on that row
233         /// even if it's not currently visible.
234         /// </summary>
235         [Obsolete("Use the EnablePersistedSelection property on a databound control such as GridView or ListView.")]
236         public static void EnablePersistedSelection(this BaseDataBoundControl dataBoundControl) {
237             EnablePersistedSelectionInternal(dataBoundControl);
238         }
239
240         internal static void EnablePersistedSelectionInternal(BaseDataBoundControl dataBoundControl) {
241             IDataBoundListControl dataBoundListControl = dataBoundControl as IDataBoundListControl;
242             if (dataBoundListControl != null) {
243                 dataBoundListControl.EnablePersistedSelection = true;
244                 // 
245
246                 if (dataBoundListControl.SelectedIndex < 0) {
247                     // Force the first item to be selected
248                     dataBoundListControl.SelectedIndex = 0;
249                 }
250             }
251         }
252
253         /// <summary>
254         /// Set the DataLoadOptions on a Linq To Sql datasource to force all the FK entities
255         /// to be directly loaded.
256         /// </summary>
257         /// <param name="dataSource">The data source for which we want to preload FKs</param>
258         /// <param name="rowType">The type of the entities returned by the data source</param>
259         public static void LoadWithForeignKeys(this LinqDataSource dataSource, Type rowType) {
260             dataSource.ContextCreated += delegate(object sender, LinqDataSourceStatusEventArgs e) {
261                 // This only applies to a DLinq data context
262                 var context = e.Result as DataContext;
263                 if (context == null)
264                     return;
265
266                 DataLoadOptions loadOptions = null;
267                 ParameterExpression tableParameter = null;
268                 System.Data.Linq.Mapping.MetaTable metaTable = context.Mapping.GetTable(rowType);
269                 foreach (System.Data.Linq.Mapping.MetaDataMember member in metaTable.RowType.DataMembers) {
270                     if (member.IsAssociation && !member.Association.IsMany) {
271                         if (member.Type.Equals(rowType)) continue;
272                         if (loadOptions == null) {
273                             loadOptions = new DataLoadOptions();
274                             tableParameter = Expression.Parameter(rowType, "e");
275                         }
276                         var memberExpression = Expression.Property(tableParameter, member.Name);
277                         loadOptions.LoadWith(Expression.Lambda(memberExpression, tableParameter));
278                     }
279                 }
280
281                 if (loadOptions != null) {
282                     context.LoadOptions = loadOptions;
283                 }
284             };
285         }
286
287         public static void LoadWith<TEntity>(this LinqDataSource dataSource) {
288             LoadWithForeignKeys(dataSource, typeof(TEntity));
289         }
290
291         /// <summary>
292         /// Apply potential HTML encoding and formatting to a string that needs to be displayed
293         /// This logic is mostly copied from BoundField.FormatDataValue, but omits the old Whidbey behavior path
294         /// </summary>
295         /// <param name="fieldValue">The value that should be formatted</param>
296         /// <param name="formattingOptions">The IFieldFormattingOptions to use. This is useful when using options different from the column's</param>
297         /// <returns>the formatted value</returns>
298         public static string FormatValue(this IFieldFormattingOptions formattingOptions, object fieldValue) {
299
300             string formattedValue = String.Empty;
301
302             if (fieldValue != null) {
303                 string dataValueString = fieldValue.ToString();
304                 string formatting = formattingOptions.DataFormatString;
305                 int dataValueStringLength = dataValueString.Length;
306
307                 // If the result is still empty and ConvertEmptyStringToNull=true, replace the value with the NullDisplayText
308                 if (dataValueStringLength == 0 && formattingOptions.ConvertEmptyStringToNull) {
309                     dataValueString = formattingOptions.NullDisplayText;
310                 }
311                 else {
312                     // If there's a format string, apply it to the raw data value
313                     // If there's no format string, then dataValueString already has the right value
314                     if (!String.IsNullOrEmpty(formatting)) {
315                         dataValueString = String.Format(CultureInfo.CurrentCulture, formatting, fieldValue);
316                     }
317
318                     // Optionally HTML encode the value (including the format string, if any was applied)
319                     if (!String.IsNullOrEmpty(dataValueString) && formattingOptions.HtmlEncode) {
320                         dataValueString = HttpUtility.HtmlEncode(dataValueString);
321                     }
322                 }
323
324                 formattedValue = dataValueString;
325             }
326             else {
327                 formattedValue = formattingOptions.NullDisplayText;
328             }
329
330             return formattedValue;
331         }
332
333         /// <summary>
334         /// Similar to FormatValue, but the string is to be used when the field is in edit mode
335         /// </summary>
336         public static string FormatEditValue(this IFieldFormattingOptions formattingOptions, object fieldValue) {
337             string valueString;
338
339             // Apply the format string to it if that flag is set.  Otherwise use it as is.
340             if (formattingOptions.ApplyFormatInEditMode) {
341                 valueString = formattingOptions.FormatValue(fieldValue);
342             }
343             else {
344                 valueString = (fieldValue != null) ? fieldValue.ToString() : String.Empty;
345             }
346
347             // Trim any trailing spaces as they cause unwanted behavior (since we limit the input length and the
348             // spaces cause the limit to be reach prematurely)
349             valueString = valueString.TrimEnd();
350
351             return valueString;
352         }
353
354         /// <summary>
355         /// Return either the input value or null based on ConvertEmptyStringToNull and NullDisplayText
356         /// </summary>
357         /// <param name="formattingOptions">the formatting options object</param>
358         /// <param name="value">The input value</param>
359         /// <returns>The converted value</returns>
360         public static object ConvertEditedValue(this IFieldFormattingOptions formattingOptions, string value) {
361             // If it's an empty string and ConvertEmptyStringToNull is set, make it null
362             if (String.IsNullOrEmpty(value) && formattingOptions.ConvertEmptyStringToNull) {
363                 return null;
364             }
365
366             // If it's the NullDisplayText, return null
367             string nullDisplayText = formattingOptions.NullDisplayText;
368             if (value == nullDisplayText && !String.IsNullOrEmpty(nullDisplayText)) {
369                 return null;
370             }
371
372             // Otherwise, return it unchanged
373             return value;
374         }
375
376         /// <summary>
377         /// If this column represents an enumeration type, this method returns that type. The caloumn can represent
378         /// an enumeration type if the underlying type is an enum, or if it is decoareted with EnumDataTypeAttribute.
379         /// If this column does not represent an enum, this method returns null.
380         /// </summary>
381         /// <param name="column"></param>
382         /// <returns></returns>
383         [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "The interface is internal")]
384         public static Type GetEnumType(this MetaColumn column) {
385             return GetEnumType((IMetaColumn)column);
386         }
387
388         internal static Type GetEnumType(this IMetaColumn column) {
389             return column.Attributes.GetAttributePropertyValue<EnumDataTypeAttribute, Type>(a => a.EnumType, null) ??
390                 (column.ColumnType.IsEnum ? column.ColumnType : null);
391         }
392
393         internal static bool IsEnumType(this MetaColumn column, out Type enumType) {
394             enumType = column.GetEnumType();
395             return enumType != null;
396         }
397     }
398 }