【发布时间】:2014-06-03 08:46:06
【问题描述】:
我目前正在为 MVC 编写一个 HtmlHelper 扩展的网格,但我遇到了一个奇怪的问题。
首先,这是我用来构建网格的代码:
RenderedOutput = HtmlHelper.GridFor(Model)
.WithColumns(column =>
{
column.Bind(x => x.Name)
.WithCss("inline");
column.Bind(x => x.Age)
.WithCss("inline fixed right");
})
.Render();
现在,整个 Fluent API 都是使用接口构建的,为了让 copy-past 的工作更小,我不在这里发布接口,但我会发布实现:
首先,HtmlHelper:
public static IGridBuilder<TEntity> GridFor<TModel, TEntity>(this HtmlHelper<TModel> htmlHelper,
IEnumerable<TEntity> dataSource)
{
return new GridBuilder<TEntity>(htmlHelper, dataSource);
}
所以这会返回一个 GridBuilder,它的实现在这里:
#region Constructors
public GridBuilder(HtmlHelper helper, IEnumerable<TModel> data)
{
HtmlHelper = helper;
DataSource = data;
ColumnBuilders = new List<IColumnBuilder<TModel>>();
}
#endregion
#region Properties
private string Id { get; set; }
#endregion
#region IGridBuilder Members
public IList<IColumnBuilder<TModel>> ColumnBuilders { get; protected set; }
public IEnumerable<TModel> DataSource { get; private set; }
public HtmlHelper HtmlHelper { get; private set; }
public IGridBuilder<TModel> WithId(string id)
{
Id = id;
return this;
}
public IGridBuilder<TModel> WithColumns(Action<IColumnBuilder<TModel>> bindAllColumns)
{
bindAllColumns(new ColumnBuilder<TModel>(this));
return this;
}
public HtmlString Render()
{
var outputBuilder = new StringBuilder();
var headerMember = new TagBuilder("div");
headerMember.MergeAttribute("class", "gridHolder v-scroll");
if (!string.IsNullOrEmpty(Id))
{
headerMember.GenerateId(Id);
}
outputBuilder.AppendLine(headerMember.ToString(TagRenderMode.StartTag));
// Process all the available columns.
var rowBuilder = new TagBuilder("div");
rowBuilder.MergeAttribute("class", "row");
outputBuilder.AppendLine(rowBuilder.ToString(TagRenderMode.StartTag));
foreach (var column in ColumnBuilders)
{
var columnBuilder = new TagBuilder("div");
outputBuilder.AppendLine(columnBuilder.ToString(TagRenderMode.StartTag));
outputBuilder.AppendLine(columnBuilder.ToString(TagRenderMode.EndTag));
}
outputBuilder.AppendLine(rowBuilder.ToString(TagRenderMode.EndTag));
outputBuilder.AppendLine(headerMember.ToString(TagRenderMode.EndTag));
return new HtmlString(outputBuilder.ToString());
}
#endregion
}
GridBuilder 采用 IColumnBuilder 的 Action 来构建列,所以这里是:
public class ColumnBuilder<TModel> : IColumnBuilder<TModel>
{
#region Constructors
public ColumnBuilder(IGridBuilder<TModel> gridBuilder)
{
GridBuilderReference = gridBuilder;
}
#endregion
#region IColumnBuilder Members
public IGridBuilder<TModel> GridBuilderReference { get; private set; }
public string CssClass { get; private set; }
public IColumnBuilder<TModel> Bind<TItem>(Expression<Func<TModel, TItem>> propertySelector)
{
// Reset the properties. This is needed because they are not cleared automatticly. It's not a new instance which is created.
CssClass = null;
GridBuilderReference.ColumnBuilders.Add(this);
return this;
}
public IColumnBuilder<TModel> WithCss(string className)
{
CssClass = className;
return this;
}
#endregion
}
首先,这是我的第一个流畅的接口实现,所以如果这不是好的方法,请指出正确的方向。
情况:
-
当我构建网格时,我传入一个绑定列的动作,这些列也再次流畅,(例如,我可以在列上添加一个类)。现在,为了渲染这些,我认为在我的 IGridBuilder 中保存对所有 IColumnBuilder 实例的引用是一个好主意,这样在我的 GridBuilder 渲染方法中我可以执行类似的操作
// 在列之前渲染。
foreach(myColumns 中的 var c) { c.渲染(); }
// 在列之后渲染。
因此,我在 GridBuilder 中创建了一个列表,其中将包含所有 ColumnBuilder。 当我执行 WithColumns 时,传递了我的 GridBuilder 对象 (this),然后在 ColumnBuilder 中,在每个 Bind() 函数上,我将 ColumnBuilder 对象添加到传递的引用中。
但这有一个奇怪的行为(例如,包含 ColumnBuilders 的列表,确实都与最后执行的 ColumnBuilder 的属性相匹配)。
【问题讨论】:
-
cmets 有必要吗?他们增加了很多噪音。您能否清楚地说明您在哪里遇到了问题——也许给出一个带有预期/实际结果的用例?
-
@DaveParsons 猜测问题是
.WithColumns只创建一个列构建器,然后绑定多次,覆盖以前的绑定?我认为.WithColumns应该提供一个IColumnBuilderFactory,它有一个方法Add或New。无论是那个还是Bind都应该是一个奇怪的列扩展方法,它允许列为空并创建一个新的 -
所以,我已经删除了代码中的 cmets 并进行了更详细的解释。希望有帮助。 @JamesBarrass您介意为您提到的工厂提供一个小例子吗?再次检查我自己的代码后,您所讲述的故事似乎是正确的,但我没有直接看到我如何特别解决这个问题。因此,如果您能提供一个小例子,那就太好了。提前致谢。
标签: c# asp.net-mvc html-helper fluent-interface