Fixing an issue with output parameters that are of type IntPtr
[castle.git] / MonoRail / Castle.MonoRail.Framework / Helpers / AbstractFormRelatedHelper.cs
blobb12af0688c8dc2ede0225898b5bac654deed0cda
1 // Copyright 2004-2008 Castle Project - http://www.castleproject.org/
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 namespace Castle.MonoRail.Framework.Helpers
17 using System;
18 using System.Collections;
19 using System.Collections.Generic;
20 using System.Collections.Specialized;
21 using System.Data;
22 using System.Reflection;
23 using System.Text;
24 using Castle.Components.Binder;
25 using Castle.Components.Validator;
26 using Castle.Core.Logging;
27 using Core;
28 using Internal;
29 using ValidationStrategy;
31 /// <summary>
32 /// Represents all scopes that the <see cref="FormHelper"/>
33 /// uses to search for root values
34 /// </summary>
35 public enum RequestContext
37 /// <summary>
38 /// All scopes should be searched
39 /// </summary>
40 All,
41 /// <summary>
42 /// Only PropertyBag should be searched
43 /// </summary>
44 PropertyBag,
45 /// <summary>
46 /// Only Flash should be searched
47 /// </summary>
48 Flash,
49 /// <summary>
50 /// Only Session should be searched
51 /// </summary>
52 Session,
53 /// <summary>
54 /// Only Request should be searched
55 /// </summary>
56 Request,
57 /// <summary>
58 /// Only Params should be searched
59 /// </summary>
60 Params
63 /// <summary>
64 /// Base class that exposes common operations for form handling, field bindings and so on.
65 /// </summary>
66 public abstract class AbstractFormRelatedHelper : AbstractHelper
68 /// <summary>
69 /// Common property flags for reflection
70 /// </summary>
71 protected static readonly BindingFlags PropertyFlags = BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase;
72 /// <summary>
73 /// Common property flags for reflection (with declared only)
74 /// </summary>
75 protected static readonly BindingFlags propertyFlagsDeclaredOnly = BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.DeclaredOnly;
76 /// <summary>
77 /// Common field flags for reflection
78 /// </summary>
79 protected static readonly BindingFlags FieldFlags = BindingFlags.GetField | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase;
81 /// <summary>
82 /// Logger instance
83 /// </summary>
84 protected static ILogger logger = NullLogger.Instance;
86 /// <summary>
87 ///
88 /// </summary>
89 protected BrowserValidationConfiguration validationConfig;
90 /// <summary>
91 ///
92 /// </summary>
93 private IBrowserValidatorProvider validatorProvider;
94 /// <summary>
95 ///
96 /// </summary>
97 protected readonly Stack objectStack = new Stack();
99 private IValidatorRegistry validatorRegistry;
100 private ValidatorRunner validatorRunner;
101 private bool isValidationDisabled;
103 /// <summary>
104 /// Initializes a new instance of the <see cref="AbstractFormRelatedHelper"/> class.
105 /// </summary>
106 protected AbstractFormRelatedHelper()
110 /// <summary>
111 /// Initializes a new instance of the <see cref="AbstractFormRelatedHelper"/> class.
112 /// </summary>
113 /// <param name="validatorRegistry">The validator registry.</param>
114 /// <param name="validatorRunner">The validator runner.</param>
115 protected AbstractFormRelatedHelper(IValidatorRegistry validatorRegistry, ValidatorRunner validatorRunner)
117 this.validatorRegistry = validatorRegistry;
118 this.validatorRunner = validatorRunner;
121 /// <summary>
122 /// Initializes a new instance of the <see cref="AbstractFormRelatedHelper"/> class.
123 /// </summary>
124 /// <param name="engineContext">The engine context.</param>
125 protected AbstractFormRelatedHelper(IEngineContext engineContext) : base(engineContext)
129 /// <summary>
130 /// Gets or sets the validator provider.
131 /// </summary>
132 /// <value>The validator provider.</value>
133 public IBrowserValidatorProvider ValidatorProvider
135 get { return validatorProvider; }
136 set { validatorProvider = value; }
139 /// <summary>
140 /// Gets or sets the validator runner.
141 /// </summary>
142 /// <value>The validator runner.</value>
143 public ValidatorRunner ValidatorRunner
145 get { return validatorRunner; }
146 set { validatorRunner = value; }
149 /// <summary>
150 /// Gets or sets the validator registry.
151 /// </summary>
152 /// <value>The validator registry.</value>
153 public IValidatorRegistry ValidatorRegistry
155 get { return validatorRegistry; }
156 set { validatorRegistry = value; }
159 #region protected members
161 /// <summary>
162 /// Rewrites the target if within object scope.
163 /// </summary>
164 /// <param name="target">The target.</param>
165 /// <returns></returns>
166 protected string RewriteTargetIfWithinObjectScope(string target)
168 if (objectStack.Count == 0)
170 return target;
172 else
174 return ((FormScopeInfo) objectStack.Peek()).RootTarget + "." + target;
178 /// <summary>
179 /// Creates the specified input element
180 /// using the specified parameters to supply the name, value, id and others
181 /// html attributes.
182 /// </summary>
183 /// <param name="type"></param>
184 /// <param name="target">The object to get the value from and to be based on to create the element name.</param>
185 /// <param name="value"></param>
186 /// <param name="attributes">Attributes for the FormHelper method and for the html element it generates</param>
187 /// <returns>The generated form element</returns>
188 protected virtual string CreateInputElement(string type, string target, Object value, IDictionary attributes)
190 if (value == null)
192 value = CommonUtils.ObtainEntryAndRemove(attributes, "defaultValue");
195 string id = CreateHtmlId(attributes, target);
197 return CreateInputElement(type, id, target, FormatIfNecessary(value, attributes), attributes);
200 /// <summary>
201 /// Creates the specified input element
202 /// using the specified parameters to supply the name, value, id and others
203 /// html attributes.
204 /// </summary>
205 /// <param name="type"></param>
206 /// <param name="id"></param>
207 /// <param name="target">The object to get the value from and to be based on to create the element name.</param>
208 /// <param name="value"></param>
209 /// <param name="attributes">Attributes for the FormHelper method and for the html element it generates</param>
210 /// <returns>The generated form element</returns>
211 protected virtual string CreateInputElement(string type, string id, string target, string value, IDictionary attributes)
213 value = FormatIfNecessary(value, attributes);
215 value = SafeHtmlEncode(value);
217 if (attributes != null && attributes.Contains("mask"))
219 string mask = CommonUtils.ObtainEntryAndRemove(attributes, "mask");
220 string maskSep = CommonUtils.ObtainEntryAndRemove(attributes, "mask_separator", "-");
222 string onBlur = CommonUtils.ObtainEntryAndRemove(attributes, "onBlur", "void(0)");
223 string onKeyUp = CommonUtils.ObtainEntryAndRemove(attributes, "onKeyUp", "void(0)");
225 string js = "return monorail_formhelper_mask(event,this,'" + mask + "','" + maskSep + "');";
227 attributes["onBlur"] = "javascript:" + onBlur + ";" + js;
228 attributes["onKeyUp"] = "javascript:" + onKeyUp + ";" + js;
231 return String.Format("<input type=\"{0}\" id=\"{1}\" name=\"{2}\" value=\"{3}\" {4}/>",
232 type, id, target, value, GetAttributes(attributes));
235 /// <summary>
236 /// Creates the input element.
237 /// </summary>
238 /// <param name="type">The type.</param>
239 /// <param name="value">The value.</param>
240 /// <param name="attributes">The attributes.</param>
241 /// <returns></returns>
242 protected virtual string CreateInputElement(string type, string value, IDictionary attributes)
244 return String.Format("<input type=\"{0}\" value=\"{1}\" {2}/>",
245 type, FormatIfNecessary(value, attributes), GetAttributes(attributes));
248 /// <summary>
249 /// Formats if necessary.
250 /// </summary>
251 /// <param name="value">The value.</param>
252 /// <param name="attributes">The attributes.</param>
253 /// <returns></returns>
254 protected static string FormatIfNecessary(object value, IDictionary attributes)
256 string formatString = CommonUtils.ObtainEntryAndRemove(attributes, "textformat");
258 if (value != null && formatString != null)
260 IFormattable formattable = value as IFormattable;
262 if (formattable != null)
264 value = formattable.ToString(formatString, null);
267 else if (value == null)
269 value = String.Empty;
272 return value.ToString();
275 /// <summary>
276 /// Obtains the target property.
277 /// </summary>
278 /// <param name="context">The context.</param>
279 /// <param name="target">The target.</param>
280 /// <param name="action">The action.</param>
281 /// <returns></returns>
282 protected PropertyInfo ObtainTargetProperty(RequestContext context, string target, Action<PropertyInfo> action)
284 string[] pieces;
286 Type root = ObtainRootType(context, target, out pieces);
288 if (root != null && pieces.Length > 1)
290 return QueryPropertyInfoRecursive(root, pieces, 1, action);
293 return null;
296 /// <summary>
297 /// Queries the context for the target value
298 /// </summary>
299 /// <param name="target">The object to get the value from and to be based on to create the element name.</param>
300 /// <returns>The generated form element</returns>
301 protected object ObtainValue(string target)
303 return ObtainValue(RequestContext.All, target);
306 /// <summary>
307 /// Queries the context for the target value
308 /// </summary>
309 /// <param name="context"></param>
310 /// <param name="target">The object to get the value from and to be based on to create the element name.</param>
311 /// <returns>The generated form element</returns>
312 protected object ObtainValue(RequestContext context, string target)
314 string[] pieces;
316 object rootInstance = ObtainRootInstance(context, target, out pieces);
318 if (rootInstance != null && pieces.Length > 1)
320 return QueryPropertyRecursive(rootInstance, pieces, 1);
323 return rootInstance;
326 /// <summary>
327 /// Obtains the root instance.
328 /// </summary>
329 /// <param name="context">The context.</param>
330 /// <param name="target">The object to get the value from and to be based on to create the element name.</param>
331 /// <returns>The generated form element</returns>
332 protected object ObtainRootInstance(RequestContext context, string target)
334 object rootInstance = null;
336 if (context == RequestContext.All || context == RequestContext.PropertyBag)
338 rootInstance = ControllerContext.PropertyBag[target];
340 if (rootInstance == null && (context == RequestContext.All || context == RequestContext.Flash))
342 rootInstance = Context.Flash[target];
344 if (rootInstance == null && (context == RequestContext.All || context == RequestContext.Session))
346 rootInstance = Context.Session[target];
348 if (rootInstance == null && (context == RequestContext.All || context == RequestContext.Params))
350 rootInstance = Context.Request.Params[target];
352 if (rootInstance == null && (context == RequestContext.All || context == RequestContext.Request))
354 rootInstance = Context.Items[target];
357 return rootInstance;
360 /// <summary>
361 /// Obtains the root instance.
362 /// </summary>
363 /// <param name="context">The context.</param>
364 /// <param name="target">The target.</param>
365 /// <param name="pieces">The pieces.</param>
366 /// <returns></returns>
367 protected object ObtainRootInstance(RequestContext context, string target, out string[] pieces)
369 pieces = target.Split(new char[] { '.' });
371 string root = pieces[0];
373 int index;
375 bool isIndexed = CheckForExistenceAndExtractIndex(ref root, out index);
377 object rootInstance = ObtainRootInstance(context, root);
379 if (rootInstance == null)
381 return null;
384 if (isIndexed)
386 AssertIsValidArray(rootInstance, root, index);
389 if (!isIndexed && pieces.Length == 1)
391 return rootInstance;
393 else if (isIndexed)
395 rootInstance = GetArrayElement(rootInstance, index);
398 return rootInstance;
401 /// <summary>
402 /// Obtains the type of the root.
403 /// </summary>
404 /// <param name="context">The context.</param>
405 /// <param name="target">The target.</param>
406 /// <param name="pieces">The pieces.</param>
407 /// <returns></returns>
408 private Type ObtainRootType(RequestContext context, string target, out string[] pieces)
410 pieces = target.Split(new char[] { '.' });
412 Type foundType = (Type) ControllerContext.PropertyBag[pieces[0] + "type"];
414 if (foundType == null)
416 string trimmed = pieces[0].Split('[')[0];
418 foundType = (Type) ControllerContext.PropertyBag[trimmed + "type"];
420 if (foundType == null)
422 object root = ObtainRootInstance(context, target, out pieces);
424 if (root != null)
426 foundType = root.GetType();
431 return foundType;
434 /// <summary>
435 /// Queries the property info recursive.
436 /// </summary>
437 /// <param name="type">The type.</param>
438 /// <param name="propertyPath">The property path.</param>
439 /// <returns></returns>
440 protected static PropertyInfo QueryPropertyInfoRecursive(Type type, string[] propertyPath)
442 return QueryPropertyInfoRecursive(type, propertyPath, 0, null);
445 /// <summary>
446 /// Queries the property info recursive.
447 /// </summary>
448 /// <param name="type">The type.</param>
449 /// <param name="propertyPath">The property path.</param>
450 /// <param name="piece">The piece.</param>
451 /// <param name="action">The action.</param>
452 /// <returns></returns>
453 protected static PropertyInfo QueryPropertyInfoRecursive(Type type, string[] propertyPath, int piece, Action<PropertyInfo> action)
455 string property = propertyPath[piece];
456 int index;
458 bool isIndexed = CheckForExistenceAndExtractIndex(ref property, out index);
460 //PropertyInfo propertyInfo = type.GetProperty(property, ResolveFlagsToUse(type));
461 PropertyInfo propertyInfo = GetPropertyInfo(type, property);
463 if (propertyInfo == null)
465 if (logger.IsErrorEnabled)
467 logger.Error("No public property '{0}' found on type '{1}'", property, type.FullName);
470 return null;
473 if (!propertyInfo.CanRead)
475 throw new BindingException("Property '{0}' for type '{1}' can not be read",
476 propertyInfo.Name, type.FullName);
479 if (propertyInfo.GetIndexParameters().Length != 0)
481 throw new BindingException("Property '{0}' for type '{1}' has indexes, which are not supported",
482 propertyInfo.Name, type.FullName);
485 if (action != null)
487 action(propertyInfo);
490 type = propertyInfo.PropertyType;
492 if (typeof(ICollection).IsAssignableFrom(type))
494 return null;
497 if (isIndexed)
499 if (type.IsGenericType)
501 Type[] args = type.GetGenericArguments();
502 if (args.Length != 1)
503 throw new BindingException("Expected the generic indexed property '{0}' to be of 1 element", type.Name);
504 type = args[0];
507 if (type.IsArray)
509 type = type.GetElementType();
513 if (piece + 1 == propertyPath.Length)
515 return propertyInfo;
518 return QueryPropertyInfoRecursive(type, propertyPath, piece + 1, action);
521 /// <summary>
522 /// Query property paths agains the rootInstance type
523 /// </summary>
524 /// <param name="rootInstance">the object to query</param>
525 /// <param name="propertyPath">property path</param>
526 /// <returns>The generated form element</returns>
527 protected static object QueryPropertyRecursive(object rootInstance, string[] propertyPath)
529 return QueryPropertyRecursive(rootInstance, propertyPath, 0);
532 /// <summary>
533 /// Query property paths agains the rootInstance type
534 /// </summary>
535 /// <param name="rootInstance">the object to query</param>
536 /// <param name="propertyPath">property path</param>
537 /// <param name="piece">start index</param>
538 /// <returns>The generated form element</returns>
539 protected static object QueryPropertyRecursive(object rootInstance, string[] propertyPath, int piece)
541 string property = propertyPath[piece];
542 int index;
544 Type instanceType = rootInstance.GetType();
546 bool isIndexed = CheckForExistenceAndExtractIndex(ref property, out index);
548 //PropertyInfo propertyInfo = instanceType.GetProperty(property, ResolveFlagsToUse(instanceType));
549 PropertyInfo propertyInfo = GetPropertyInfo(instanceType, property);
551 object instance = null;
553 if (propertyInfo == null)
555 FieldInfo fieldInfo = instanceType.GetField(property, FieldFlags);
557 if (fieldInfo != null)
559 instance = fieldInfo.GetValue(rootInstance);
562 else
564 if (!propertyInfo.CanRead)
566 throw new BindingException("Property '{0}' for type '{1}' can not be read",
567 propertyInfo.Name, instanceType.FullName);
570 if (propertyInfo.GetIndexParameters().Length != 0)
572 throw new BindingException("Property '{0}' for type '{1}' has indexes, which are not supported",
573 propertyInfo.Name, instanceType.FullName);
576 instance = propertyInfo.GetValue(rootInstance, null);
579 if (isIndexed && instance != null)
581 AssertIsValidArray(instance, property, index);
583 instance = GetArrayElement(instance, index);
586 if (instance == null || piece + 1 == propertyPath.Length)
588 return instance;
591 return QueryPropertyRecursive(instance, propertyPath, piece + 1);
594 /// <summary>
595 /// Creates the HTML id.
596 /// </summary>
597 /// <param name="attributes">The attributes.</param>
598 /// <param name="target">The target.</param>
599 /// <returns>The generated form element</returns>
600 protected static string CreateHtmlId(IDictionary attributes, string target)
602 return CreateHtmlId(attributes, target, true);
605 /// <summary>
606 /// Creates the HTML id.
607 /// </summary>
608 /// <param name="attributes">The attributes.</param>
609 /// <param name="target">The target.</param>
610 /// <param name="removeEntry">if set to <c>true</c> [remove entry].</param>
611 /// <returns>The generated form element</returns>
612 protected static string CreateHtmlId(IDictionary attributes, string target, bool removeEntry)
614 string id;
616 if (removeEntry)
618 id = CommonUtils.ObtainEntryAndRemove(attributes, "id");
620 else
622 id = CommonUtils.ObtainEntry(attributes, "id");
625 if (id == null)
627 id = CreateHtmlId(target);
630 return id;
633 #endregion
636 #region Validation
638 /// <summary>
639 /// Disables the validation.
640 /// </summary>
641 public virtual void DisableValidation()
643 isValidationDisabled = true;
646 /// <summary>
647 /// Applies the validation.
648 /// </summary>
649 /// <param name="inputType">Type of the input.</param>
650 /// <param name="target">The target.</param>
651 /// <param name="attributes">The attributes.</param>
652 protected virtual void ApplyValidation(InputElementType inputType, string target, ref IDictionary attributes)
654 bool disableValidation = CommonUtils.ObtainEntryAndRemove(attributes, "disablevalidation", "false") == "true";
656 if (!IsValidationEnabled || disableValidation)
658 return;
661 if (validatorRegistry == null || validationConfig == null)
663 return;
666 if (attributes == null)
668 attributes = new HybridDictionary(true);
671 IValidator[] validators = CollectValidators(RequestContext.All, target);
673 IBrowserValidationGenerator generator = validatorProvider.CreateGenerator(validationConfig, inputType, attributes);
675 string id = CreateHtmlId(attributes, target, false);
677 foreach (IValidator validator in validators)
679 if (validator.SupportsBrowserValidation)
681 validator.ApplyBrowserValidation(validationConfig, inputType, generator, attributes, id);
686 private IValidator[] CollectValidators(RequestContext requestContext, string target)
688 List<IValidator> validators = new List<IValidator>();
690 ObtainTargetProperty(requestContext, target, delegate(PropertyInfo property)
692 validators.AddRange(validatorRegistry.GetValidators(validatorRunner, property.DeclaringType, property, RunWhen.Everytime));
695 return validators.ToArray();
698 /// <summary>
699 /// Gets a value indicating whether validation is enabled.
700 /// </summary>
701 /// <value>
702 /// <c>true</c> if validation is enabled; otherwise, <c>false</c>.
703 /// </value>
704 protected bool IsValidationEnabled
708 if (isValidationDisabled)
709 return false;
711 if (objectStack.Count == 0)
712 return true;
714 return ((FormScopeInfo) objectStack.Peek()).IsValidationEnabled;
718 #endregion
720 #region private helpers
722 private static void AssertIsValidArray(object instance, string property, int index)
724 Type instanceType = instance.GetType();
726 IList list = instance as IList;
728 bool validList = false;
730 if (list == null && instanceType.IsGenericType)
732 Type[] genArgs = instanceType.GetGenericArguments();
734 Type genList = typeof(System.Collections.Generic.IList<>).MakeGenericType(genArgs);
735 Type genTypeDef = instanceType.GetGenericTypeDefinition().MakeGenericType(genArgs);
737 validList = genList.IsAssignableFrom(genTypeDef);
740 if (!validList && list == null)
742 throw new MonoRailException("The property {0} is being accessed as " +
743 "an indexed property but does not seem to implement IList. " +
744 "In fact the type is {1}", property, instanceType.FullName);
747 if (index < 0)
749 throw new MonoRailException("The specified index '{0}' is outside the bounds " +
750 "of the array. Property {1}", index, property);
754 private static object GetArrayElement(object instance, int index)
756 IList list = instance as IList;
758 if (list == null && instance != null && instance.GetType().IsGenericType)
760 Type instanceType = instance.GetType();
762 Type[] genArguments = instanceType.GetGenericArguments();
764 Type genType = instanceType.GetGenericTypeDefinition().MakeGenericType(genArguments);
766 // I'm not going to retest for IList implementation as
767 // if we got here, the AssertIsValidArray has run successfully
769 PropertyInfo countPropInfo = genType.GetProperty("Count");
771 int count = (int) countPropInfo.GetValue(instance, null);
773 if (count == 0 || index + 1 > count)
775 return null;
778 PropertyInfo indexerPropInfo = genType.GetProperty("Item");
780 return indexerPropInfo.GetValue(instance, new object[] { index });
783 if (list == null || list.Count == 0 || index + 1 > list.Count)
785 return null;
788 return list[index];
791 private static bool CheckForExistenceAndExtractIndex(ref string property, out int index)
793 bool isIndexed = property.IndexOf('[') != -1;
795 index = -1;
797 if (isIndexed)
799 int start = property.IndexOf('[') + 1;
800 int len = property.IndexOf(']', start) - start;
802 string indexStr = property.Substring(start, len);
806 index = Convert.ToInt32(indexStr);
808 catch (Exception)
810 throw new MonoRailException("Could not convert (param {0}) index to Int32. Value is {1}",
811 property, indexStr);
814 property = property.Substring(0, start - 1);
817 return isIndexed;
820 /// <summary>
821 /// Compares the left and right value for equality.
822 /// </summary>
823 /// <param name="left">The left.</param>
824 /// <param name="right">The right.</param>
825 /// <returns></returns>
826 protected static bool AreEqual(object left, object right)
828 if (left == null || right == null)
829 return false;
831 if (left is string && right is String)
833 return String.Compare(left.ToString(), right.ToString()) == 0;
836 if (left.GetType() == right.GetType())
838 return right.Equals(left);
841 IConvertible convertible = left as IConvertible;
843 if (convertible != null)
847 object newleft = convertible.ToType(right.GetType(), null);
848 return (newleft.Equals(right));
850 catch (Exception)
852 // Do nothing
856 return left.ToString().Equals(right.ToString());
859 /// <summary>
860 /// Encodes the content if there is a valid context set for this helper.
861 /// </summary>
862 /// <param name="content">The content to be encoded.</param>
863 /// <returns></returns>
864 protected string SafeHtmlEncode(string content)
866 if (Context != null)
868 return HtmlEncode(content);
871 return content;
874 /// <summary>
875 /// Determines whether the present value matches the value on
876 /// the initialSetValue (which can be a single value or a set)
877 /// </summary>
878 /// <param name="value">Value from the datasource</param>
879 /// <param name="initialSetValue">Value from the initial selection set</param>
880 /// <param name="propertyOnInitialSet">Optional. Property to obtain the value from</param>
881 /// <param name="isMultiple"><c>true</c> if the initial selection is a set</param>
882 /// <returns><c>true</c> if it's selected</returns>
883 protected internal static bool IsPresent(object value, object initialSetValue,
884 ValueGetter propertyOnInitialSet, bool isMultiple)
886 if (!isMultiple)
888 object valueToCompare = initialSetValue;
890 if (propertyOnInitialSet != null)
892 // propertyOnInitialSet.GetValue(initialSetValue, null);
893 valueToCompare = propertyOnInitialSet.GetValue(initialSetValue);
896 return AreEqual(value, valueToCompare);
898 else
900 foreach (object item in (IEnumerable) initialSetValue)
902 object valueToCompare = item;
904 if (propertyOnInitialSet != null)
906 // valueToCompare = propertyOnInitialSet.GetValue(item, null);
907 valueToCompare = propertyOnInitialSet.GetValue(item);
910 if (AreEqual(value, valueToCompare))
912 return true;
917 return false;
920 /// <summary>
921 /// Adds the checked attribute to the attributes.
922 /// </summary>
923 /// <param name="attributes">The attributes.</param>
924 protected static void AddChecked(IDictionary attributes)
926 attributes["checked"] = "checked";
929 /// <summary>
930 /// Removes the checked attribute from the dictionary.
931 /// </summary>
932 /// <param name="attributes">The attributes.</param>
933 protected static void RemoveChecked(IDictionary attributes)
935 attributes.Remove("checked");
938 /// <summary>
939 /// Uses the specified name to create a valid id for the element
940 /// </summary>
941 /// <param name="name">The name.</param>
942 /// <returns></returns>
943 protected static string CreateHtmlId(string name)
945 StringBuilder sb = new StringBuilder(name.Length);
947 bool canUseUnderline = false;
949 foreach (char c in name.ToCharArray())
951 switch (c)
953 case '.':
954 case '[':
955 case ']':
956 if (canUseUnderline)
958 sb.Append('_');
959 canUseUnderline = false;
961 break;
962 default:
963 canUseUnderline = true;
964 sb.Append(c);
965 break;
970 return sb.ToString();
973 /// <summary>
974 /// Abstracts the approach to access values on objects.
975 /// </summary>
976 public abstract class ValueGetter
978 /// <summary>
979 /// Gets the name.
980 /// </summary>
981 /// <value>The name.</value>
982 public abstract string Name { get; }
984 /// <summary>
985 /// Gets the value.
986 /// </summary>
987 /// <param name="instance">The instance.</param>
988 /// <returns></returns>
989 public abstract object GetValue(object instance);
992 /// <summary>
993 /// Implementation of <see cref="ValueGetter"/>
994 /// that uses reflection to access values
995 /// </summary>
996 public class ReflectionValueGetter : ValueGetter
998 private PropertyInfo propInfo;
1000 /// <summary>
1001 /// Initializes a new instance of the <see cref="ReflectionValueGetter"/> class.
1002 /// </summary>
1003 /// <param name="propInfo">The prop info.</param>
1004 public ReflectionValueGetter(PropertyInfo propInfo)
1006 this.propInfo = propInfo;
1009 /// <summary>
1010 /// Gets the name.
1011 /// </summary>
1012 /// <value>The name.</value>
1013 public override string Name
1015 get { return propInfo.Name; }
1018 /// <summary>
1019 /// Gets the value.
1020 /// </summary>
1021 /// <param name="instance">The instance.</param>
1022 /// <returns></returns>
1023 public override object GetValue(object instance)
1027 return propInfo.GetValue(instance, null);
1029 catch (TargetException)
1031 PropertyInfo tempProp = instance.GetType().GetProperty(Name);
1033 if (tempProp == null)
1035 throw;
1038 return tempProp.GetValue(instance, null);
1043 /// <summary>
1044 /// Implementation of <see cref="ValueGetter"/>
1045 /// that uses reflection and recusion to access values
1046 /// </summary>
1047 public class RecursiveReflectionValueGetter : ValueGetter
1049 private readonly string[] keyName;
1050 private readonly string name = string.Empty;
1052 /// <summary>
1053 /// Initializes a new instance of the <see cref="RecursiveReflectionValueGetter"/> class.
1054 /// </summary>
1055 /// <param name="targetType">The target type to query</param>
1056 /// <param name="keyName">the property path</param>
1057 public RecursiveReflectionValueGetter(Type targetType, string keyName)
1059 this.keyName = keyName.Split('.');
1060 name = QueryPropertyInfoRecursive(targetType, this.keyName).Name;
1063 /// <summary>
1064 /// Gets the name.
1065 /// </summary>
1066 /// <value>The name.</value>
1067 public override string Name
1069 get { return name; }
1072 /// <summary>
1073 /// Gets the value.
1074 /// </summary>
1075 /// <param name="instance">The instance.</param>
1076 /// <returns></returns>
1077 public override object GetValue(object instance)
1081 return QueryPropertyRecursive(instance, keyName);
1083 catch (TargetException)
1085 PropertyInfo tempProp = instance.GetType().GetProperty(Name);
1087 if (tempProp == null)
1089 throw;
1092 return tempProp.GetValue(instance, null);
1097 /// <summary>
1098 /// Implementation of <see cref="ValueGetter"/>
1099 /// to access DataRow's value
1100 /// </summary>
1101 public class DataRowValueGetter : ValueGetter
1103 private readonly string columnName;
1105 /// <summary>
1106 /// Initializes a new instance of the <see cref="DataRowValueGetter"/> class.
1107 /// </summary>
1108 /// <param name="columnName">Name of the column.</param>
1109 public DataRowValueGetter(string columnName)
1111 this.columnName = columnName;
1114 /// <summary>
1115 /// Gets the name.
1116 /// </summary>
1117 /// <value>The name.</value>
1118 public override string Name
1120 get { return columnName; }
1123 /// <summary>
1124 /// Gets the value.
1125 /// </summary>
1126 /// <param name="instance">The instance.</param>
1127 /// <returns></returns>
1128 public override object GetValue(object instance)
1130 DataRow row = (DataRow) instance;
1132 return row[columnName];
1136 /// <summary>
1137 /// Implementation of <see cref="ValueGetter"/>
1138 /// to access DataRowView's value
1139 /// </summary>
1140 public class DataRowViewValueGetter : ValueGetter
1142 private readonly string columnName;
1144 /// <summary>
1145 /// Initializes a new instance of the <see cref="DataRowViewValueGetter"/> class.
1146 /// </summary>
1147 /// <param name="columnName">Name of the column.</param>
1148 public DataRowViewValueGetter(string columnName)
1150 this.columnName = columnName;
1153 /// <summary>
1154 /// Gets the name.
1155 /// </summary>
1156 /// <value>The name.</value>
1157 public override string Name
1159 get { return columnName; }
1162 /// <summary>
1163 /// Gets the value.
1164 /// </summary>
1165 /// <param name="instance">The instance.</param>
1166 /// <returns></returns>
1167 public override object GetValue(object instance)
1169 DataRowView row = (DataRowView) instance;
1171 return row[columnName];
1175 /// <summary>
1176 /// Empty implementation of a <see cref="ValueGetter"/>
1177 /// </summary>
1178 public class NoActionGetter : ValueGetter
1180 /// <summary>
1181 /// Gets the name.
1182 /// </summary>
1183 /// <value>The name.</value>
1184 public override string Name
1186 get { return string.Empty; }
1189 /// <summary>
1190 /// Gets the value.
1191 /// </summary>
1192 /// <param name="instance">The instance.</param>
1193 /// <returns></returns>
1194 public override object GetValue(object instance)
1196 return null;
1200 /// <summary>
1201 /// Implementation of <see cref="ValueGetter"/>
1202 /// to access enum fields
1203 /// </summary>
1204 public class EnumValueGetter : ValueGetter
1206 private readonly Type enumType;
1208 /// <summary>
1209 /// Initializes a new instance of the <see cref="EnumValueGetter"/> class.
1210 /// </summary>
1211 /// <param name="enumType">Type of the enum.</param>
1212 public EnumValueGetter(Type enumType)
1214 this.enumType = enumType;
1217 /// <summary>
1218 /// Gets the name.
1219 /// </summary>
1220 /// <value>The name.</value>
1221 public override string Name
1223 get { return string.Empty; }
1226 /// <summary>
1227 /// Gets the value.
1228 /// </summary>
1229 /// <param name="instance">The instance.</param>
1230 /// <returns></returns>
1231 public override object GetValue(object instance)
1233 return Convert.ToDecimal(Enum.Format(enumType, Enum.Parse(enumType, Convert.ToString(instance)), "d"));
1237 /// <summary>
1238 /// Abstract factory for <see cref="ValueGetter"/> implementations
1239 /// </summary>
1240 public class ValueGetterAbstractFactory
1242 /// <summary>
1243 /// Creates the specified target type.
1244 /// </summary>
1245 /// <param name="targetType">Type of the target.</param>
1246 /// <param name="keyName">Name of the key.</param>
1247 /// <returns></returns>
1248 public static ValueGetter Create(Type targetType, string keyName)
1250 if (targetType == null)
1252 return new NoActionGetter();
1254 else if (targetType == typeof(DataRow))
1256 return new DataRowValueGetter(keyName);
1258 else if (targetType == typeof(DataRowView))
1260 return new DataRowViewValueGetter(keyName);
1262 else if (targetType.IsEnum)
1264 return new EnumValueGetter(targetType);
1266 else
1268 PropertyInfo info = null;
1270 // check for recursion
1271 if (keyName.Contains("."))
1273 info = QueryPropertyInfoRecursive(targetType, keyName.Split('.'));
1275 if (info != null)
1277 return new RecursiveReflectionValueGetter(targetType, keyName);
1280 else
1282 info = GetPropertyInfo(targetType, keyName);
1284 if (info != null)
1286 return new ReflectionValueGetter(info);
1290 return null;
1295 /// <summary>
1296 /// Gets the property info for the property with the <paramref name="propertyName"/>.
1297 /// </summary>
1298 /// <param name="type">Type that has the property.</param>
1299 /// <param name="propertyName">Name of the property.</param>
1300 /// <returns></returns>
1301 protected static PropertyInfo GetPropertyInfo(Type type, string propertyName)
1303 PropertyInfo info = null;
1307 info = type.GetProperty(propertyName, ResolveFlagsToUse(type));
1309 catch (AmbiguousMatchException amex)
1311 // This is kind of an edge case, so tried it the normal way first,
1312 // seems to happen if you override a generic property
1314 if (logger.IsDebugEnabled)
1316 logger.DebugFormat(amex, "Retrieving property {0} on type {1} raised a {2}. Maybe it is generic and overriden. Will try to get the most specific property now.",
1317 propertyName, type, amex.GetType().Name);
1320 // Try again on instance only, loop through base type hierarchy
1321 Type baseType = type;
1322 while (info == null)
1324 info = baseType.GetProperty(propertyName, propertyFlagsDeclaredOnly);
1326 baseType = baseType.BaseType;
1327 if (baseType == typeof(object) || baseType == null)
1329 break;
1334 return info;
1337 private static BindingFlags ResolveFlagsToUse(Type type)
1339 if (type.Assembly.FullName.StartsWith("DynamicAssemblyProxyGen") || type.Assembly.FullName.StartsWith("DynamicProxyGenAssembly2"))
1341 return propertyFlagsDeclaredOnly;
1344 return PropertyFlags;
1347 #endregion
1349 #region FormScopeInfo
1351 /// <summary>
1352 /// Groups validation enabled configuration for a target set
1353 /// </summary>
1354 protected class FormScopeInfo
1356 private readonly string target;
1357 private readonly bool isValidationEnabled;
1359 /// <summary>
1360 /// Initializes a new instance of the <see cref="FormScopeInfo"/> class.
1361 /// </summary>
1362 /// <param name="target">The target.</param>
1363 /// <param name="isValidationEnabled">if set to <c>true</c> [is validation enabled].</param>
1364 public FormScopeInfo(string target, bool isValidationEnabled)
1366 this.target = target;
1367 this.isValidationEnabled = isValidationEnabled;
1370 /// <summary>
1371 /// Gets the root target.
1372 /// </summary>
1373 /// <value>The root target.</value>
1374 public string RootTarget
1376 get { return target; }
1379 /// <summary>
1380 /// Gets a value indicating whether this instance is validation enabled.
1381 /// </summary>
1382 /// <value>
1383 /// <c>true</c> if this instance is validation enabled; otherwise, <c>false</c>.
1384 /// </value>
1385 public bool IsValidationEnabled
1387 get { return isValidationEnabled; }
1391 #endregion