public static IValueProvider GetResource <T>(object value, object defaultValue, Func <string, object> deserializer) { if (value == null) { return(new LiteralValue(defaultValue)); } if (value is string expression) { var boundExpression = BoundExpression.Parse(expression); switch (boundExpression.Resources.Count) { case 0 when deserializer != null: return(new LiteralValue(deserializer(expression))); case 1 when boundExpression.StringFormat == null: return(new CoercedValueProvider <T>(boundExpression.Resources[0], defaultValue)); default: throw new ArgumentException( $"The expression '{expression}' is not a valid resource because it does not define a single value source.", nameof(value)); } } if (value is T) { return(new LiteralValue(value)); } throw new ArgumentException( $"The provided value must be a bound resource or a literal value of type '{typeof(T).FullName}'.", nameof(value)); }
public static Func <IResourceContext, IErrorStringProvider> GetErrorProvider(string message, string propertyKey) { var func = GetValueProvider(propertyKey); var boundExpression = BoundExpression.Parse(message, new Dictionary <string, object> { ["Value"] = func }); if (boundExpression.IsPlainString) { var errorMessage = boundExpression.StringFormat; return(context => new PlainErrorStringProvider(errorMessage)); } if (boundExpression.Resources.Any( res => res is DeferredProxyResource resource && resource.ProxyProvider == func)) { var key = propertyKey; return(context => new ValueErrorStringProvider(boundExpression.GetStringValue(context), GetValueProxy(context, key))); } return(context => new ErrorStringProvider(boundExpression.GetStringValue(context))); }
public FormElement TryBuild(IFormProperty property, Func <string, object> deserializer) { var selectFrom = property.GetCustomAttribute <SelectFromAttribute>(); if (selectFrom == null) { return(null); } var type = property.PropertyType; var field = new SelectionField(property.Name, property.PropertyType); if (selectFrom.DisplayPath != null) { field.DisplayPath = BoundExpression.ParseSimplified(selectFrom.DisplayPath); } if (selectFrom.ValuePath != null) { field.ValuePath = BoundExpression.ParseSimplified(selectFrom.ValuePath); } if (selectFrom.ItemStringFormat != null) { field.ItemStringFormat = BoundExpression.ParseSimplified(selectFrom.ItemStringFormat); } field.SelectionType = Utilities.GetResource <SelectionType>(selectFrom.SelectionType, SelectionType.ComboBox, Deserializers.Enum <SelectionType>()); switch (selectFrom.ItemsSource) { case string expr: var value = BoundExpression.Parse(expr); if (!value.IsSingleResource) { throw new InvalidOperationException("ItemsSource must be a single resource reference."); } field.ItemsSource = value.Resources[0]; break; case IEnumerable <object> enumerable: field.ItemsSource = new LiteralValue(enumerable.ToList()); break; case Type enumType: if (!enumType.IsEnum) { throw new InvalidOperationException("A type argument for ItemsSource must be an enum."); } var values = Enum.GetValues(enumType); var collection = new List <KeyValuePair <ValueType, IValueProvider> >(); foreach (Enum enumValue in values) { var enumName = enumValue.ToString(); var memInfo = enumType.GetMember(enumName); var attributes = memInfo[0].GetCustomAttributes(typeof(EnumDisplayAttribute), false); IValueProvider name; if (attributes.Length > 0) { var attr = (EnumDisplayAttribute)attributes[0]; name = BoundExpression.ParseSimplified(attr.Name); } else { name = new LiteralValue(enumName.Humanize()); } collection.Add(new KeyValuePair <ValueType, IValueProvider>(enumValue, name)); } field.ItemsSource = new EnumerableStringValueProvider(collection); field.DisplayPath = new LiteralValue(nameof(StringProxy.Value)); field.ValuePath = new LiteralValue(type == typeof(string) ? nameof(StringProxy.Value) : nameof(StringProxy.Key)); break; } return(field); }
private FormDefinition BuildDefinition(Type type) { // Only classes are allowed. // Primitives should be retrieved from prebuilt definitions. if (!type.IsClass || typeof(MulticastDelegate).IsAssignableFrom(type.BaseType)) { return(null); } var formDefinition = new FormDefinition(type); var mode = DefaultFields.AllExcludingReadonly; var grid = new[] { 1d }; var beforeFormContent = new List <AttrElementTuple>(); var afterFormContent = new List <AttrElementTuple>(); foreach (var attribute in type.GetCustomAttributes()) { switch (attribute) { case ResourceAttribute resource: formDefinition.Resources.Add(resource.Name, resource.Value is string expr ? (IValueProvider)BoundExpression.Parse(expr) : new LiteralValue(resource.Value)); break; case FormAttribute form: mode = form.Mode; grid = form.Grid; if (grid == null || grid.Length < 1) { grid = new[] { 1d }; } break; case FormContentAttribute contentAttribute: if (contentAttribute.Placement == Placement.After) { afterFormContent.Add(new AttrElementTuple(contentAttribute, contentAttribute.GetElement())); } else if (contentAttribute.Placement == Placement.Before) { beforeFormContent.Add(new AttrElementTuple(contentAttribute, contentAttribute.GetElement())); } break; case MetaAttribute meta: if (!string.IsNullOrEmpty(meta.Name)) { formDefinition.Metadata[meta.Name] = meta.Value; } break; } } beforeFormContent.Sort((a, b) => a.Attr.Position.CompareTo(b.Attr.Position)); afterFormContent.Sort((a, b) => a.Attr.Position.CompareTo(b.Attr.Position)); var gridLength = grid.Length; // Pass one - get list of valid properties. var properties = Utilities .GetProperties(type, mode) .Select(p => new PropertyInfoWrapper(p)) .ToArray(); // Pass two - build form elements. var elements = new List <ElementWrapper>(); foreach (var property in properties) { var deserializer = TryGetDeserializer(property.PropertyType); // Query property builders. var element = Build(property, deserializer); if (element == null) { // Unhandled properties are ignored. continue; } // Pass three - initialize elements. foreach (var initializer in FieldInitializers) { initializer.Initialize(element, property, deserializer); } var wrapper = new ElementWrapper(element, property); // Set layout. var attr = property.GetCustomAttribute <FieldAttribute>(); if (attr != null) { wrapper.Position = attr.Position; wrapper.Row = attr.Row; wrapper.Column = attr.Column; wrapper.ColumnSpan = attr.ColumnSpan; } elements.Add(wrapper); } // Pass four - order elements. elements = elements.OrderBy(element => element.Position).ToList(); // Pass five - group rows and calculate layout. var layout = PerformLayout(grid, elements); // Pass six - add attached elements. var rows = new List <FormRow>(); // Before form. rows.AddRange(CreateRows(beforeFormContent, gridLength)); foreach (var row in layout) { var before = new List <AttrElementTuple>(); var after = new List <AttrElementTuple>(); foreach (var element in row.Elements) { var property = element.Property; foreach (var attr in property.GetCustomAttributes <FormContentAttribute>()) { if (attr.Placement == Placement.Before) { before.Add(new AttrElementTuple(attr, attr.GetElement())); } else if (attr.Placement == Placement.After) { after.Add(new AttrElementTuple(attr, attr.GetElement())); } } } before.Sort((a, b) => a.Attr.Position.CompareTo(b.Attr.Position)); after.Sort((a, b) => a.Attr.Position.CompareTo(b.Attr.Position)); // Before element. rows.AddRange(CreateRows(before, gridLength)); // Field row. var formRow = new FormRow(); formRow.Elements.AddRange( row.Elements.Select(w => { var inlineElements = w.Property .GetCustomAttributes <FormContentAttribute>() .Where(attr => attr.Placement == Placement.Inline) .Select(attr => new AttrElementTuple(attr, attr.GetElement())) .OrderBy(tuple => tuple.Attr.Position) .ToList(); w.Element.LinePosition = (Position)(-1); if (inlineElements.Count != 0) { return(new FormElementContainer(w.Column, w.ColumnSpan, inlineElements .Select(t => t.Element) .Concat(new[] { w.Element }) .ToList())); } return(new FormElementContainer(w.Column, w.ColumnSpan, w.Element)); })); rows.Add(formRow); // After element. rows.AddRange(CreateRows(after, gridLength)); } // After form. rows.AddRange(CreateRows(afterFormContent, gridLength)); // Wrap up everything. formDefinition.Grid = grid; formDefinition.FormRows = rows; formDefinition.Freeze(); foreach (var element in formDefinition.FormRows.SelectMany(r => r.Elements).SelectMany(c => c.Elements)) { element.Freeze(); } return(formDefinition); }
private static IValidatorProvider CreateValidator(Type modelType, string propertyKey, ValueAttribute attribute, Func <string, object> deserializer) { Func <IResourceContext, IProxy> argumentProvider; var argument = attribute.Argument; if (argument is string expression) { var boundExpression = BoundExpression.Parse(expression); if (boundExpression.IsPlainString) { var literal = new PlainObject(deserializer != null ? deserializer(boundExpression.StringFormat) : boundExpression.StringFormat); argumentProvider = context => literal; } else { var getString = boundExpression.StringFormat != null; var action = attribute.ArgumentUpdatedAction; var notify = action == ValidationAction.ClearErrors || action == ValidationAction.ValidateField; argumentProvider = context => { var value = getString ? (IProxy)boundExpression.GetStringValue(context) : boundExpression.GetValue(context); if (notify) { value.ValueChanged = () => { object model; try { model = context.GetModelInstance(); } catch { // Something went wrong so it's best to // disable the feature entirely. value.ValueChanged = null; return; } if (model is ExpandoObject && modelType == null) { // Do nothing. } else if (model == null || model.GetType() != modelType) { // Self dispose when form indicates model change. value.ValueChanged = null; return; } if (action == ValidationAction.ValidateField) { ModelState.Validate(model, propertyKey); } else { ModelState.ClearValidationErrors(model, propertyKey); } }; } return(value); }; } } else { var literal = new PlainObject(argument); argumentProvider = context => literal; } BindingProxy ValueProvider(IResourceContext context) { var key = new BindingProxyKey(propertyKey); if (context.TryFindResource(key) is BindingProxy proxy) { return(proxy); } proxy = new BindingProxy(); context.AddResource(key, proxy); return(proxy); } Func <IResourceContext, IBoolProxy> isEnforcedProvider; switch (attribute.When) { case null: isEnforcedProvider = context => new PlainBool(true); break; case string expr: var boundExpression = BoundExpression.Parse(expr); if (!boundExpression.IsSingleResource) { throw new ArgumentException( "The provided value must be a bound resource or a literal bool value.", nameof(attribute)); } isEnforcedProvider = context => boundExpression.GetBoolValue(context); break; case bool b: isEnforcedProvider = context => new PlainBool(b); break; default: throw new ArgumentException( "The provided value must be a bound resource or a literal bool value.", nameof(attribute)); } Func <IResourceContext, IErrorStringProvider> errorProvider; var message = attribute.Message; if (message == null) { switch (attribute.Condition) { case Must.BeGreaterThan: message = "Value must be greater than {Argument}."; break; case Must.BeGreaterThanOrEqualTo: message = "Value must be greater than or equal to {Argument}."; break; case Must.BeLessThan: message = "Value must be less than {Argument}."; break; case Must.BeLessThanOrEqualTo: message = "Value must be less than or equal to {Argument}."; break; case Must.BeEmpty: message = "@Field must be empty."; break; case Must.NotBeEmpty: message = "@Field cannot be empty."; break; default: message = "@Invalid value."; break; } } { var func = new Func <IResourceContext, IProxy>(ValueProvider); var boundExpression = BoundExpression.Parse(message, new Dictionary <string, object> { ["Value"] = func, ["Argument"] = argumentProvider }); if (boundExpression.IsPlainString) { var errorMessage = boundExpression.StringFormat; errorProvider = context => new PlainErrorStringProvider(errorMessage); } else { if (boundExpression.Resources.Any( res => res is DeferredProxyResource resource && resource.ProxyProvider == func)) { errorProvider = context => new ValueErrorStringProvider(boundExpression.GetStringValue(context), ValueProvider(context)); }
public static IValueProvider GetResource <T>(object value, object defaultValue, Func <string, object> deserializer) { if (value == null) { return(new LiteralValue(defaultValue)); } if (value is string expression) { var boundExpression = BoundExpression.Parse(expression); switch (boundExpression.Resources.Count) { case 0: return(new LiteralValue( deserializer != null && boundExpression.StringFormat != null ? deserializer(boundExpression.UnescapedStringFormat()) : boundExpression.UnescapedStringFormat())); case 1 when boundExpression.StringFormat == null: return(new CoercedValueProvider <T>(boundExpression.Resources[0], defaultValue)); default: if (typeof(T) == typeof(bool)) { var expr = boundExpression.UnescapedStringFormat(); string converter = null; for (var i = expr.Length - 1; i >= 0; i--) { var c = expr[i]; if (c == '}') { var next = i > 0 ? expr[i - 1] : '\0'; if (next == '}') { i--; continue; } break; } if (c == '|') { var next = i > 0 ? expr[i - 1] : '\0'; if (next == '|') { // This will throw later anyway... break; } converter = expr.Substring(i + 1); expr = expr.Substring(0, i); break; } } var ast = BooleanExpression.Parse(expr); return(new MultiBooleanBinding(ast, boundExpression.Resources, converter)); } throw new ArgumentException( $"The expression '{expression}' is not a valid resource because it does not define a single value source.", nameof(value)); } } if (value is T) { return(new LiteralValue(value)); } throw new ArgumentException( $"The provided value must be a bound resource or a literal value of type '{typeof(T).FullName}'.", nameof(value)); }