reduce memory use on Attribute() caused by Attributes().
[mono.git] / mcs / class / System.Web.DynamicData / System.Web.DynamicData / FieldTemplateFactory.cs
1 //
2 // FieldTemplateFactory.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 //
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 // 
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 // 
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 //
31 using System;
32 using System.Collections;
33 using System.Collections.Generic;
34 using System.Collections.Specialized;
35 using System.ComponentModel;
36 using System.ComponentModel.DataAnnotations;
37 using System.Globalization;
38 using System.IO;
39 using System.Security.Permissions;
40 using System.Security.Principal;
41 using System.Web.Caching;
42 using System.Web.Compilation;
43 using System.Web.Hosting;
44 using System.Web.UI.WebControls;
45
46 namespace System.Web.DynamicData
47 {
48         [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
49         [AspNetHostingPermission (SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
50         public class FieldTemplateFactory : IFieldTemplateFactory
51         {
52                 const string DEFAULT_TEMPLATE_FOLDER_VIRTUAL_PATH = "FieldTemplates/";
53
54                 static readonly Dictionary <Type, Type> typeFallbacks = new Dictionary <Type, Type> () {
55                         {typeof (float), typeof (decimal)},
56                         {typeof (double), typeof (decimal)},
57                         {typeof (short), typeof (int)},
58                         {typeof (long), typeof (int)},
59                         {typeof (byte), typeof (int)},
60                         {typeof (char), typeof (string)},
61                         {typeof (int), typeof (string)},
62                         {typeof (decimal), typeof (string)},
63                         {typeof (Guid), typeof (string)},
64                         {typeof (DateTime), typeof (string)},
65                         {typeof (DateTimeOffset), typeof (string)},
66                         {typeof (TimeSpan), typeof (string)}
67                 };
68                 
69                 string templateFolderVirtualPath;
70                 string userTemplateVirtualPath;
71                 
72                 public MetaModel Model { get; private set; }
73
74                 public string TemplateFolderVirtualPath {
75                         get {
76                                 if (templateFolderVirtualPath == null) {
77                                         MetaModel m = Model;
78                                         string virtualPath = userTemplateVirtualPath == null ? DEFAULT_TEMPLATE_FOLDER_VIRTUAL_PATH : userTemplateVirtualPath;
79
80                                         if (m != null)
81                                                 templateFolderVirtualPath = VirtualPathUtility.Combine (m.DynamicDataFolderVirtualPath, virtualPath);
82                                         else
83                                                 templateFolderVirtualPath = virtualPath;
84
85                                         templateFolderVirtualPath = VirtualPathUtility.AppendTrailingSlash (templateFolderVirtualPath);
86                                 }
87
88                                 return templateFolderVirtualPath;
89                         }
90                         
91                         set {
92                                 userTemplateVirtualPath = value;
93                                 templateFolderVirtualPath = null;
94                         }
95                 }
96
97                 public virtual string BuildVirtualPath (string templateName, MetaColumn column, DataBoundControlMode mode)
98                 {
99                         // Tests show the 'column' parameter is not used here
100                         
101                         if (String.IsNullOrEmpty (templateName))
102                                 throw new ArgumentNullException ("templateName");
103
104                         string basePath = TemplateFolderVirtualPath;
105                         string suffix;
106
107                         switch (mode) {
108                                 default:
109                                 case DataBoundControlMode.ReadOnly:
110                                         suffix = String.Empty;
111                                         break;
112
113                                 case DataBoundControlMode.Edit:
114                                         suffix = "_Edit";
115                                         break;
116
117                                 case DataBoundControlMode.Insert:
118                                         suffix = "_Insert";
119                                         break;
120                         }
121                         
122                         return basePath + templateName + suffix + ".ascx";
123                 }
124
125                 public virtual IFieldTemplate CreateFieldTemplate (MetaColumn column, DataBoundControlMode mode, string uiHint)
126                 {
127                         // NO checks are made on parameters in .NET, but well "handle" the NREX
128                         // throws in the other methods
129                         string virtualPath = GetFieldTemplateVirtualPath (column, mode, uiHint);
130                         if (String.IsNullOrEmpty (virtualPath))
131                                 return null;
132                         
133                         return BuildManager.CreateInstanceFromVirtualPath (virtualPath, typeof (IFieldTemplate)) as IFieldTemplate;
134                 }
135
136                 public virtual string GetFieldTemplateVirtualPath (MetaColumn column, DataBoundControlMode mode, string uiHint)
137                 {
138                         // NO checks are made on parameters in .NET, but well "handle" the NREX
139                         // throws in the other methods
140                         DataBoundControlMode newMode = PreprocessMode (column, mode);
141
142                         // The algorithm is as follows:
143                         //
144                         //  1. If column has a DataTypeAttribute on it, get the data type
145                         //     - if it's Custom data type, uiHint is used unconditionally
146                         //     - if it's not a custom type, ignore uiHint and choose template based
147                         //       on type
148                         //
149                         //  2. If #1 is false and uiHint is not empty, use uiHint if the template
150                         //     exists
151                         //
152                         //  3. If #2 is false, look up type according to the following algorithm:
153                         //
154                         //     1. lookup column type's full name
155                         //     2. if #1 fails, look up short type name
156                         //     3. if #2 fails, map type to special type name (Int -> Integer, String
157                         //        -> Text etc)
158                         //     4. if #3 fails, try to find a fallback type
159                         //     5. if #4 fails, check if it's a foreign key or child column
160                         //     6. if #5 fails, return null
161                         //
162                         //     From: http://msdn.microsoft.com/en-us/library/cc488523.aspx (augmented)
163                         //
164
165                         DataTypeAttribute attr = column.DataTypeAttribute;
166                         bool uiHintPresent = !String.IsNullOrEmpty (uiHint);
167                         string templatePath = null;
168                         int step = uiHintPresent ? 0 : 1;
169                         Type columnType = column.ColumnType;
170
171                         if (!uiHintPresent && attr == null) {
172                                 if (column is MetaChildrenColumn)
173                                         templatePath = GetExistingTemplateVirtualPath ("Children", column, newMode);
174                                 else if (column is MetaForeignKeyColumn)
175                                         templatePath = GetExistingTemplateVirtualPath ("ForeignKey", column, newMode);
176                         }
177                         
178                         while (step < 6 && templatePath == null) {
179                                 switch (step) {
180                                         case 0:
181                                                 templatePath = GetExistingTemplateVirtualPath (uiHint, column, newMode);
182                                                 break;
183
184                                         case 1:
185                                                 if (attr != null)
186                                                         templatePath = GetTemplateForDataType (attr.DataType, attr.GetDataTypeName (), uiHint, column, newMode);
187                                                 break;
188                                                         
189                                         case 2:
190                                                 templatePath = GetExistingTemplateVirtualPath (columnType.FullName, column, newMode);
191                                                 break;
192
193                                         case 3:
194                                                 templatePath = GetExistingTemplateVirtualPath (columnType.Name, column, newMode);
195                                                 break;
196
197                                         case 4:
198                                                 templatePath = ColumnTypeToSpecialName (columnType, column, newMode);
199                                                 break;
200
201                                         case 5:
202                                                 columnType = GetFallbackType (columnType, column, newMode);
203                                                 if (columnType == null)
204                                                         step = 5;
205                                                 else
206                                                         step = uiHintPresent ? 0 : 1;
207                                                 break;
208                                 }
209
210                                 step++;
211                         }
212
213                         return templatePath;
214                 }
215
216                 Type GetFallbackType (Type columnType, MetaColumn column, DataBoundControlMode mode)
217                 {
218                         Type ret;
219                         if (typeFallbacks.TryGetValue (columnType, out ret))
220                                 return ret;
221                         
222                         return null;
223                 }
224                 
225                 string ColumnTypeToSpecialName (Type columnType, MetaColumn column, DataBoundControlMode mode)
226                 {
227                         if (columnType == typeof (int))
228                                 return GetExistingTemplateVirtualPath ("Integer", column, mode);
229
230                         if (columnType == typeof (string))
231                                 return GetExistingTemplateVirtualPath ("Text", column, mode);
232                         
233                         return null;
234                 }
235                 
236                 string GetExistingTemplateVirtualPath (string baseName, MetaColumn column, DataBoundControlMode mode)
237                 {
238                         string templatePath = BuildVirtualPath (baseName, column, mode);
239                         if (String.IsNullOrEmpty (templatePath))
240                                 return null;
241
242                         // TODO: cache positive hits (and watch for removal events on those)
243                         string physicalPath = HostingEnvironment.MapPath (templatePath);
244                         if (File.Exists (physicalPath))
245                                 return templatePath;
246
247                         return null;
248                 }
249                 
250                 string GetTemplateForDataType (DataType dataType, string customDataType, string uiHint, MetaColumn column, DataBoundControlMode mode)
251                 {
252                         switch (dataType) {
253                                 case DataType.Custom:
254                                         return GetExistingTemplateVirtualPath (customDataType, column, mode);
255
256                                 case DataType.DateTime:
257                                         return GetExistingTemplateVirtualPath ("DateTime", column, mode);
258
259                                 case DataType.MultilineText:
260                                         return GetExistingTemplateVirtualPath ("MultilineText", column, mode);
261                                         
262                                 default:
263                                         return GetExistingTemplateVirtualPath ("Text", column, mode);
264                         }
265                 }
266                 
267                 public virtual void Initialize (MetaModel model)
268                 {
269                         Model = model;
270                 }
271
272                 public virtual DataBoundControlMode PreprocessMode (MetaColumn column, DataBoundControlMode mode)
273                 {
274                         // In good tradition of .NET's DynamicData, let's not check the
275                         // parameters...
276                         if (column == null)
277                                 throw new NullReferenceException ();
278
279                         if (column.IsGenerated)
280                                 return DataBoundControlMode.ReadOnly;
281                         
282                         if (column.IsPrimaryKey) {
283                                 if (mode == DataBoundControlMode.Edit)
284                                         return DataBoundControlMode.ReadOnly;
285                         }
286                         
287                         return mode;    
288                 }
289         }
290 }