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