Example #1
0
        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));
        }
Example #2
0
        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)));
        }
Example #3
0
        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);
        }
Example #4
0
        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));
                    }
Example #6
0
        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));
        }