我回来了!
在@nkosi最后一次评论之后,我回到画板上从头开始。我太沉迷于仿制药了,一旦我退后一点,我就知道如何得到我想要的。不能说这是最合适的方法,但是现在我已经把它应用到我的整个应用程序中了,它似乎已经开始工作了。我最终制作了多个构建器(模板构建器?)对于特定任务,如窗体、表、PDF文档。这就是我想到的。我将只展示表单生成器,因为它对于所有其他生成器都是非常重复的,除了针对其场景的专用方法。这是自我发布问题以来经过多次迭代后的当前版本。哦,这个项目是在C 5上的vs 2013中构建的,所以一旦升级到新版本,就可能进行改进。
行动
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; }
}
从技术上讲,操作、组和属性接口可以合并为一个接口,但我选择将它们拆分为方法和属性,以便在配置期间和提取元数据时保持IntelliSense的整洁。
建设者
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把我从我自己陷入的陷阱中解救出来。