C#如何构建表达式<func <t,u =“”>&gt;对象的每个属性?

时间:2018-06-19 00:02:56

标签: c# reflection

我有一个类FormBuilder<TObject>,其方法为Property<TProperty>(Expression<Func<TObject, TProperty>> expression)。在构造函数中,我想在获取TObject的所有属性后调用该方法。我怎么能这样做呢?我现在的代码,从其他SO答案改编,只是抛出错误,我不知道如何解决它们。这就是我到目前为止所做的:

public abstract class FormBuilder<TObject> :
    FormBuilder {
    public FormBuilder() {
        //  REPLICATE: Property(p => p.NAME);

        var method = this.GetType().GetMethod("Property");
        var properties = typeof(TObject).GetProperties();

        var objectType = typeof(TObject);

        foreach (var p in properties) {
            var propertyType = p.GetType();

            var parameter = Expression.Parameter(propertyType, "p");
            var property = Expression.Property(parameter, p.Name);
            var @delegate = typeof(Func<,>).MakeGenericType(objectType, propertyType);

            //  throws: ParameterExpression of type 'System.Reflection.RuntimePropertyInfo'
            //  cannot be used for delegate parameter of type 'UserQuery+Command'
            var expression = Expression.Lambda(@delegate, property, new[] { parameter });

            //  method.MakeGenericMethod(parameter).Invoke(this, new[] { property });
        }
    }

    public FormPropertyBuilder<TProperty> Property<TProperty>(
        Expression<Func<TObject, TProperty>> expression) {
        var member = (expression.Body as MemberExpression).Member;
        var builder = new FormPropertyBuilder<TProperty>(member.Name);

        Properties.Add(builder);

        return builder;
    }
}

修改

public sealed class CommandFormBuilder :
    FormBuilder<Command> {
    public CommandFormBuilder() {
        Property(
            p => p.SourceOfLoss).HasLabel("Source of Loss");
    }
}

这就是我通常会调用该方法的方法,但我需要在构造每个属性时立即调用它。

我的最终目标是通过该方法简单地播种所有属性,并在必要时稍后对其进行进一步配置。我感谢任何帮助!

1 个答案:

答案 0 :(得分:1)

我回来了!

在@Nkosi的最后评论之后,我回到了绘图板上,从头开始。我太迷上了仿制药,一旦我退缩了一点,我就想出了如何得到自己想要的。不能说这是最正确的方法,但是由于我已经在整个应用程序中应用了它,因此似乎可以正常工作。我最终为特定任务(例如表单,表格,PDF文档)制作了多个构建器(模板构建器?)。这是我想出的。我将仅展示表单构建器,因为除适用于其方案的专用方法外,其他构建器几乎都会重复使用它。自发布问题以来,这是经过多次迭代的当前版本。哦,这个构建的项目是在VS 2013中的C#5上进行的,因此,一旦升级到较新的版本,就可以进行改进。

操作

public interface IFormActionBuilder {
    IFormActionBuilder HasFormId(
        string formId);

    IFormActionBuilder HasLabel(
        string label);

    IFormActionBuilder IsDefault();

    IFormActionBuilder IsFileUpload();
}

public interface IFormActionMetadata {
    string Controller { get; }
    bool Default { get; }
    string FormId { get; }
    string Enctype { get; }
    string Label { get; }
    string Name { get; }
}

public interface IFormGroupBuilder {
    IFormGroupBuilder HasOrder(
        short order);
}

public interface IFormGroupMetadata {
    string Label { get; }
    short Order { get; }
}

属性

public interface IFormPropertyBuilder {
    IFormPropertyBuilder HasFormat(
        string format);

    IFormPropertyBuilder HasLabel(
        string label);

    IFormPropertyBuilder HasOrder(
        short order);

    IFormPropertyBuilder HasType(
        FormFieldType type);

    IFormPropertyBuilder InGroup(
        string group);

    IFormPropertyBuilder IsHidden();

    IFormPropertyBuilder IsReadOnly();

    IFormPropertyBuilder IsRequired();
}

public interface IFormPropertyMetadata {
    string Format { get; }
    string Group { get; }
    bool IsIgnored { get; set; }
    string Label { get; }
    string Name { get; }
    short Order { get; }
    bool Required { get; }
    FormFieldType Type { get; }
}

从技术上讲,可以将动作,组和属性接口合并为一个,但是我选择将它们拆分为方法和属性,以在配置过程中和提取元数据时将它们保持整洁。

构建器

public interface IFormMetadata {
    IList<IFormActionMetadata> ActionMetadatas { get; }
    IList<IFormGroupMetadata> GroupMetadatas { get; }
    IList<IFormPropertyMetadata> PropertyMetadatas { get; }
}

