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 BuildVirtualPath(string templateName, MetaColumn column, DataBoundControlMode mode)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 CreateFieldTemplate(MetaColumn column, DataBoundControlMode mode, string uiHint)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 GetFieldTemplateVirtualPath(MetaColumn column, DataBoundControlMode mode, string uiHint)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 GetFallbackType(Type columnType, MetaColumn column, DataBoundControlMode mode)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 ColumnTypeToSpecialName(Type columnType, MetaColumn column, DataBoundControlMode mode)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 GetExistingTemplateVirtualPath(string baseName, MetaColumn column, DataBoundControlMode mode)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 GetTemplateForDataType(DataType dataType, string customDataType, string uiHint, MetaColumn column, DataBoundControlMode mode)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 Initialize(MetaModel model)267 public virtual void Initialize (MetaModel model) 268 { 269 Model = model; 270 } 271 PreprocessMode(MetaColumn column, DataBoundControlMode mode)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 } 291