public abstract class FormBuilder<TObject> :
    IFormMetadata {
    public IList<IFormActionMetadata> ActionMetadatas { get; private set; }
    public IList<IFormGroupMetadata> GroupMetadatas { get; private set; }
    public IList<IFormPropertyMetadata> PropertyMetadatas { get; private set; }

    protected FormBuilder() {
        ActionMetadatas = new List<IFormActionMetadata>();
        GroupMetadatas = new List<IFormGroupMetadata> {
            new GroupBuilder()
        };
        PropertyMetadatas = typeof(TObject).GetProperties().Select(
            p => new PropertyBuilder(p.Name)).Cast<IFormPropertyMetadata>().ToList();
    }

    public IFormActionBuilder Action<TController>(
        Expression<Action<TController>> action)
        where TController : IController {
        var method = ((MethodCallExpression)action.Body).Method;
        var controller = typeof(TController).Name.Replace("Controller", null);
        var builder = new ActionBuilder(method.Name, controller);

        ActionMetadatas.Add(builder);

        return builder;
    }

    public IFormGroupBuilder Group(
        string label) {
        var builder = new GroupBuilder(label);

        GroupMetadatas.Add(builder);

        return builder;
    }

    public void Ignore<TProperty>(
        Expression<Func<TObject, TProperty>> expression) {
        var member = ((MemberExpression)expression.Body).Member;
        var propertyMetadata = PropertyMetadatas.SingleOrDefault(
            b => b.Name == member.Name);

        if (propertyMetadata == null) {
            return;
        }

        propertyMetadata.IsIgnored = true;
    }

    protected static T Placeholder<T>() {
        return default(T);
    }

    public IFormPropertyBuilder Property<TProperty>(
        Expression<Func<TObject, TProperty>> expression) {
        var member = ((MemberExpression)expression.Body).Member;

        return PropertyMetadatas.Single(
            b => b.Name == member.Name) as IFormPropertyBuilder;
    }

    public sealed class ActionBuilder :
        IFormActionBuilder,
        IFormActionMetadata {
        public string Controller { get; private set; }
        public bool Default { get; private set; }
        public string Enctype { get; private set; }
        public string FormId { get; private set; }
        public string Label { get; private set; }
        public string Name { get; private set; }

        public ActionBuilder(
            string action,
            string controller) {
            Controller = controller;
            Enctype = "application/x-www-form-urlencoded";
            FormId = "panel-form";
            Label = "Save";
            Name = action;
        }

        public IFormActionBuilder HasFormId(
            string formId) {
            FormId = formId;

            return this;
        }

        public IFormActionBuilder HasLabel(
            string label) {
            Label = label;

            return this;
        }

        public IFormActionBuilder IsDefault() {
            Default = true;

            return this;
        }

        public IFormActionBuilder IsFileUpload() {
            Enctype = "multipart/form-data";

            return this;
        }
    }

    public sealed class GroupBuilder :
        IFormGroupBuilder,
        IFormGroupMetadata {
        public string Label { get; private set; }
        public short Order { get; private set; }

        public GroupBuilder() {
        }

        public GroupBuilder(
            string label) {
            Label = label;
        }

        public IFormGroupBuilder HasOrder(
            short order) {
            Order = order;

            return this;
        }
    }

    public sealed class PropertyBuilder :
        IFormPropertyBuilder,
        IFormPropertyMetadata {
        private static readonly Type StringType;

        static PropertyBuilder() {
            StringType = typeof(string);
        }

        public string Format { get; private set; }
        public string Group { get; private set; }
        public bool IsIgnored { get; set; }
        public string Label { get; private set; }
        public string Name { get; private set; }
        public short Order { get; private set; }
        public bool Required { get; private set; }
        public FormFieldType Type { get; private set; }

        public PropertyBuilder(
            string name) {
            Name = name;
            Order = short.MaxValue;
            Required = !IsNullable(name);
            Type = FormFieldType.Text;
        }

        public IFormPropertyBuilder HasFormat(
            string format) {
            Format = format;

            return this;
        }

        public IFormPropertyBuilder HasLabel(
            string label) {
            Label = label;

            return this;
        }

        public IFormPropertyBuilder HasOrder(
            short order) {
            Order = order;

            return this;
        }

        public IFormPropertyBuilder HasType(
            FormFieldType type) {
            Type = type;

            return this;
        }

        public IFormPropertyBuilder InGroup(
            string group) {
            Group = group;

            return this;
        }

        public IFormPropertyBuilder IsHidden() {
            Type = FormFieldType.Hidden;

            return this;
        }

        private static bool IsNullable(
            string name) {
            var type = typeof(TObject).GetProperty(name).PropertyType;

            if (type == StringType) {
                return true;
            }

            return type.IsValueType
                && Nullable.GetUnderlyingType(type) != null;
        }

        public IFormPropertyBuilder IsReadOnly() {
            Type = FormFieldType.None;

            return this;
        }

        public IFormPropertyBuilder IsRequired() {
            Required = true;

            return this;
        }
    }
}

查看

public sealed class FormView {
    public IList<Action> Actions { get; private set; }
    public IList<FormGroup> Groups { get; private set; }

    public FormView(
        object obj) {
        if (obj == null) {
            throw new ArgumentNullException("obj");
        }

        var type = obj.GetType();
        var builder = FormLoader.Get(type);

        if (builder == null) {
            var message = string.Format("Unable to find form builder for type: {0}", type.FullName);

            throw new InvalidOperationException(message);
        }

        Actions = GetActions(builder);
        Groups = GetGroups(obj, builder);
    }

    private static IList<Action> GetActions(
        IFormMetadata builder) {
        return builder.ActionMetadatas.Select(
            a => new Action {
                Controller = a.Controller,
                Enctype = a.Enctype,
                FormId = a.FormId,
                IsDefault = a.Default,
                Label = a.Label,
                Name = a.Name
            }).ToList();
    }

    private static object GetFieldValue(
        object obj,
        PropertyInfo property,
        IFormPropertyMetadata propertyMetadata) {
        var value = property.GetValue(obj, null);

        if (string.IsNullOrEmpty(propertyMetadata.Format)) {
            return value;
        }

        return string.Format(propertyMetadata.Format, value);
    }

    private static IList<FormField> GetFields(
        object obj,
        string group,
        IList<PropertyInfo> properties,
        IList<IFormPropertyMetadata> propertyMetadatas) {
        return properties.Select(
            p => {
                var propertyMetadata = propertyMetadatas.SingleOrDefault(
                    pm =>
                        !pm.IsIgnored
                        && pm.Name == p.Name);

                if (propertyMetadata == null) {
                    return null;
                }

                return new {
                    propertyMetadata.Group,
                    propertyMetadata.Label,
                    propertyMetadata.Name,
                    propertyMetadata.Order,
                    propertyMetadata.Required,
                    propertyMetadata.Type,
                    Value = GetFieldValue(obj, p, propertyMetadata)
                };
            }).Where(
            a =>
                a != null
                && a.Group == group).OrderBy(
            a => a.Order).Select(
            a => new FormField {
                IsRequired = a.Required,
                Label = a.Label,
                Name = a.Name,
                Type = a.Type,
                Value = a.Value
            }).ToList();
    }

    private static IList<FormGroup> GetGroups(
        object obj,
        IFormMetadata builder) {
        var properties = obj.GetType().GetProperties();

        return builder.GroupMetadatas.Select(
            g => new {
                g.Label,
                g.Order,
                Fields = GetFields(obj, g.Label, properties, builder.PropertyMetadatas)
            }).OrderBy(
            a => a.Order).Select(
            a => new FormGroup {
                Label = a.Label,
                Fields = a.Fields
            }).ToList();
    }

    public sealed class Action {
        public string Controller { get; set; }
        public string Enctype { get; set; }
        public string FormId { get; set; }
        public bool IsDefault { get; set; }
        public string Label { get; set; }
        public string Name { get; set; }
    }
}

加载程序

public static class FormLoader {
    private static IDictionary<Type, Builder> _builders;

    public static IFormMetadata Get<TObject>() {
        return Get(typeof(TObject));
    }

    public static IFormMetadata Get(
        Type type) {
        var builder = _builders[type];

        if (builder.Instance == null) {
            builder.Instance = Activator.CreateInstance(builder.Type) as IFormMetadata;
        }

        return builder.Instance;
    }

    public static void Initialize() {
        Initialize(Assembly.GetExecutingAssembly());
    }

    public static void Initialize(
        params Assembly[] assemblies) {
        var type = typeof(FormBuilder<>);

        _builders = assemblies.SelectMany(
            a => a.GetTypes()).Where(
            t =>
                !t.IsAbstract
                && !t.IsInterface
                && t.BaseType != null
                && t.BaseType.IsGenericType
                && t.BaseType.GetGenericTypeDefinition() == type).Select(
            t => new {
                ObjectType = t.BaseType.GetGenericArguments()[0],
                BuilderType = t
            }).ToDictionary(
            k => k.ObjectType,
            v => new Builder {
                Type = v.BuilderType
            });
    }

    private sealed class Builder {
        public IFormMetadata Instance { get; set; }
        public Type Type { get; set; }
    }
}

我不太满意的装载机。它可以工作,但是每种构建器类型都有一个加载器,因此我只重复很多次。前几天,我花了几个小时试图将它们全部合并为一个,但并没有恢复原状。两者之间的主要区别是它们更改了接口。如果有人对改进加载程序和/或任何其他代码有建议,我将很高兴听到他们的声音。

对于两个构建器,我确实有一个特殊情况,它们需要继承基础对象的构建器,因此它们有一个IncludeBase<TObjectBase>方法。我对此不是很自信,但是它可以工作。

public void IncludeBase<TObjectBase>() {
    var baseBuilder = DocumentLoader.Get<TObjectBase>();

    if (baseBuilder == null) {
        return;
    }

    foreach (var basePropertyMetadata in baseBuilder.PropertyMetadatas) {
        var propertyMetadata = PropertyMetadatas.SingleOrDefault(
            pm => pm.Name == basePropertyMetadata.Name);

        if (propertyMetadata == null) {
            continue;
        }

        propertyMetadata = basePropertyMetadata;
    }
}

就是这样。感谢您的阅读,如果您对本书有所了解,我也要感谢@Nkosi,使我摆脱了自己陷入的陷阱。